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.ElementsPanel = function() | |
32 { | |
33 WebInspector.Panel.call(this); | |
34 | |
35 this.element.addStyleClass("elements"); | |
36 | |
37 this.contentElement = document.createElement("div"); | |
38 this.contentElement.id = "elements-content"; | |
39 this.contentElement.className = "outline-disclosure"; | |
40 | |
41 this.treeOutline = new WebInspector.ElementsTreeOutline(); | |
42 this.treeOutline.panel = this; | |
43 this.treeOutline.includeRootDOMNode = false; | |
44 this.treeOutline.selectEnabled = true; | |
45 | |
46 this.treeOutline.focusedNodeChanged = function(forceUpdate) | |
47 { | |
48 if (this.panel.visible && WebInspector.currentFocusElement !== document.
getElementById("search")) | |
49 WebInspector.currentFocusElement = document.getElementById("main-pan
els"); | |
50 | |
51 this.panel.updateBreadcrumb(forceUpdate); | |
52 | |
53 for (var pane in this.panel.sidebarPanes) | |
54 this.panel.sidebarPanes[pane].needsUpdate = true; | |
55 | |
56 this.panel.updateStyles(true); | |
57 this.panel.updateMetrics(); | |
58 this.panel.updateProperties(); | |
59 this.panel.updateEventListeners(); | |
60 | |
61 if (InspectorController.searchingForNode()) { | |
62 InspectorController.toggleNodeSearch(); | |
63 this.panel.nodeSearchButton.toggled = false; | |
64 } | |
65 if (this._focusedDOMNode) | |
66 InjectedScriptAccess.addInspectedNode(this._focusedDOMNode.id, funct
ion() {}); | |
67 }; | |
68 | |
69 this.contentElement.appendChild(this.treeOutline.element); | |
70 | |
71 this.crumbsElement = document.createElement("div"); | |
72 this.crumbsElement.className = "crumbs"; | |
73 this.crumbsElement.addEventListener("mousemove", this._mouseMovedInCrumbs.bi
nd(this), false); | |
74 this.crumbsElement.addEventListener("mouseout", this._mouseMovedOutOfCrumbs.
bind(this), false); | |
75 | |
76 this.sidebarPanes = {}; | |
77 this.sidebarPanes.styles = new WebInspector.StylesSidebarPane(); | |
78 this.sidebarPanes.metrics = new WebInspector.MetricsSidebarPane(); | |
79 this.sidebarPanes.properties = new WebInspector.PropertiesSidebarPane(); | |
80 this.sidebarPanes.eventListeners = new WebInspector.EventListenersSidebarPan
e(); | |
81 | |
82 this.sidebarPanes.styles.onexpand = this.updateStyles.bind(this); | |
83 this.sidebarPanes.metrics.onexpand = this.updateMetrics.bind(this); | |
84 this.sidebarPanes.properties.onexpand = this.updateProperties.bind(this); | |
85 this.sidebarPanes.eventListeners.onexpand = this.updateEventListeners.bind(t
his); | |
86 | |
87 this.sidebarPanes.styles.expanded = true; | |
88 | |
89 this.sidebarPanes.styles.addEventListener("style edited", this._stylesPaneEd
ited, this); | |
90 this.sidebarPanes.styles.addEventListener("style property toggled", this._st
ylesPaneEdited, this); | |
91 this.sidebarPanes.metrics.addEventListener("metrics edited", this._metricsPa
neEdited, this); | |
92 | |
93 this.sidebarElement = document.createElement("div"); | |
94 this.sidebarElement.id = "elements-sidebar"; | |
95 | |
96 this.sidebarElement.appendChild(this.sidebarPanes.styles.element); | |
97 this.sidebarElement.appendChild(this.sidebarPanes.metrics.element); | |
98 this.sidebarElement.appendChild(this.sidebarPanes.properties.element); | |
99 this.sidebarElement.appendChild(this.sidebarPanes.eventListeners.element); | |
100 | |
101 this.sidebarResizeElement = document.createElement("div"); | |
102 this.sidebarResizeElement.className = "sidebar-resizer-vertical"; | |
103 this.sidebarResizeElement.addEventListener("mousedown", this.rightSidebarRes
izerDragStart.bind(this), false); | |
104 | |
105 this.nodeSearchButton = new WebInspector.StatusBarButton(WebInspector.UIStri
ng("Select an element in the page to inspect it."), "node-search-status-bar-item
"); | |
106 this.nodeSearchButton.addEventListener("click", this._nodeSearchButtonClicke
d.bind(this), false); | |
107 | |
108 this.searchingForNode = false; | |
109 | |
110 this.element.appendChild(this.contentElement); | |
111 this.element.appendChild(this.sidebarElement); | |
112 this.element.appendChild(this.sidebarResizeElement); | |
113 | |
114 this._changedStyles = {}; | |
115 | |
116 this.reset(); | |
117 } | |
118 | |
119 WebInspector.ElementsPanel.prototype = { | |
120 toolbarItemClass: "elements", | |
121 | |
122 get toolbarItemLabel() | |
123 { | |
124 return WebInspector.UIString("Elements"); | |
125 }, | |
126 | |
127 get statusBarItems() | |
128 { | |
129 return [this.nodeSearchButton.element, this.crumbsElement]; | |
130 }, | |
131 | |
132 updateStatusBarItems: function() | |
133 { | |
134 this.updateBreadcrumbSizes(); | |
135 }, | |
136 | |
137 show: function() | |
138 { | |
139 WebInspector.Panel.prototype.show.call(this); | |
140 this.sidebarResizeElement.style.right = (this.sidebarElement.offsetWidth
- 3) + "px"; | |
141 this.updateBreadcrumb(); | |
142 this.treeOutline.updateSelection(); | |
143 if (this.recentlyModifiedNodes.length) | |
144 this._updateModifiedNodes(); | |
145 }, | |
146 | |
147 hide: function() | |
148 { | |
149 WebInspector.Panel.prototype.hide.call(this); | |
150 | |
151 WebInspector.hoveredDOMNode = null; | |
152 | |
153 if (InspectorController.searchingForNode()) { | |
154 InspectorController.toggleNodeSearch(); | |
155 this.nodeSearchButton.toggled = false; | |
156 } | |
157 }, | |
158 | |
159 resize: function() | |
160 { | |
161 this.treeOutline.updateSelection(); | |
162 this.updateBreadcrumbSizes(); | |
163 }, | |
164 | |
165 reset: function() | |
166 { | |
167 this.rootDOMNode = null; | |
168 this.focusedDOMNode = null; | |
169 | |
170 WebInspector.hoveredDOMNode = null; | |
171 | |
172 if (InspectorController.searchingForNode()) { | |
173 InspectorController.toggleNodeSearch(); | |
174 this.nodeSearchButton.toggled = false; | |
175 } | |
176 | |
177 this.recentlyModifiedNodes = []; | |
178 | |
179 delete this.currentQuery; | |
180 this.searchCanceled(); | |
181 | |
182 var domWindow = WebInspector.domAgent.domWindow; | |
183 if (!domWindow || !domWindow.document || !domWindow.document.firstChild) | |
184 return; | |
185 | |
186 // If the window isn't visible, return early so the DOM tree isn't built | |
187 // and mutation event listeners are not added. | |
188 if (!InspectorController.isWindowVisible()) | |
189 return; | |
190 | |
191 var inspectedRootDocument = domWindow.document; | |
192 inspectedRootDocument.addEventListener("DOMNodeInserted", this._nodeInse
rted.bind(this)); | |
193 inspectedRootDocument.addEventListener("DOMNodeRemoved", this._nodeRemov
ed.bind(this)); | |
194 | |
195 this.rootDOMNode = inspectedRootDocument; | |
196 | |
197 var canidateFocusNode = inspectedRootDocument.body || inspectedRootDocum
ent.documentElement; | |
198 if (canidateFocusNode) { | |
199 this.treeOutline.suppressSelectHighlight = true; | |
200 this.focusedDOMNode = canidateFocusNode; | |
201 this.treeOutline.suppressSelectHighlight = false; | |
202 | |
203 if (this.treeOutline.selectedTreeElement) | |
204 this.treeOutline.selectedTreeElement.expand(); | |
205 } | |
206 }, | |
207 | |
208 searchCanceled: function() | |
209 { | |
210 if (this._searchResults) { | |
211 for (var i = 0; i < this._searchResults.length; ++i) { | |
212 var treeElement = this.treeOutline.findTreeElement(this._searchR
esults[i]); | |
213 if (treeElement) | |
214 treeElement.highlighted = false; | |
215 } | |
216 } | |
217 | |
218 WebInspector.updateSearchMatchesCount(0, this); | |
219 | |
220 this._currentSearchResultIndex = 0; | |
221 this._searchResults = []; | |
222 InjectedScriptAccess.searchCanceled(function() {}); | |
223 }, | |
224 | |
225 performSearch: function(query) | |
226 { | |
227 // Call searchCanceled since it will reset everything we need before doi
ng a new search. | |
228 this.searchCanceled(); | |
229 | |
230 const whitespaceTrimmedQuery = query.trimWhitespace(); | |
231 if (!whitespaceTrimmedQuery.length) | |
232 return; | |
233 | |
234 this._updatedMatchCountOnce = false; | |
235 this._matchesCountUpdateTimeout = null; | |
236 | |
237 InjectedScriptAccess.performSearch(whitespaceTrimmedQuery, function() {}
); | |
238 }, | |
239 | |
240 _updateMatchesCount: function() | |
241 { | |
242 WebInspector.updateSearchMatchesCount(this._searchResults.length, this); | |
243 this._matchesCountUpdateTimeout = null; | |
244 this._updatedMatchCountOnce = true; | |
245 }, | |
246 | |
247 _updateMatchesCountSoon: function() | |
248 { | |
249 if (!this._updatedMatchCountOnce) | |
250 return this._updateMatchesCount(); | |
251 if (this._matchesCountUpdateTimeout) | |
252 return; | |
253 // Update the matches count every half-second so it doesn't feel twitchy
. | |
254 this._matchesCountUpdateTimeout = setTimeout(this._updateMatchesCount.bi
nd(this), 500); | |
255 }, | |
256 | |
257 addNodesToSearchResult: function(nodeIds) | |
258 { | |
259 if (!nodeIds) | |
260 return; | |
261 | |
262 var nodeIdsArray = nodeIds.split(","); | |
263 for (var i = 0; i < nodeIdsArray.length; ++i) { | |
264 var nodeId = nodeIdsArray[i]; | |
265 var node = WebInspector.domAgent.nodeForId(nodeId); | |
266 if (!node) | |
267 continue; | |
268 | |
269 if (!this._searchResults.length) { | |
270 this._currentSearchResultIndex = 0; | |
271 | |
272 // Only change the focusedDOMNode if the search was manually per
formed, because | |
273 // the search may have been performed programmatically and we wo
uldn't want to | |
274 // change the current focusedDOMNode. | |
275 if (WebInspector.currentFocusElement === document.getElementById
("search")) | |
276 this.focusedDOMNode = node; | |
277 } | |
278 | |
279 this._searchResults.push(node); | |
280 | |
281 // Highlight the tree element to show it matched the search. | |
282 // FIXME: highlight the substrings in text nodes and attributes. | |
283 var treeElement = this.treeOutline.findTreeElement(node); | |
284 if (treeElement) | |
285 treeElement.highlighted = true; | |
286 } | |
287 | |
288 this._updateMatchesCountSoon(); | |
289 }, | |
290 | |
291 jumpToNextSearchResult: function() | |
292 { | |
293 if (!this._searchResults || !this._searchResults.length) | |
294 return; | |
295 if (++this._currentSearchResultIndex >= this._searchResults.length) | |
296 this._currentSearchResultIndex = 0; | |
297 this.focusedDOMNode = this._searchResults[this._currentSearchResultIndex
]; | |
298 }, | |
299 | |
300 jumpToPreviousSearchResult: function() | |
301 { | |
302 if (!this._searchResults || !this._searchResults.length) | |
303 return; | |
304 if (--this._currentSearchResultIndex < 0) | |
305 this._currentSearchResultIndex = (this._searchResults.length - 1); | |
306 this.focusedDOMNode = this._searchResults[this._currentSearchResultIndex
]; | |
307 }, | |
308 | |
309 renameSelector: function(oldIdentifier, newIdentifier, oldSelector, newSelec
tor) | |
310 { | |
311 // TODO: Implement Shifting the oldSelector, and its contents to a newSe
lector | |
312 }, | |
313 | |
314 addStyleChange: function(identifier, style, property) | |
315 { | |
316 if (!style.parentRule) | |
317 return; | |
318 | |
319 var selector = style.parentRule.selectorText; | |
320 if (!this._changedStyles[identifier]) | |
321 this._changedStyles[identifier] = {}; | |
322 | |
323 if (!this._changedStyles[identifier][selector]) | |
324 this._changedStyles[identifier][selector] = {}; | |
325 | |
326 if (!this._changedStyles[identifier][selector][property]) | |
327 WebInspector.styleChanges += 1; | |
328 | |
329 this._changedStyles[identifier][selector][property] = style.getPropertyV
alue(property); | |
330 }, | |
331 | |
332 removeStyleChange: function(identifier, style, property) | |
333 { | |
334 if (!style.parentRule) | |
335 return; | |
336 | |
337 var selector = style.parentRule.selectorText; | |
338 if (!this._changedStyles[identifier] || !this._changedStyles[identifier]
[selector]) | |
339 return; | |
340 | |
341 if (this._changedStyles[identifier][selector][property]) { | |
342 delete this._changedStyles[identifier][selector][property]; | |
343 WebInspector.styleChanges -= 1; | |
344 } | |
345 }, | |
346 | |
347 generateStylesheet: function() | |
348 { | |
349 if (!WebInspector.styleChanges) | |
350 return; | |
351 | |
352 // Merge Down to Just Selectors | |
353 var mergedSelectors = {}; | |
354 for (var identifier in this._changedStyles) { | |
355 for (var selector in this._changedStyles[identifier]) { | |
356 if (!mergedSelectors[selector]) | |
357 mergedSelectors[selector] = this._changedStyles[identifier][
selector]; | |
358 else { // merge on selector | |
359 var merge = {}; | |
360 for (var property in mergedSelectors[selector]) | |
361 merge[property] = mergedSelectors[selector][property]; | |
362 for (var property in this._changedStyles[identifier][selecto
r]) { | |
363 if (!merge[property]) | |
364 merge[property] = this._changedStyles[identifier][se
lector][property]; | |
365 else { // merge on property within a selector, include c
omment to notify user | |
366 var value1 = merge[property]; | |
367 var value2 = this._changedStyles[identifier][selecto
r][property]; | |
368 | |
369 if (value1 === value2) | |
370 merge[property] = [value1]; | |
371 else if (value1 instanceof Array) | |
372 merge[property].push(value2); | |
373 else | |
374 merge[property] = [value1, value2]; | |
375 } | |
376 } | |
377 mergedSelectors[selector] = merge; | |
378 } | |
379 } | |
380 } | |
381 | |
382 var builder = []; | |
383 builder.push("/**"); | |
384 builder.push(" * Inspector Generated Stylesheet"); // UIString? | |
385 builder.push(" */\n"); | |
386 | |
387 var indent = " "; | |
388 function displayProperty(property, value, comment) { | |
389 if (comment) | |
390 return indent + "/* " + property + ": " + value + "; */"; | |
391 else | |
392 return indent + property + ": " + value + ";"; | |
393 } | |
394 | |
395 for (var selector in mergedSelectors) { | |
396 var psuedoStyle = mergedSelectors[selector]; | |
397 var properties = Object.properties(psuedoStyle); | |
398 if (properties.length) { | |
399 builder.push(selector + " {"); | |
400 for (var i = 0; i < properties.length; ++i) { | |
401 var property = properties[i]; | |
402 var value = psuedoStyle[property]; | |
403 if (!(value instanceof Array)) | |
404 builder.push(displayProperty(property, value)); | |
405 else { | |
406 if (value.length === 1) | |
407 builder.push(displayProperty(property, value) + " /*
merged from equivalent edits */"); // UIString? | |
408 else { | |
409 builder.push(indent + "/* There was a Conflict... Th
ere were Multiple Edits for '" + property + "' */"); // UIString? | |
410 for (var j = 0; j < value.length; ++j) | |
411 builder.push(displayProperty(property, value, tr
ue)); | |
412 } | |
413 } | |
414 } | |
415 builder.push("}\n"); | |
416 } | |
417 } | |
418 | |
419 WebInspector.showConsole(); | |
420 WebInspector.console.addMessage(new WebInspector.ConsoleTextMessage(buil
der.join("\n"))); | |
421 }, | |
422 | |
423 get rootDOMNode() | |
424 { | |
425 return this.treeOutline.rootDOMNode; | |
426 }, | |
427 | |
428 set rootDOMNode(x) | |
429 { | |
430 this.treeOutline.rootDOMNode = x; | |
431 }, | |
432 | |
433 get focusedDOMNode() | |
434 { | |
435 return this.treeOutline.focusedDOMNode; | |
436 }, | |
437 | |
438 set focusedDOMNode(x) | |
439 { | |
440 this.treeOutline.focusedDOMNode = x; | |
441 }, | |
442 | |
443 _nodeInserted: function(event) | |
444 { | |
445 this.recentlyModifiedNodes.push({node: event.target, parent: event.relat
edNode, inserted: true}); | |
446 if (this.visible) | |
447 this._updateModifiedNodesSoon(); | |
448 }, | |
449 | |
450 _nodeRemoved: function(event) | |
451 { | |
452 this.recentlyModifiedNodes.push({node: event.target, parent: event.relat
edNode, removed: true}); | |
453 if (this.visible) | |
454 this._updateModifiedNodesSoon(); | |
455 }, | |
456 | |
457 _updateModifiedNodesSoon: function() | |
458 { | |
459 if ("_updateModifiedNodesTimeout" in this) | |
460 return; | |
461 this._updateModifiedNodesTimeout = setTimeout(this._updateModifiedNodes.
bind(this), 0); | |
462 }, | |
463 | |
464 _updateModifiedNodes: function() | |
465 { | |
466 if ("_updateModifiedNodesTimeout" in this) { | |
467 clearTimeout(this._updateModifiedNodesTimeout); | |
468 delete this._updateModifiedNodesTimeout; | |
469 } | |
470 | |
471 var updatedParentTreeElements = []; | |
472 var updateBreadcrumbs = false; | |
473 | |
474 for (var i = 0; i < this.recentlyModifiedNodes.length; ++i) { | |
475 var replaced = this.recentlyModifiedNodes[i].replaced; | |
476 var parent = this.recentlyModifiedNodes[i].parent; | |
477 if (!parent) | |
478 continue; | |
479 | |
480 var parentNodeItem = this.treeOutline.findTreeElement(parent); | |
481 if (parentNodeItem && !parentNodeItem.alreadyUpdatedChildren) { | |
482 parentNodeItem.updateChildren(replaced); | |
483 parentNodeItem.alreadyUpdatedChildren = true; | |
484 updatedParentTreeElements.push(parentNodeItem); | |
485 } | |
486 | |
487 if (!updateBreadcrumbs && (this.focusedDOMNode === parent || isAnces
torNode(this.focusedDOMNode, parent))) | |
488 updateBreadcrumbs = true; | |
489 } | |
490 | |
491 for (var i = 0; i < updatedParentTreeElements.length; ++i) | |
492 delete updatedParentTreeElements[i].alreadyUpdatedChildren; | |
493 | |
494 this.recentlyModifiedNodes = []; | |
495 | |
496 if (updateBreadcrumbs) | |
497 this.updateBreadcrumb(true); | |
498 }, | |
499 | |
500 _stylesPaneEdited: function() | |
501 { | |
502 this.sidebarPanes.metrics.needsUpdate = true; | |
503 this.updateMetrics(); | |
504 }, | |
505 | |
506 _metricsPaneEdited: function() | |
507 { | |
508 this.sidebarPanes.styles.needsUpdate = true; | |
509 this.updateStyles(true); | |
510 }, | |
511 | |
512 _mouseMovedInCrumbs: function(event) | |
513 { | |
514 var nodeUnderMouse = document.elementFromPoint(event.pageX, event.pageY)
; | |
515 var crumbElement = nodeUnderMouse.enclosingNodeOrSelfWithClass("crumb"); | |
516 | |
517 WebInspector.hoveredDOMNode = (crumbElement ? crumbElement.representedOb
ject : null); | |
518 | |
519 if ("_mouseOutOfCrumbsTimeout" in this) { | |
520 clearTimeout(this._mouseOutOfCrumbsTimeout); | |
521 delete this._mouseOutOfCrumbsTimeout; | |
522 } | |
523 }, | |
524 | |
525 _mouseMovedOutOfCrumbs: function(event) | |
526 { | |
527 var nodeUnderMouse = document.elementFromPoint(event.pageX, event.pageY)
; | |
528 if (nodeUnderMouse.isDescendant(this.crumbsElement)) | |
529 return; | |
530 | |
531 WebInspector.hoveredDOMNode = null; | |
532 | |
533 this._mouseOutOfCrumbsTimeout = setTimeout(this.updateBreadcrumbSizes.bi
nd(this), 1000); | |
534 }, | |
535 | |
536 updateBreadcrumb: function(forceUpdate) | |
537 { | |
538 if (!this.visible) | |
539 return; | |
540 | |
541 var crumbs = this.crumbsElement; | |
542 | |
543 var handled = false; | |
544 var foundRoot = false; | |
545 var crumb = crumbs.firstChild; | |
546 while (crumb) { | |
547 if (crumb.representedObject === this.rootDOMNode) | |
548 foundRoot = true; | |
549 | |
550 if (foundRoot) | |
551 crumb.addStyleClass("dimmed"); | |
552 else | |
553 crumb.removeStyleClass("dimmed"); | |
554 | |
555 if (crumb.representedObject === this.focusedDOMNode) { | |
556 crumb.addStyleClass("selected"); | |
557 handled = true; | |
558 } else { | |
559 crumb.removeStyleClass("selected"); | |
560 } | |
561 | |
562 crumb = crumb.nextSibling; | |
563 } | |
564 | |
565 if (handled && !forceUpdate) { | |
566 // We don't need to rebuild the crumbs, but we need to adjust sizes | |
567 // to reflect the new focused or root node. | |
568 this.updateBreadcrumbSizes(); | |
569 return; | |
570 } | |
571 | |
572 crumbs.removeChildren(); | |
573 | |
574 var panel = this; | |
575 | |
576 function selectCrumbFunction(event) | |
577 { | |
578 var crumb = event.currentTarget; | |
579 if (crumb.hasStyleClass("collapsed")) { | |
580 // Clicking a collapsed crumb will expose the hidden crumbs. | |
581 if (crumb === panel.crumbsElement.firstChild) { | |
582 // If the focused crumb is the first child, pick the farthes
t crumb | |
583 // that is still hidden. This allows the user to expose ever
y crumb. | |
584 var currentCrumb = crumb; | |
585 while (currentCrumb) { | |
586 var hidden = currentCrumb.hasStyleClass("hidden"); | |
587 var collapsed = currentCrumb.hasStyleClass("collapsed"); | |
588 if (!hidden && !collapsed) | |
589 break; | |
590 crumb = currentCrumb; | |
591 currentCrumb = currentCrumb.nextSibling; | |
592 } | |
593 } | |
594 | |
595 panel.updateBreadcrumbSizes(crumb); | |
596 } else { | |
597 // Clicking a dimmed crumb or double clicking (event.detail >= 2
) | |
598 // will change the root node in addition to the focused node. | |
599 if (event.detail >= 2 || crumb.hasStyleClass("dimmed")) | |
600 panel.rootDOMNode = crumb.representedObject.parentNode; | |
601 panel.focusedDOMNode = crumb.representedObject; | |
602 } | |
603 | |
604 event.preventDefault(); | |
605 } | |
606 | |
607 foundRoot = false; | |
608 for (var current = this.focusedDOMNode; current; current = current.paren
tNode) { | |
609 if (current.nodeType === Node.DOCUMENT_NODE) | |
610 continue; | |
611 | |
612 if (current === this.rootDOMNode) | |
613 foundRoot = true; | |
614 | |
615 var crumb = document.createElement("span"); | |
616 crumb.className = "crumb"; | |
617 crumb.representedObject = current; | |
618 crumb.addEventListener("mousedown", selectCrumbFunction, false); | |
619 | |
620 var crumbTitle; | |
621 switch (current.nodeType) { | |
622 case Node.ELEMENT_NODE: | |
623 crumbTitle = current.nodeName.toLowerCase(); | |
624 | |
625 var nameElement = document.createElement("span"); | |
626 nameElement.textContent = crumbTitle; | |
627 crumb.appendChild(nameElement); | |
628 | |
629 var idAttribute = current.getAttribute("id"); | |
630 if (idAttribute) { | |
631 var idElement = document.createElement("span"); | |
632 crumb.appendChild(idElement); | |
633 | |
634 var part = "#" + idAttribute; | |
635 crumbTitle += part; | |
636 idElement.appendChild(document.createTextNode(part)); | |
637 | |
638 // Mark the name as extra, since the ID is more importan
t. | |
639 nameElement.className = "extra"; | |
640 } | |
641 | |
642 var classAttribute = current.getAttribute("class"); | |
643 if (classAttribute) { | |
644 var classes = classAttribute.split(/\s+/); | |
645 var foundClasses = {}; | |
646 | |
647 if (classes.length) { | |
648 var classesElement = document.createElement("span"); | |
649 classesElement.className = "extra"; | |
650 crumb.appendChild(classesElement); | |
651 | |
652 for (var i = 0; i < classes.length; ++i) { | |
653 var className = classes[i]; | |
654 if (className && !(className in foundClasses)) { | |
655 var part = "." + className; | |
656 crumbTitle += part; | |
657 classesElement.appendChild(document.createTe
xtNode(part)); | |
658 foundClasses[className] = true; | |
659 } | |
660 } | |
661 } | |
662 } | |
663 | |
664 break; | |
665 | |
666 case Node.TEXT_NODE: | |
667 if (isNodeWhitespace.call(current)) | |
668 crumbTitle = WebInspector.UIString("(whitespace)"); | |
669 else | |
670 crumbTitle = WebInspector.UIString("(text)"); | |
671 break | |
672 | |
673 case Node.COMMENT_NODE: | |
674 crumbTitle = "<!-->"; | |
675 break; | |
676 | |
677 case Node.DOCUMENT_TYPE_NODE: | |
678 crumbTitle = "<!DOCTYPE>"; | |
679 break; | |
680 | |
681 default: | |
682 crumbTitle = current.nodeName.toLowerCase(); | |
683 } | |
684 | |
685 if (!crumb.childNodes.length) { | |
686 var nameElement = document.createElement("span"); | |
687 nameElement.textContent = crumbTitle; | |
688 crumb.appendChild(nameElement); | |
689 } | |
690 | |
691 crumb.title = crumbTitle; | |
692 | |
693 if (foundRoot) | |
694 crumb.addStyleClass("dimmed"); | |
695 if (current === this.focusedDOMNode) | |
696 crumb.addStyleClass("selected"); | |
697 if (!crumbs.childNodes.length) | |
698 crumb.addStyleClass("end"); | |
699 | |
700 crumbs.appendChild(crumb); | |
701 } | |
702 | |
703 if (crumbs.hasChildNodes()) | |
704 crumbs.lastChild.addStyleClass("start"); | |
705 | |
706 this.updateBreadcrumbSizes(); | |
707 }, | |
708 | |
709 updateBreadcrumbSizes: function(focusedCrumb) | |
710 { | |
711 if (!this.visible) | |
712 return; | |
713 | |
714 if (document.body.offsetWidth <= 0) { | |
715 // The stylesheet hasn't loaded yet or the window is closed, | |
716 // so we can't calculate what is need. Return early. | |
717 return; | |
718 } | |
719 | |
720 var crumbs = this.crumbsElement; | |
721 if (!crumbs.childNodes.length || crumbs.offsetWidth <= 0) | |
722 return; // No crumbs, do nothing. | |
723 | |
724 // A Zero index is the right most child crumb in the breadcrumb. | |
725 var selectedIndex = 0; | |
726 var focusedIndex = 0; | |
727 var selectedCrumb; | |
728 | |
729 var i = 0; | |
730 var crumb = crumbs.firstChild; | |
731 while (crumb) { | |
732 // Find the selected crumb and index. | |
733 if (!selectedCrumb && crumb.hasStyleClass("selected")) { | |
734 selectedCrumb = crumb; | |
735 selectedIndex = i; | |
736 } | |
737 | |
738 // Find the focused crumb index. | |
739 if (crumb === focusedCrumb) | |
740 focusedIndex = i; | |
741 | |
742 // Remove any styles that affect size before | |
743 // deciding to shorten any crumbs. | |
744 if (crumb !== crumbs.lastChild) | |
745 crumb.removeStyleClass("start"); | |
746 if (crumb !== crumbs.firstChild) | |
747 crumb.removeStyleClass("end"); | |
748 | |
749 crumb.removeStyleClass("compact"); | |
750 crumb.removeStyleClass("collapsed"); | |
751 crumb.removeStyleClass("hidden"); | |
752 | |
753 crumb = crumb.nextSibling; | |
754 ++i; | |
755 } | |
756 | |
757 // Restore the start and end crumb classes in case they got removed in c
oalesceCollapsedCrumbs(). | |
758 // The order of the crumbs in the document is opposite of the visual ord
er. | |
759 crumbs.firstChild.addStyleClass("end"); | |
760 crumbs.lastChild.addStyleClass("start"); | |
761 | |
762 function crumbsAreSmallerThanContainer() | |
763 { | |
764 var rightPadding = 20; | |
765 var errorWarningElement = document.getElementById("error-warning-cou
nt"); | |
766 if (!WebInspector.drawer.visible && errorWarningElement) | |
767 rightPadding += errorWarningElement.offsetWidth; | |
768 return ((crumbs.totalOffsetLeft + crumbs.offsetWidth + rightPadding)
< window.innerWidth); | |
769 } | |
770 | |
771 if (crumbsAreSmallerThanContainer()) | |
772 return; // No need to compact the crumbs, they all fit at full size. | |
773 | |
774 var BothSides = 0; | |
775 var AncestorSide = -1; | |
776 var ChildSide = 1; | |
777 | |
778 function makeCrumbsSmaller(shrinkingFunction, direction, significantCrum
b) | |
779 { | |
780 if (!significantCrumb) | |
781 significantCrumb = (focusedCrumb || selectedCrumb); | |
782 | |
783 if (significantCrumb === selectedCrumb) | |
784 var significantIndex = selectedIndex; | |
785 else if (significantCrumb === focusedCrumb) | |
786 var significantIndex = focusedIndex; | |
787 else { | |
788 var significantIndex = 0; | |
789 for (var i = 0; i < crumbs.childNodes.length; ++i) { | |
790 if (crumbs.childNodes[i] === significantCrumb) { | |
791 significantIndex = i; | |
792 break; | |
793 } | |
794 } | |
795 } | |
796 | |
797 function shrinkCrumbAtIndex(index) | |
798 { | |
799 var shrinkCrumb = crumbs.childNodes[index]; | |
800 if (shrinkCrumb && shrinkCrumb !== significantCrumb) | |
801 shrinkingFunction(shrinkCrumb); | |
802 if (crumbsAreSmallerThanContainer()) | |
803 return true; // No need to compact the crumbs more. | |
804 return false; | |
805 } | |
806 | |
807 // Shrink crumbs one at a time by applying the shrinkingFunction unt
il the crumbs | |
808 // fit in the container or we run out of crumbs to shrink. | |
809 if (direction) { | |
810 // Crumbs are shrunk on only one side (based on direction) of th
e signifcant crumb. | |
811 var index = (direction > 0 ? 0 : crumbs.childNodes.length - 1); | |
812 while (index !== significantIndex) { | |
813 if (shrinkCrumbAtIndex(index)) | |
814 return true; | |
815 index += (direction > 0 ? 1 : -1); | |
816 } | |
817 } else { | |
818 // Crumbs are shrunk in order of descending distance from the si
gnifcant crumb, | |
819 // with a tie going to child crumbs. | |
820 var startIndex = 0; | |
821 var endIndex = crumbs.childNodes.length - 1; | |
822 while (startIndex != significantIndex || endIndex != significant
Index) { | |
823 var startDistance = significantIndex - startIndex; | |
824 var endDistance = endIndex - significantIndex; | |
825 if (startDistance >= endDistance) | |
826 var index = startIndex++; | |
827 else | |
828 var index = endIndex--; | |
829 if (shrinkCrumbAtIndex(index)) | |
830 return true; | |
831 } | |
832 } | |
833 | |
834 // We are not small enough yet, return false so the caller knows. | |
835 return false; | |
836 } | |
837 | |
838 function coalesceCollapsedCrumbs() | |
839 { | |
840 var crumb = crumbs.firstChild; | |
841 var collapsedRun = false; | |
842 var newStartNeeded = false; | |
843 var newEndNeeded = false; | |
844 while (crumb) { | |
845 var hidden = crumb.hasStyleClass("hidden"); | |
846 if (!hidden) { | |
847 var collapsed = crumb.hasStyleClass("collapsed"); | |
848 if (collapsedRun && collapsed) { | |
849 crumb.addStyleClass("hidden"); | |
850 crumb.removeStyleClass("compact"); | |
851 crumb.removeStyleClass("collapsed"); | |
852 | |
853 if (crumb.hasStyleClass("start")) { | |
854 crumb.removeStyleClass("start"); | |
855 newStartNeeded = true; | |
856 } | |
857 | |
858 if (crumb.hasStyleClass("end")) { | |
859 crumb.removeStyleClass("end"); | |
860 newEndNeeded = true; | |
861 } | |
862 | |
863 continue; | |
864 } | |
865 | |
866 collapsedRun = collapsed; | |
867 | |
868 if (newEndNeeded) { | |
869 newEndNeeded = false; | |
870 crumb.addStyleClass("end"); | |
871 } | |
872 } else | |
873 collapsedRun = true; | |
874 crumb = crumb.nextSibling; | |
875 } | |
876 | |
877 if (newStartNeeded) { | |
878 crumb = crumbs.lastChild; | |
879 while (crumb) { | |
880 if (!crumb.hasStyleClass("hidden")) { | |
881 crumb.addStyleClass("start"); | |
882 break; | |
883 } | |
884 crumb = crumb.previousSibling; | |
885 } | |
886 } | |
887 } | |
888 | |
889 function compact(crumb) | |
890 { | |
891 if (crumb.hasStyleClass("hidden")) | |
892 return; | |
893 crumb.addStyleClass("compact"); | |
894 } | |
895 | |
896 function collapse(crumb, dontCoalesce) | |
897 { | |
898 if (crumb.hasStyleClass("hidden")) | |
899 return; | |
900 crumb.addStyleClass("collapsed"); | |
901 crumb.removeStyleClass("compact"); | |
902 if (!dontCoalesce) | |
903 coalesceCollapsedCrumbs(); | |
904 } | |
905 | |
906 function compactDimmed(crumb) | |
907 { | |
908 if (crumb.hasStyleClass("dimmed")) | |
909 compact(crumb); | |
910 } | |
911 | |
912 function collapseDimmed(crumb) | |
913 { | |
914 if (crumb.hasStyleClass("dimmed")) | |
915 collapse(crumb); | |
916 } | |
917 | |
918 if (!focusedCrumb) { | |
919 // When not focused on a crumb we can be biased and collapse less im
portant | |
920 // crumbs that the user might not care much about. | |
921 | |
922 // Compact child crumbs. | |
923 if (makeCrumbsSmaller(compact, ChildSide)) | |
924 return; | |
925 | |
926 // Collapse child crumbs. | |
927 if (makeCrumbsSmaller(collapse, ChildSide)) | |
928 return; | |
929 | |
930 // Compact dimmed ancestor crumbs. | |
931 if (makeCrumbsSmaller(compactDimmed, AncestorSide)) | |
932 return; | |
933 | |
934 // Collapse dimmed ancestor crumbs. | |
935 if (makeCrumbsSmaller(collapseDimmed, AncestorSide)) | |
936 return; | |
937 } | |
938 | |
939 // Compact ancestor crumbs, or from both sides if focused. | |
940 if (makeCrumbsSmaller(compact, (focusedCrumb ? BothSides : AncestorSide)
)) | |
941 return; | |
942 | |
943 // Collapse ancestor crumbs, or from both sides if focused. | |
944 if (makeCrumbsSmaller(collapse, (focusedCrumb ? BothSides : AncestorSide
))) | |
945 return; | |
946 | |
947 if (!selectedCrumb) | |
948 return; | |
949 | |
950 // Compact the selected crumb. | |
951 compact(selectedCrumb); | |
952 if (crumbsAreSmallerThanContainer()) | |
953 return; | |
954 | |
955 // Collapse the selected crumb as a last resort. Pass true to prevent co
alescing. | |
956 collapse(selectedCrumb, true); | |
957 }, | |
958 | |
959 updateStyles: function(forceUpdate) | |
960 { | |
961 var stylesSidebarPane = this.sidebarPanes.styles; | |
962 if (!stylesSidebarPane.expanded || !stylesSidebarPane.needsUpdate) | |
963 return; | |
964 | |
965 stylesSidebarPane.update(this.focusedDOMNode, null, forceUpdate); | |
966 stylesSidebarPane.needsUpdate = false; | |
967 }, | |
968 | |
969 updateMetrics: function() | |
970 { | |
971 var metricsSidebarPane = this.sidebarPanes.metrics; | |
972 if (!metricsSidebarPane.expanded || !metricsSidebarPane.needsUpdate) | |
973 return; | |
974 | |
975 metricsSidebarPane.update(this.focusedDOMNode); | |
976 metricsSidebarPane.needsUpdate = false; | |
977 }, | |
978 | |
979 updateProperties: function() | |
980 { | |
981 var propertiesSidebarPane = this.sidebarPanes.properties; | |
982 if (!propertiesSidebarPane.expanded || !propertiesSidebarPane.needsUpdat
e) | |
983 return; | |
984 | |
985 propertiesSidebarPane.update(this.focusedDOMNode); | |
986 propertiesSidebarPane.needsUpdate = false; | |
987 }, | |
988 | |
989 updateEventListeners: function() | |
990 { | |
991 var eventListenersSidebarPane = this.sidebarPanes.eventListeners; | |
992 if (!eventListenersSidebarPane.expanded || !eventListenersSidebarPane.ne
edsUpdate) | |
993 return; | |
994 | |
995 eventListenersSidebarPane.update(this.focusedDOMNode); | |
996 eventListenersSidebarPane.needsUpdate = false; | |
997 }, | |
998 | |
999 handleKeyEvent: function(event) | |
1000 { | |
1001 this.treeOutline.handleKeyEvent(event); | |
1002 }, | |
1003 | |
1004 handleCopyEvent: function(event) | |
1005 { | |
1006 // Don't prevent the normal copy if the user has a selection. | |
1007 if (!window.getSelection().isCollapsed) | |
1008 return; | |
1009 event.clipboardData.clearData(); | |
1010 event.preventDefault(); | |
1011 InspectorController.copyNode(this.focusedDOMNode.id); | |
1012 }, | |
1013 | |
1014 rightSidebarResizerDragStart: function(event) | |
1015 { | |
1016 WebInspector.elementDragStart(this.sidebarElement, this.rightSidebarResi
zerDrag.bind(this), this.rightSidebarResizerDragEnd.bind(this), event, "col-resi
ze"); | |
1017 }, | |
1018 | |
1019 rightSidebarResizerDragEnd: function(event) | |
1020 { | |
1021 WebInspector.elementDragEnd(event); | |
1022 }, | |
1023 | |
1024 rightSidebarResizerDrag: function(event) | |
1025 { | |
1026 var x = event.pageX; | |
1027 var newWidth = Number.constrain(window.innerWidth - x, Preferences.minEl
ementsSidebarWidth, window.innerWidth * 0.66); | |
1028 | |
1029 this.sidebarElement.style.width = newWidth + "px"; | |
1030 this.contentElement.style.right = newWidth + "px"; | |
1031 this.sidebarResizeElement.style.right = (newWidth - 3) + "px"; | |
1032 | |
1033 this.treeOutline.updateSelection(); | |
1034 | |
1035 event.preventDefault(); | |
1036 }, | |
1037 | |
1038 _nodeSearchButtonClicked: function(event) | |
1039 { | |
1040 InspectorController.toggleNodeSearch(); | |
1041 | |
1042 this.nodeSearchButton.toggled = InspectorController.searchingForNode(); | |
1043 } | |
1044 } | |
1045 | |
1046 WebInspector.ElementsPanel.prototype.__proto__ = WebInspector.Panel.prototype; | |
OLD | NEW |