Rubber stamped by Hyatt.
[WebKit-https.git] / WebKit / WebInspector / WebInspector.m
1 /*
2  * Copyright (C) 2006 Apple Computer, Inc.  All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  *
8  * 1.  Redistributions of source code must retain the above copyright
9  *     notice, this list of conditions and the following disclaimer. 
10  * 2.  Redistributions in binary form must reproduce the above copyright
11  *     notice, this list of conditions and the following disclaimer in the
12  *     documentation and/or other materials provided with the distribution. 
13  * 3.  Neither the name of Apple Computer, Inc. ("Apple") nor the names of
14  *     its contributors may be used to endorse or promote products derived
15  *     from this software without specific prior written permission. 
16  *
17  * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
18  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
19  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
20  * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
21  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
22  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
23  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
24  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27  */
28
29 #import <WebKit/WebView.h>
30 #import <WebKit/WebViewPrivate.h>
31 #import <WebKit/WebHTMLView.h>
32 #import <WebKit/WebFrame.h>
33 #import <WebKit/DOMCore.h>
34 #import <WebKit/DOMEvents.h>
35 #import <WebKit/DOMHTML.h>
36 #import <WebKit/DOMCSS.h>
37 #import <WebKit/DOMTraversal.h>
38 #import <WebKit/DOMExtensions.h>
39 #import <WebKit/DOMPrivate.h>
40 #import <WebKit/WebInspector.h>
41 #import <WebKit/WebInspectorInternal.h>
42 #import <WebKit/WebLocalizableStrings.h>
43
44 #import "WebKitNSStringExtras.h"
45 #import "WebNodeHighlight.h"
46 #import "WebInspectorPanel.h"
47 #import "WebInspectorOutlineView.h"
48
49 static NSMapTable *lengthIgnoringWhitespaceCache = NULL;
50 static NSMapTable *lastChildIndexIgnoringWhitespaceCache = NULL;
51 static NSMapTable *lastChildIgnoringWhitespaceCache = NULL;
52
53 @implementation WebInspector
54 + (WebInspector *)sharedWebInspector
55 {
56     static WebInspector *_sharedWebInspector = nil;
57     if (!_sharedWebInspector) {
58         _sharedWebInspector = [[self alloc] initWithWebFrame:nil];
59         _sharedWebInspector->_private->isSharedInspector = YES;
60     }
61     return _sharedWebInspector;
62 }
63
64 #pragma mark -
65
66 - (id)init
67 {
68     if (![super initWithWindow:nil])
69         return nil;
70
71     [self setWindowFrameAutosaveName:@"Web Inspector"];
72
73     _private = [[WebInspectorPrivate alloc] init];
74     _private->ignoreWhitespace = YES;
75
76     return self;
77 }
78
79 - (id)initWithWebFrame:(WebFrame *)webFrame
80 {
81     if (![self init])
82         return nil;
83     [self setWebFrame:webFrame];
84     return self;
85 }
86
87 - (void)dealloc
88 {
89     [[NSNotificationCenter defaultCenter] removeObserver:self name:@"WebNodeHighlightExpired" object:nil];
90     [_private release];
91     [super dealloc];
92 }
93
94 #pragma mark -
95
96 - (NSWindow *)window
97 {
98     NSWindow *window = [super window];
99     if (!window) {
100         NSPanel *window = [[WebInspectorPanel alloc] initWithContentRect:NSMakeRect(60.0, 200.0, 350.0, 550.0) styleMask:(NSBorderlessWindowMask | NSUtilityWindowMask) backing:NSBackingStoreBuffered defer:YES];
101         [window setBackgroundColor:[NSColor clearColor]];
102         [window setOpaque:NO];
103         [window setHasShadow:YES];
104         [window _setContentHasShadow:NO];
105         [window setWorksWhenModal:YES];
106         [window setAcceptsMouseMovedEvents:YES];
107         [window setIgnoresMouseEvents:NO];
108         [window setFloatingPanel:YES];
109         [window setReleasedWhenClosed:YES];
110         [window setMovableByWindowBackground:YES];
111         [window setDelegate:self];
112         [window setMinSize:NSMakeSize(280.0, 450.0)];
113
114         _private->webView = [[WebView alloc] initWithFrame:[[window contentView] frame] frameName:nil groupName:nil];
115         [_private->webView setFrameLoadDelegate:self];
116         [_private->webView setUIDelegate:self];
117         [_private->webView setDrawsBackground:NO];
118         [_private->webView _setDashboardBehavior:WebDashboardBehaviorAlwaysSendMouseEventsToAllWindows to:YES];
119         [_private->webView _setDashboardBehavior:WebDashboardBehaviorAlwaysAcceptsFirstMouse to:YES];
120         [[_private->webView windowScriptObject] setValue:self forKey:@"Inspector"];
121
122         [window setContentView:_private->webView];
123
124         NSString *path = [[NSBundle bundleForClass:[self class]] pathForResource:@"inspector" ofType:@"html" inDirectory:@"webInspector"];
125         [[_private->webView mainFrame] loadRequest:[[[NSURLRequest alloc] initWithURL:[NSURL fileURLWithPath:path]] autorelease]];
126
127         [self setWindow:window];
128         [self windowDidLoad];
129         return window;
130     }
131
132     return window;
133 }
134
135 - (void)windowDidLoad
136 {
137     if (_private->windowLoaded)
138         return;
139     _private->windowLoaded = YES;
140
141     _private->rightArrowImage = [[NSImage alloc] initWithContentsOfFile:[[NSBundle bundleForClass:[self class]] pathForResource:@"rightTriangle" ofType:@"png" inDirectory:@"webInspector/Images"]];
142     _private->downArrowImage = [[NSImage alloc] initWithContentsOfFile:[[NSBundle bundleForClass:[self class]] pathForResource:@"downTriangle" ofType:@"png" inDirectory:@"webInspector/Images"]];
143
144     if ([_private->searchQuery length]) {
145         [self _showSearchResults:YES];
146         [self _refreshSearch];
147     }
148
149     [self performSelector:@selector(_highlightNode:) withObject:_private->focusedNode afterDelay:0.0];
150     [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_nodeHighlightExpired:) name:@"WebNodeHighlightExpired" object:nil];
151 }
152
153 - (void)windowWillClose:(NSNotification *)notification
154 {
155     if (!_private->isSharedInspector)
156         [self release];
157 }
158
159 - (IBAction)showWindow:(id)sender
160 {
161     [self _update];
162     [super showWindow:sender];
163 }
164
165 #pragma mark -
166
167 - (void)setWebFrame:(WebFrame *)webFrame
168 {
169     if ([webFrame isEqual:_private->webFrame])
170         return;
171
172     [webFrame retain];
173     [_private->webFrame release];
174     _private->webFrame = webFrame;
175
176     [self setFocusedDOMNode:[webFrame DOMDocument]];
177 }
178
179 - (WebFrame *)webFrame
180 {
181     return _private->webFrame;
182 }
183
184 #pragma mark -
185
186 - (void)setRootDOMNode:(DOMNode *)node
187 {
188     if (![node hasChildNodes] || [node isSameNode:_private->rootNode])
189         return;
190
191     [node retain];
192     [_private->rootNode release];
193     _private->rootNode = node;
194
195     [self _updateRoot];
196 }
197
198 - (DOMNode *)rootDOMNode
199 {
200     return _private->rootNode;
201 }
202
203 #pragma mark -
204
205 - (void)setFocusedDOMNode:(DOMNode *)node
206 {
207     if ([node isSameNode:_private->focusedNode])
208         return;
209
210     [[NSRunLoop currentRunLoop] cancelPerformSelector:@selector(_highlightNode:) target:self argument:_private->focusedNode];
211
212     [node retain];
213     [_private->focusedNode release];
214     _private->focusedNode = node;
215
216     DOMNode *root = _private->rootNode;
217     if (!root || (![root isSameNode:node] && ![root _isAncestorOfNode:node]))
218         [self setRootDOMNode:node];
219
220     if (!_private->webViewLoaded)
221         return;
222
223     [self _revealAndSelectNodeInTree:node];
224     [self _update];
225
226     if (!_private->preventHighlight) {
227         NSRect bounds = [node boundingBox];
228         if (!NSIsEmptyRect(bounds)) {
229             NSRect visible = [[[_private->webFrame frameView] documentView] visibleRect];
230             BOOL needsScroll = !NSContainsRect(visible, bounds) && !NSContainsRect(bounds, visible);
231
232             // only scroll if the bounds isn't in the visible rect and dosen't contain the visible rect
233             if (needsScroll) {
234                 // scroll to the parent element if we arn't focued on an element
235                 DOMElement *element = (DOMElement *)_private->focusedNode;
236                 if (![element isKindOfClass:[DOMElement class]])
237                     element = (DOMElement *)[element parentNode];
238
239                 if ([element isKindOfClass:[DOMElement class]])
240                     [element scrollIntoViewIfNeeded:YES];
241
242                 // give time for the scroll to happen
243                 [self performSelector:@selector(_highlightNode:) withObject:node afterDelay:0.25];
244             } else
245                 [self _highlightNode:node];
246         } else
247             [_private->currentHighlight expire];
248     }
249 }
250
251 - (DOMNode *)focusedDOMNode
252 {
253     return _private->focusedNode;
254 }
255
256 #pragma mark -
257
258 - (void)setSearchQuery:(NSString *)query
259 {
260     if ([query isEqualToString:_private->searchQuery])
261         return;
262
263     [query retain];
264     [_private->searchQuery release];
265     _private->searchQuery = query;
266
267     if (_private->webViewLoaded) {
268         DOMHTMLInputElement *search = (DOMHTMLInputElement *)[_private->domDocument getElementById:@"search"];
269         [search setValue:(query ? query : @"")];
270         [self _showSearchResults:([query length] ? YES : NO)];
271         [self _refreshSearch];
272     }
273 }
274
275 - (NSString *)searchQuery
276 {
277     return _private->searchQuery;
278 }
279
280 #pragma mark -
281
282 - (NSArray *)searchResults
283 {
284     return [NSArray arrayWithArray:_private->searchResults];
285 }
286 @end
287
288 #pragma mark -
289
290 @implementation WebInspector (WebInspectorScripting)
291 - (void)showOptionsMenu
292 {
293     NSMenu *menu = [[NSMenu alloc] init];
294     [menu setAutoenablesItems:NO];
295
296     NSMenuItem *item = [[[NSMenuItem alloc] init] autorelease];
297     [item setTitle:@"Ignore Whitespace"];
298     [item setTarget:self];
299     [item setAction:@selector(_toggleIgnoreWhitespace:)];
300     [item setState:_private->ignoreWhitespace];
301     [menu addItem:item];
302
303     [NSMenu popUpContextMenu:menu withEvent:[[self window] currentEvent] forView:_private->webView];
304     [menu release];
305
306     // hack to force a layout and balance mouse events
307     NSEvent *currentEvent = [[self window] currentEvent];
308     NSEvent *event = [NSEvent mouseEventWithType:NSLeftMouseUp location:[currentEvent locationInWindow] modifierFlags:[currentEvent modifierFlags] timestamp:[currentEvent timestamp] windowNumber:[[currentEvent window] windowNumber] context:[currentEvent context] eventNumber:[currentEvent eventNumber] clickCount:[currentEvent clickCount] pressure:[currentEvent pressure]];
309     [[[[_private->webView mainFrame] frameView] documentView] mouseUp:event];
310 }
311
312 - (void)selectNewRoot:(DOMHTMLSelectElement *)popup
313 {
314     unsigned index = [popup selectedIndex];
315     unsigned count = 0;
316
317     DOMNode *currentNode = _private->rootNode;
318     while (currentNode) {
319         if (count == index) {
320             [self setRootDOMNode:currentNode];
321             break;
322         }
323         count++;
324         currentNode = [currentNode parentNode];
325     }
326 }
327
328 - (void)resizeTopArea:(DOMEvent *)domEvent
329 {
330     NSWindow *window = [self window];
331     NSEvent *event = [window currentEvent];
332     NSPoint lastLocation = [window convertBaseToScreen:[event locationInWindow]];
333     NSRect lastFrame = [window frame];
334     NSSize minSize = [window minSize];
335     NSSize maxSize = [window maxSize];
336     BOOL mouseUpOccurred = NO;
337
338     DOMHTMLElement *topArea = (DOMHTMLElement *)[_private->domDocument getElementById:@"top"];
339     DOMHTMLElement *splitter = (DOMHTMLElement *)[_private->domDocument getElementById:@"splitter"];
340     DOMHTMLElement *bottomArea = (DOMHTMLElement *)[_private->domDocument getElementById:@"bottom"];
341
342     while (!mouseUpOccurred) {
343         // set mouseUp flag here, but process location of event before exiting from loop, leave mouseUp in queue
344         event = [window nextEventMatchingMask:(NSLeftMouseDraggedMask | NSLeftMouseUpMask) untilDate:[NSDate distantFuture] inMode:NSEventTrackingRunLoopMode dequeue:YES];
345
346         if ([event type] == NSLeftMouseUp)
347             mouseUpOccurred = YES;
348
349         NSPoint newLocation = [window convertBaseToScreen:[event locationInWindow]];
350         if (NSEqualPoints(newLocation, lastLocation))
351             continue;
352
353         NSRect proposedRect = lastFrame;
354         long delta = newLocation.y - lastLocation.y;
355         proposedRect.size.height -= delta;
356         proposedRect.origin.y += delta;
357
358         if (proposedRect.size.height < minSize.height) {
359             proposedRect.origin.y -= minSize.height - proposedRect.size.height;
360             proposedRect.size.height = minSize.height;
361         } else if (proposedRect.size.height > maxSize.height) {
362             proposedRect.origin.y -= maxSize.height - proposedRect.size.height;
363             proposedRect.size.height = maxSize.height;
364         }
365
366         NSNumber *baseValue = [topArea valueForKey:@"offsetHeight"];
367         NSString *newValue = [NSString stringWithFormat:@"%dpx", [baseValue unsignedLongValue] - delta];
368         [[topArea style] setProperty:@"height" :newValue :@""];
369
370         baseValue = [splitter valueForKey:@"offsetTop"];
371         newValue = [NSString stringWithFormat:@"%dpx", [baseValue unsignedLongValue] - delta];
372         [[splitter style] setProperty:@"top" :newValue :@""];
373
374         baseValue = [bottomArea valueForKey:@"offsetTop"];
375         newValue = [NSString stringWithFormat:@"%dpx", [baseValue unsignedLongValue] - delta];
376         [[bottomArea style] setProperty:@"top" :newValue :@""];
377
378         [window setFrame:proposedRect display:YES];
379         lastLocation = newLocation;
380         lastFrame = proposedRect;
381
382         [self _updateTreeScrollbar];
383     }
384
385     // post the mouse up event back to the queue so the WebView can get it
386     [window postEvent:event atStart:YES];
387 }
388
389 - (void)treeViewScrollTo:(float)number
390 {
391     float bottom = NSHeight([_private->treeOutlineView frame]) - NSHeight([_private->treeScrollView documentVisibleRect]);
392     number = MAX(0.0, MIN(bottom, number));
393     [[_private->treeScrollView contentView] scrollToPoint:NSMakePoint(0.0, number)];
394 }
395
396 - (float)treeViewOffsetTop
397 {
398     return NSMinY([_private->treeScrollView documentVisibleRect]);
399 }
400
401 - (float)treeViewScrollHeight
402 {
403     return NSHeight([_private->treeOutlineView frame]);
404 }
405
406 - (void)traverseTreeBackward
407 {
408     if (_private->searchResultsVisible) {
409         // if we have a search showing, we will only walk up and down the results
410         int row = [_private->treeOutlineView selectedRow];
411         if (row == -1)
412             return;
413         if ((row - 1) >= 0) {
414             row--;
415             [_private->treeOutlineView selectRowIndexes:[NSIndexSet indexSetWithIndex:row] byExtendingSelection:NO];
416             [_private->treeOutlineView scrollRowToVisible:row];
417         }
418         return;
419     }
420
421     DOMNode *node =  nil;
422     // traverse backward, holding opton will traverse only to the previous sibling
423     if ([[[self window] currentEvent] modifierFlags] & NSAlternateKeyMask) {
424         if (!_private->ignoreWhitespace)
425             node = [_private->focusedNode previousSibling];
426         else
427             node = [_private->focusedNode _previousSiblingSkippingWhitespace];
428     } else {
429         if (!_private->ignoreWhitespace)
430             node = [_private->focusedNode _traversePreviousNode];
431         else
432             node = [_private->focusedNode _traversePreviousNodeSkippingWhitespace];
433     }
434
435     if (node) {
436         DOMNode *root = _private->rootNode;
437         if (![root isSameNode:node] && ![root _isAncestorOfNode:node])
438             [self setRootDOMNode:[_private->focusedNode _firstAncestorCommonWithNode:node]];
439         [self setFocusedDOMNode:node];
440     }
441 }
442
443 - (void)traverseTreeForward
444 {
445     if (_private->searchResultsVisible) {
446         // if we have a search showing, we will only walk up and down the results
447         int row = [_private->treeOutlineView selectedRow];
448         if (row == -1)
449             return;
450         if ((row + 1) < (int)[_private->searchResults count]) {
451             row++;
452             [_private->treeOutlineView selectRowIndexes:[NSIndexSet indexSetWithIndex:row] byExtendingSelection:NO];
453             [_private->treeOutlineView scrollRowToVisible:row];
454         }
455         return;
456     }
457
458     DOMNode *node =  nil;
459     // traverse forward, holding opton will traverse only to the next sibling
460     if ([[[self window] currentEvent] modifierFlags] & NSAlternateKeyMask) {
461         if (!_private->ignoreWhitespace)
462             node = [_private->focusedNode nextSibling];
463         else
464             node = [_private->focusedNode _nextSiblingSkippingWhitespace];
465     } else {
466         if (!_private->ignoreWhitespace)
467             node = [_private->focusedNode _traverseNextNodeStayingWithin:nil];
468         else
469             node = [_private->focusedNode _traverseNextNodeSkippingWhitespaceStayingWithin:nil];
470     }
471
472     if (node) {
473         DOMNode *root = _private->rootNode;
474         if (![root isSameNode:node] && ![root _isAncestorOfNode:node])
475             [self setRootDOMNode:[_private->focusedNode _firstAncestorCommonWithNode:node]];
476         [self setFocusedDOMNode:node];
477     }
478 }
479 @end
480
481 #pragma mark -
482
483 @implementation WebInspector (WebInspectorPrivate)
484 - (IBAction)_toggleIgnoreWhitespace:(id)sender
485 {
486     _private->ignoreWhitespace = !_private->ignoreWhitespace;
487     [_private->treeOutlineView reloadData];
488 }
489
490 - (void)_highlightNode:(DOMNode *)node
491 {
492     if (_private->currentHighlight) {
493         [_private->currentHighlight expire];
494         [_private->currentHighlight release];
495         _private->currentHighlight = nil;
496     }
497
498     NSView *view = [[_private->webFrame frameView] documentView];
499     NSRect bounds = NSIntersectionRect([node boundingBox], [view visibleRect]);
500     if (!NSIsEmptyRect(bounds)) {
501         NSArray *rects = nil;
502         if ([node isKindOfClass:[DOMElement class]]) {
503             DOMCSSStyleDeclaration *style = [_private->domDocument getComputedStyle:(DOMElement *)node :@""];
504             if ([[style getPropertyValue:@"display"] isEqualToString:@"inline"])
505                 rects = [node lineBoxRects];
506         } else if ([node isKindOfClass:[DOMText class]])
507             rects = [node lineBoxRects];
508
509         if (![rects count])
510             rects = [NSArray arrayWithObject:[NSValue valueWithRect:bounds]];
511
512         _private->currentHighlight = [[WebNodeHighlight alloc] initWithBounds:bounds andRects:rects forView:view];
513     }
514 }
515
516 - (void)_nodeHighlightExpired:(NSNotification *)notification
517 {
518     if (_private->currentHighlight == [notification object]) {
519         [_private->currentHighlight release];
520         _private->currentHighlight = nil;
521     }
522 }
523
524 - (void)_focusRootNode:(id)sender
525 {
526     id node = nil;
527     if ([sender isKindOfClass:[NSMenuItem class]]) {
528         node = [sender representedObject];
529     } else if(sender == _private->treeOutlineView) {
530         int index = [sender selectedRow];
531         if (index == -1)
532             return;
533         node = [sender itemAtRow:index];
534     }
535
536     if (![node isSameNode:_private->rootNode]) {
537         [self setRootDOMNode:node];
538         [self setSearchQuery:nil]; // exit search mode
539     }
540 }
541
542 - (void)_revealAndSelectNodeInTree:(DOMNode *)node
543 {
544
545     if (!_private->webViewLoaded || !node)
546         return;
547
548     if (!_private->preventRevealOnFocus) {
549         NSMutableArray *ancestors = [[NSMutableArray alloc] init];
550         DOMNode *currentNode = [node parentNode];
551         while (currentNode) {
552             [ancestors addObject:currentNode];
553             if ([currentNode isSameNode:_private->rootNode])
554                 break;
555             currentNode = [currentNode parentNode];
556         }
557
558         NSEnumerator *enumerator = [ancestors reverseObjectEnumerator];
559         while ((currentNode = [enumerator nextObject]))
560             [_private->treeOutlineView expandItem:currentNode];
561
562         [ancestors release];
563     }
564
565     int index = [_private->treeOutlineView rowForItem:node];
566     if (index != -1) {
567         [_private->treeOutlineView selectRowIndexes:[NSIndexSet indexSetWithIndex:index] byExtendingSelection:NO];
568         [_private->treeOutlineView scrollRowToVisible:index];
569     }
570 }
571
572 - (void)_showSearchResults:(BOOL)show
573 {
574     if (show == _private->searchResultsVisible)
575         return;
576
577     DOMHTMLElement *treePopup = (DOMHTMLElement *)[_private->domDocument getElementById:@"treePopup"];
578     DOMHTMLElement *searchField = (DOMHTMLElement *)[_private->domDocument getElementById:@"search"];
579     DOMHTMLElement *searchCount = (DOMHTMLElement *)[_private->domDocument getElementById:@"searchCount"];
580
581     if (show) {
582         [[treePopup style] setDisplay:@"none"];
583         [[searchCount style] setDisplay:@"block"];
584         [[searchField style] setWidth:@"150px"];
585     } else {
586         [[treePopup style] removeProperty:@"display"];
587         [[searchCount style] removeProperty:@"display"];
588         [[searchField style] setWidth:@"90px"];
589     }
590
591     _private->preventRevealOnFocus = show;
592     _private->searchResultsVisible = show;
593 }
594
595 - (void)_refreshSearch
596 {
597     if (!_private->searchResults)
598         _private->searchResults = [[NSMutableArray alloc] initWithCapacity:100];
599     else
600         [_private->searchResults removeAllObjects];
601
602     NSString *query = _private->searchQuery;
603     if (![query length]) {
604         [_private->treeOutlineView reloadData];
605         return;
606     }
607
608     BOOL matched = NO;
609     unsigned count = 0;
610     DOMNode *node = [_private->webFrame DOMDocument];
611     while ((node = [node _traverseNextNodeStayingWithin:nil])) {
612         if ([[node nodeName] _webkit_hasCaseInsensitiveSubstring:query])
613             matched = YES;
614         else if ([node nodeType] == DOM_TEXT_NODE && [[node nodeValue] _webkit_hasCaseInsensitiveSubstring:query])
615             matched = YES;
616         else if ([node isKindOfClass:[DOMHTMLElement class]] && [[(DOMHTMLElement *)node idName] _webkit_hasCaseInsensitiveSubstring:query])
617             matched = YES;
618         else if ([node isKindOfClass:[DOMHTMLElement class]] && [[(DOMHTMLElement *)node className] _webkit_hasCaseInsensitiveSubstring:query])
619             matched = YES;
620         if (matched) {
621             [_private->searchResults addObject:node];
622             count++;
623             matched = NO;
624         }
625     }
626
627     DOMHTMLElement *searchCount = (DOMHTMLElement *)[_private->domDocument getElementById:@"searchCount"];
628     if (count == 1)
629         [searchCount setInnerText:[NSString stringWithFormat:UI_STRING("1 node", "web inspector node search results count with 1 result"), count]];
630     else
631         [searchCount setInnerText:[NSString stringWithFormat:UI_STRING("%u nodes", "web inspector node search results count with zero or more than 1 results"), count]];
632
633     [_private->treeOutlineView reloadData];
634 }
635
636 - (void)_update
637 {
638     if (!_private->webViewLoaded || !_private->focusedNode)
639         return;
640
641     [[_private->webView windowScriptObject] callWebScriptMethod:@"updatePanes" withArguments:nil];
642
643     DOMNode *node = _private->focusedNode;
644     DOMHTMLButtonElement *back = (DOMHTMLButtonElement *)[_private->domDocument getElementById:@"traverseUp"];
645     DOMHTMLButtonElement *forward = (DOMHTMLButtonElement *)[_private->domDocument getElementById:@"traverseDown"];
646     if (_private->searchResultsVisible) {
647         int row = [_private->treeOutlineView selectedRow];
648         if (row != -1) {
649             [forward setDisabled:!((row + 1) < (int)[_private->searchResults count])];
650             [back setDisabled:!((row - 1) >= 0)];
651         }
652     } else {
653         if (!_private->ignoreWhitespace)
654             node = [_private->focusedNode _traverseNextNodeStayingWithin:nil];
655         else
656             node = [_private->focusedNode _traverseNextNodeSkippingWhitespaceStayingWithin:nil];
657         [forward setDisabled:!node];
658
659         if (!_private->ignoreWhitespace)
660             node = [_private->focusedNode _traversePreviousNode];
661         else
662             node = [_private->focusedNode _traversePreviousNodeSkippingWhitespace];
663         [back setDisabled:!node];
664     }
665 }
666
667 - (void)_updateRoot
668 {
669     if (!_private->webViewLoaded || !_private->rootNode || _private->searchResultsVisible)
670         return;
671
672     DOMHTMLElement *titleArea = (DOMHTMLElement *)[_private->domDocument getElementById:@"treePopupTitleArea"];
673     [titleArea setTextContent:[_private->rootNode _displayName]];
674
675     DOMHTMLElement *popup = (DOMHTMLElement *)[_private->domDocument getElementById:@"realTreePopup"];
676     [popup setInnerHTML:@""]; // reset the list
677
678     DOMNode *currentNode = _private->rootNode;
679     while (currentNode) {
680         DOMHTMLOptionElement *option = (DOMHTMLOptionElement *)[_private->domDocument createElement:@"option"];
681         [option setTextContent:[currentNode _displayName]];
682         [popup appendChild:option];
683         currentNode = [currentNode parentNode];
684     }
685
686     _private->preventSelectionRefocus = YES;
687     DOMNode *focusedNode = [_private->focusedNode retain];
688     [_private->treeOutlineView reloadData];
689     [_private->treeOutlineView expandItem:_private->rootNode];
690     [self _revealAndSelectNodeInTree:focusedNode];
691     [focusedNode release];
692     _private->preventSelectionRefocus = NO;
693 }
694
695 - (void)_updateTreeScrollbar
696 {
697     [[_private->webView windowScriptObject] evaluateWebScript:@"treeScrollbar.refresh()"];
698 }
699
700 #pragma mark -
701
702 + (BOOL)isSelectorExcludedFromWebScript:(SEL)aSelector
703 {
704     return NO;
705 }
706
707 + (NSString *)webScriptNameForSelector:(SEL)aSelector {
708     NSMutableString *name = [[NSStringFromSelector(aSelector) mutableCopy] autorelease];
709     [name replaceOccurrencesOfString:@":" withString:@"_" options:NSLiteralSearch range:NSMakeRange(0, [name length])];
710     if ([name hasSuffix:@"_"])
711         return [name substringToIndex:[name length] - 1];
712     return name;
713 }
714
715 + (BOOL)isKeyExcludedFromWebScript:(const char *)name
716 {
717     return NO;
718 }
719
720 #pragma mark -
721
722 - (void)handleEvent:(DOMEvent *)event
723 {
724     if ([[event type] isEqualToString:@"search"] && [[event target] isKindOfClass:[DOMHTMLInputElement class]]) {
725         DOMHTMLInputElement *search = (DOMHTMLInputElement *)[event target];
726         [self setSearchQuery:[search value]];
727     }
728 }
729
730 - (void)webView:(WebView *)sender didFinishLoadForFrame:(WebFrame *)frame
731 {
732     _private->webViewLoaded = YES;
733     [[sender windowScriptObject] setValue:self forKey:@"Inspector"];
734
735     [_private->domDocument release];
736     _private->domDocument = (DOMHTMLDocument *)[[[sender mainFrame] DOMDocument] retain];
737
738     [_private->domDocument addEventListener:@"search" :self :NO];
739
740     if ([[self searchQuery] length]) {
741         DOMHTMLInputElement *search = (DOMHTMLInputElement *)[_private->domDocument getElementById:@"search"];
742         [search setValue:[self searchQuery]];
743         [self _showSearchResults:YES];
744         [self _refreshSearch];
745     }
746
747     [self _update];
748     [self _updateRoot];
749     [[self window] invalidateShadow];
750 }
751
752 - (NSView *)webView:(WebView *)sender plugInViewWithArguments:(NSDictionary *)arguments
753 {
754     NSDictionary *attributes = [arguments objectForKey:@"WebPlugInAttributesKey"];
755     if ([[attributes objectForKey:@"type"] isEqualToString:@"application/x-inspector-tree"]) {
756         if (!_private->treeOutlineView) {
757             _private->nodeCache = [[NSMutableSet alloc] init];
758             _private->treeScrollView = [[NSScrollView alloc] initWithFrame:NSMakeRect(0.0, 0.0, 250.0, 100.0)];
759             [_private->treeScrollView setDrawsBackground:NO];
760             [_private->treeScrollView setBorderType:NSNoBorder];
761             [_private->treeScrollView setVerticalScroller:NO];
762             [_private->treeScrollView setHasHorizontalScroller:NO];
763             [_private->treeScrollView setAutoresizingMask:(NSViewWidthSizable | NSViewHeightSizable)];
764             [_private->treeScrollView setFocusRingType:NSFocusRingTypeNone];
765
766             _private->treeOutlineView = [[WebInspectorOutlineView alloc] initWithFrame:[_private->treeScrollView frame]];
767             [_private->treeOutlineView setHeaderView:nil];
768             [_private->treeOutlineView setAllowsMultipleSelection:NO];
769             [_private->treeOutlineView setAllowsEmptySelection:NO];
770             [_private->treeOutlineView setDelegate:self];
771             [_private->treeOutlineView setDataSource:self];
772             [_private->treeOutlineView sendActionOn:(NSLeftMouseUpMask | NSLeftMouseDownMask | NSLeftMouseDraggedMask)];
773             [_private->treeOutlineView setFocusRingType:NSFocusRingTypeNone];
774             [_private->treeOutlineView setAutoresizesOutlineColumn:NO];
775             [_private->treeOutlineView setRowHeight:15.0];
776             [_private->treeOutlineView setTarget:self];
777             [_private->treeOutlineView setDoubleAction:@selector(_focusRootNode:)];
778             [_private->treeOutlineView setIndentationPerLevel:12.0];
779             [_private->treeScrollView setDocumentView:_private->treeOutlineView];
780
781             NSCell *headerCell = [[NSCell alloc] initTextCell:@""];
782             NSCell *dataCell = [[NSCell alloc] initTextCell:@""];
783             [dataCell setFont:[NSFont systemFontOfSize:11.0]];
784
785             NSTableColumn *tableColumn = [[NSTableColumn alloc] initWithIdentifier:@"node"];
786             [tableColumn setHeaderCell:headerCell];
787             [tableColumn setDataCell:dataCell];
788             [tableColumn setMinWidth:50];
789             [tableColumn setWidth:300];
790             [tableColumn setEditable:NO];
791             [_private->treeOutlineView addTableColumn:tableColumn];
792             [_private->treeOutlineView setOutlineTableColumn:tableColumn];
793
794             [_private->treeOutlineView sizeLastColumnToFit];
795
796             [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_updateTreeScrollbar) name:NSViewFrameDidChangeNotification object:_private->treeOutlineView]; 
797         }
798
799         return [_private->treeOutlineView enclosingScrollView];
800     }
801
802     return nil;
803 }
804
805 #pragma mark -
806
807 - (int)outlineView:(NSOutlineView *)outlineView numberOfChildrenOfItem:(id)item
808 {
809     if (outlineView == _private->treeOutlineView) {
810         if (!item && _private->searchResultsVisible)
811             return [_private->searchResults count];
812         if (!item)
813             return 1;
814         if (!_private->ignoreWhitespace)
815             return [[item childNodes] length];
816         return [item _lengthOfChildNodesIgnoringWhitespace];
817     }
818     return 0;
819 }
820
821 - (BOOL)outlineView:(NSOutlineView *)outlineView isItemExpandable:(id)item
822 {
823     if (outlineView == _private->treeOutlineView) {
824         if (!_private->ignoreWhitespace)
825             return [item hasChildNodes];
826         return ([item _firstChildSkippingWhitespace] ? YES : NO);
827     }
828     return NO;
829 }
830
831 - (id)outlineView:(NSOutlineView *)outlineView child:(int)index ofItem:(id)item
832 {
833     if (outlineView == _private->treeOutlineView) {
834         if (!item && _private->searchResultsVisible)
835             return [_private->searchResults objectAtIndex:index];
836         if (!item)
837             return _private->rootNode;
838         id node = nil;
839         if (!_private->ignoreWhitespace)
840             node = [[item childNodes] item:index];
841         else
842             node = [item _childNodeAtIndexIgnoringWhitespace:index];
843         if (!node)
844             return nil;
845         // cache the node because NSOutlineView assumes we hold on to it
846         // if we don't hold on to the node it will be released before the next time the NSOutlineView needs it
847         [_private->nodeCache addObject:node];
848         return node;
849     }
850     return nil;
851 }
852
853 - (id)outlineView:(NSOutlineView *)outlineView objectValueForTableColumn:(NSTableColumn *)tableColumn byItem:(id)item
854 {
855     if (outlineView == _private->treeOutlineView && item) {
856         NSShadow *shadow = [[NSShadow alloc] init];
857         [shadow setShadowColor:[NSColor blackColor]];
858         [shadow setShadowBlurRadius:2.0];
859         [shadow setShadowOffset:NSMakeSize(2.0,-2.0)];
860         NSMutableParagraphStyle *para = [[NSParagraphStyle defaultParagraphStyle] mutableCopy];
861         [para setLineBreakMode:NSLineBreakByTruncatingTail];
862         
863         NSDictionary *attrs = [[NSDictionary alloc] initWithObjectsAndKeys:[NSColor whiteColor], NSForegroundColorAttributeName, shadow, NSShadowAttributeName, para, NSParagraphStyleAttributeName, nil];
864         NSMutableAttributedString *string = [[NSMutableAttributedString alloc] initWithString:[item _displayName] attributes:attrs];
865         [attrs release];
866
867         if ([item hasChildNodes] && ![outlineView isItemExpanded:item]) {
868             attrs = [[NSDictionary alloc] initWithObjectsAndKeys:[NSColor colorWithCalibratedRed:1.0 green:1.0 blue:1.0 alpha:0.5], NSForegroundColorAttributeName, shadow, NSShadowAttributeName, para, NSParagraphStyleAttributeName, nil];
869             NSAttributedString *preview = [[NSAttributedString alloc] initWithString:[item _contentPreview] attributes:attrs];
870             [string appendAttributedString:preview];
871             [attrs release];
872             [preview release];
873         }
874
875         [para release];
876         [shadow release];
877         return [string autorelease];
878     }
879
880     return nil;
881 }
882
883 - (void)outlineView:(NSOutlineView *)outlineView willDisplayOutlineCell:(id)cell forTableColumn:(NSTableColumn *)tableColumn item:(id)item
884 {
885     if (outlineView == _private->treeOutlineView)
886         [cell setImage:([cell state] ? _private->downArrowImage : _private->rightArrowImage)];
887 }
888
889 - (void)outlineViewItemDidCollapse:(NSNotification *)notification
890 {
891     if ([notification object] == _private->treeOutlineView) {
892         DOMNode *node = [[notification userInfo] objectForKey:@"NSObject"];
893         if (!node)
894             return;
895
896         // remove all child nodes from the node cache when the parent collapses
897         node = [node firstChild];
898         while (node) {
899             NSMapRemove(lengthIgnoringWhitespaceCache, node);
900             NSMapRemove(lastChildIndexIgnoringWhitespaceCache, node);
901             NSMapRemove(lastChildIgnoringWhitespaceCache, node);
902             [_private->nodeCache removeObject:node];
903             node = [node nextSibling];
904         }
905     }
906 }
907
908 - (void)outlineViewSelectionDidChange:(NSNotification *)notification
909 {
910     if ([notification object] == _private->treeOutlineView && !_private->preventSelectionRefocus) {
911         int index = [_private->treeOutlineView selectedRow];
912         if (index == -1)
913             return;
914         [self setFocusedDOMNode:[_private->treeOutlineView itemAtRow:index]];
915         [self _updateTreeScrollbar];
916     }
917 }
918 @end
919
920 #pragma mark -
921
922 @implementation WebInspectorPrivate
923 - (void)dealloc
924 {
925     [webView release];
926     [domDocument release];
927     [webFrame release];
928     [rootNode release];
929     [focusedNode release];
930     [searchQuery release];
931     [nodeCache release];
932     [treeScrollView release];
933     [treeOutlineView release];
934     [matchedRules release];
935     [currentHighlight release];
936     [rightArrowImage release];
937     [downArrowImage release];
938     [super dealloc];
939 }
940 @end
941
942 #pragma mark -
943
944 @implementation DOMHTMLElement (DOMHTMLElementInspectorAdditions)
945 - (void)_addClassName:(NSString *)name
946 {
947     NSString *class = [self className];
948     if ([class rangeOfString:name].location != NSNotFound)
949         return;
950     if ([class length]) {
951         NSString *new = [[NSString alloc] initWithFormat:@"%@ %@", class, name];
952         [self setClassName:new];
953         [new release];
954     } else {
955         [self setClassName:name];
956     }
957 }
958
959 - (void)_removeClassName:(NSString *)name
960 {
961     if ([[self className] isEqualToString:name]) {
962         [self setClassName:@""];
963     } else if ([[self className] rangeOfString:name].location != NSNotFound) {
964         NSMutableString *class = [[self className] mutableCopy];
965         [class replaceOccurrencesOfString:name withString:@"" options:NSLiteralSearch range:NSMakeRange(0, [class length])];
966         [self setClassName:[class _webkit_stringByCollapsingWhitespaceCharacters]];
967         [class release];
968     }
969 }
970 @end
971
972 #pragma mark -
973
974 @implementation DOMNode (DOMNodeInspectorAdditions)
975 - (NSString *)_contentPreview
976 {
977     if (![self hasChildNodes])
978         return @"";
979
980     unsigned limit = 0;
981     NSMutableString *preview = [[NSMutableString alloc] initWithCapacity:100];
982     [preview appendString:@" "];
983
984     // always skip whitespace here
985     DOMNode *currentNode = [self _traverseNextNodeSkippingWhitespaceStayingWithin:self];
986     while (currentNode) {
987         if ([currentNode nodeType] == DOM_TEXT_NODE)
988             [preview appendString:[[currentNode nodeValue] _webkit_stringByCollapsingWhitespaceCharacters]];
989         else
990             [preview appendString:[currentNode _displayName]];
991         currentNode = [currentNode _traverseNextNodeStayingWithin:self];
992         if (++limit > 4) {
993             unichar ellipsis = 0x2026;
994             [preview appendString:[NSString stringWithCharacters:&ellipsis length:1]];
995             break;
996         }
997     }
998
999     return [preview autorelease];
1000 }
1001
1002 #pragma mark -
1003
1004 - (BOOL)_isAncestorOfNode:(DOMNode *)node
1005 {
1006     DOMNode *currentNode = [node parentNode];
1007     while (currentNode) {
1008         if ([self isSameNode:currentNode])
1009             return YES;
1010         currentNode = [currentNode parentNode];
1011     }
1012     return NO;
1013 }
1014
1015 - (BOOL)_isDescendantOfNode:(DOMNode *)node
1016 {
1017     return [node _isAncestorOfNode:self];
1018 }
1019
1020 #pragma mark -
1021
1022 - (BOOL)_isWhitespace
1023 {
1024     static NSCharacterSet *characters = nil;
1025     if (!characters)
1026         characters = [[[NSCharacterSet whitespaceAndNewlineCharacterSet] invertedSet] retain];
1027     NSRange range = [[self nodeValue] rangeOfCharacterFromSet:characters];
1028     return (range.location == NSNotFound);
1029 }
1030
1031 - (unsigned long)_lengthOfChildNodesIgnoringWhitespace
1032 {
1033     if (!lengthIgnoringWhitespaceCache)
1034         lengthIgnoringWhitespaceCache = NSCreateMapTable(NSObjectMapKeyCallBacks, NSIntMapValueCallBacks, 300);
1035
1036     void *lookup = NSMapGet(lengthIgnoringWhitespaceCache, self);
1037     if (lookup)
1038         return (unsigned long)lookup;
1039
1040     unsigned long count = 0;
1041     DOMNode *node = [self _firstChildSkippingWhitespace];
1042     while (node) {
1043         node = [node _nextSiblingSkippingWhitespace];
1044         count++;
1045     }
1046
1047     NSMapInsert(lengthIgnoringWhitespaceCache, self, (void *)count);
1048     return count;
1049 }
1050
1051 - (DOMNode *)_childNodeAtIndexIgnoringWhitespace:(unsigned long)nodeIndex
1052 {
1053     unsigned long count = 0;
1054     DOMNode *node = nil;
1055
1056     if (!lastChildIndexIgnoringWhitespaceCache)
1057         lastChildIndexIgnoringWhitespaceCache = NSCreateMapTable(NSObjectMapKeyCallBacks, NSIntMapValueCallBacks, 300);
1058     if (!lastChildIgnoringWhitespaceCache)
1059         lastChildIgnoringWhitespaceCache = NSCreateMapTable(NSObjectMapKeyCallBacks, NSObjectMapValueCallBacks, 300);
1060
1061     void *cachedLastIndex = NSMapGet(lastChildIndexIgnoringWhitespaceCache, self);
1062     if (cachedLastIndex) {
1063         DOMNode *lastChild = (DOMNode *)NSMapGet(lastChildIgnoringWhitespaceCache, self);
1064         if (lastChild) {
1065             unsigned long cachedIndex = (unsigned long)cachedLastIndex;
1066             if (nodeIndex == cachedIndex) {
1067                 return lastChild;
1068             } else if (nodeIndex > cachedIndex) {
1069                 node = lastChild;
1070                 count = cachedIndex;
1071             }
1072         }
1073     }
1074
1075     if (!node)
1076         node = [self _firstChildSkippingWhitespace];
1077
1078     while (node && count < nodeIndex) {
1079         node = [node _nextSiblingSkippingWhitespace];
1080         count++;
1081     }
1082
1083     if (node) {
1084         NSMapInsert(lastChildIndexIgnoringWhitespaceCache, self, (void *)count);
1085         NSMapInsert(lastChildIgnoringWhitespaceCache, self, node);
1086     } else {
1087         NSMapRemove(lastChildIndexIgnoringWhitespaceCache, self);
1088         NSMapRemove(lastChildIgnoringWhitespaceCache, self);
1089     }
1090
1091     return node;
1092 }
1093
1094 #pragma mark -
1095
1096 - (DOMNode *)_nextSiblingSkippingWhitespace
1097 {
1098     DOMNode *node = [self nextSibling];
1099     while ([node nodeType] == DOM_TEXT_NODE && [node _isWhitespace])
1100         node = [node nextSibling];
1101     return node;
1102 }
1103
1104 - (DOMNode *)_previousSiblingSkippingWhitespace
1105 {
1106     DOMNode *node = [self previousSibling];
1107     while ([node nodeType] == DOM_TEXT_NODE && [node _isWhitespace])
1108         node = [node previousSibling];
1109     return node;
1110 }
1111
1112 - (DOMNode *)_firstChildSkippingWhitespace
1113 {
1114     DOMNode *node = [self firstChild];
1115     while ([node nodeType] == DOM_TEXT_NODE && [node _isWhitespace])
1116         node = [node _nextSiblingSkippingWhitespace];
1117     return node;
1118 }
1119
1120 - (DOMNode *)_lastChildSkippingWhitespace
1121 {
1122     DOMNode *node = [self lastChild];
1123     while ([node nodeType] == DOM_TEXT_NODE && [node _isWhitespace])
1124         node = [node _previousSiblingSkippingWhitespace];
1125     return node;
1126 }
1127
1128 #pragma mark -
1129
1130 - (DOMNode *)_firstAncestorCommonWithNode:(DOMNode *)node
1131 {
1132     if ([[self parentNode] isSameNode:[node parentNode]])
1133         return [self parentNode];
1134
1135     NSMutableArray *ancestorsOne = [[[NSMutableArray alloc] init] autorelease];
1136     NSMutableArray *ancestorsTwo = [[[NSMutableArray alloc] init] autorelease];
1137
1138     DOMNode *currentNode = [self parentNode];
1139     while (currentNode) {
1140         [ancestorsOne addObject:currentNode];
1141         currentNode = [currentNode parentNode];
1142     }
1143
1144     currentNode = [node parentNode];
1145     while (currentNode) {
1146         [ancestorsTwo addObject:currentNode];
1147         currentNode = [currentNode parentNode];
1148     }
1149
1150     return [ancestorsOne firstObjectCommonWithArray:ancestorsTwo];
1151 }
1152
1153 #pragma mark -
1154
1155 - (DOMNode *)_traverseNextNodeStayingWithin:(DOMNode *)stayWithin
1156 {
1157     DOMNode *node = [self firstChild];
1158     if (node)
1159         return node;
1160
1161     if ([self isSameNode:stayWithin])
1162         return 0;
1163
1164     node = [self nextSibling];
1165     if (node)
1166         return node;
1167
1168     node = self;
1169     while (node && ![node nextSibling] && (!stayWithin || ![[node parentNode] isSameNode:stayWithin]))
1170         node = [node parentNode];
1171
1172     return [node nextSibling];
1173 }
1174
1175 - (DOMNode *)_traverseNextNodeSkippingWhitespaceStayingWithin:(DOMNode *)stayWithin
1176 {
1177     DOMNode *node = [self _firstChildSkippingWhitespace];
1178     if (node)
1179         return node;
1180
1181     if ([self isSameNode:stayWithin])
1182         return 0;
1183
1184     node = [self _nextSiblingSkippingWhitespace];
1185     if (node)
1186         return node;
1187
1188     node = self;
1189     while (node && ![node _nextSiblingSkippingWhitespace] && (!stayWithin || ![[node parentNode] isSameNode:stayWithin]))
1190         node = [node parentNode];
1191
1192     return [node _nextSiblingSkippingWhitespace];
1193 }
1194
1195 - (DOMNode *)_traversePreviousNode
1196 {
1197     DOMNode *node = [self previousSibling];
1198     while ([node lastChild])
1199         node = [node lastChild];
1200     if (node)
1201         return node;
1202     return [self parentNode];
1203 }
1204
1205 - (DOMNode *)_traversePreviousNodeSkippingWhitespace
1206 {
1207     DOMNode *node = [self _previousSiblingSkippingWhitespace];
1208     while ([node _lastChildSkippingWhitespace])
1209         node = [node _lastChildSkippingWhitespace];
1210     if (node)
1211         return node;
1212     return [self parentNode];
1213 }
1214
1215 #pragma mark -
1216
1217 - (NSString *)_nodeTypeName
1218 {
1219     switch([self nodeType]) {
1220         case DOM_ELEMENT_NODE: return UI_STRING("Element", "element node type name");
1221         case DOM_ATTRIBUTE_NODE: return UI_STRING("Attribute", "attribute node type name");
1222         case DOM_TEXT_NODE: return UI_STRING("Text", "text node type name");
1223         case DOM_CDATA_SECTION_NODE: return UI_STRING("Character Data", "character data node type name");
1224         case DOM_ENTITY_REFERENCE_NODE: return UI_STRING("Entity Reference", "entity reference node type name");
1225         case DOM_ENTITY_NODE: return UI_STRING("Entity", "entity node type name");
1226         case DOM_PROCESSING_INSTRUCTION_NODE: return UI_STRING("Processing Instruction", "processing instruction node type name");
1227         case DOM_COMMENT_NODE: return UI_STRING("Comment", "comment node type name");
1228         case DOM_DOCUMENT_NODE: return UI_STRING_KEY("Document", "Document (node type)", "document node type name");
1229         case DOM_DOCUMENT_TYPE_NODE: return UI_STRING("Document Type", "document type node type name");
1230         case DOM_DOCUMENT_FRAGMENT_NODE: return UI_STRING("Document Fragment", "document fragment node type name");
1231         case DOM_NOTATION_NODE: return UI_STRING("Notation", "notation node type name");
1232     }
1233     return nil;
1234 }
1235
1236 - (NSString *)_shortDisplayName
1237 {
1238     if ([self nodeType] == DOM_TEXT_NODE)
1239         return UI_STRING("Text", "text node type name");
1240     return [self _displayName];
1241 }
1242
1243 - (NSString *)_displayName
1244 {
1245     switch([self nodeType]) {
1246         case DOM_DOCUMENT_NODE:
1247             return UI_STRING_KEY("Document", "Document (node)", "document node name");
1248         case DOM_ELEMENT_NODE: {
1249             if ([self hasAttributes]) {
1250                 NSMutableString *name = [NSMutableString stringWithFormat:@"<%@", [[self nodeName] lowercaseString]];
1251                 NSString *value = [(DOMElement *)self getAttribute:@"id"];
1252                 if ([value length])
1253                     [name appendFormat:@" id=\"%@\"", value];
1254                 value = [(DOMElement *)self getAttribute:@"class"];
1255                 if ([value length])
1256                     [name appendFormat:@" class=\"%@\"", value];
1257                 if ([[self nodeName] caseInsensitiveCompare:@"a"] == NSOrderedSame) {
1258                     value = [(DOMElement *)self getAttribute:@"name"];
1259                     if ([value length])
1260                         [name appendFormat:@" name=\"%@\"", value];
1261                     value = [(DOMElement *)self getAttribute:@"href"];
1262                     if ([value length])
1263                         [name appendFormat:@" href=\"%@\"", value];
1264                 } else if ([[self nodeName] caseInsensitiveCompare:@"img"] == NSOrderedSame) {
1265                     value = [(DOMElement *)self getAttribute:@"src"];
1266                     if ([value length])
1267                         [name appendFormat:@" src=\"%@\"", value];
1268                 } else if ([[self nodeName] caseInsensitiveCompare:@"iframe"] == NSOrderedSame) {
1269                     value = [(DOMElement *)self getAttribute:@"src"];
1270                     if ([value length])
1271                         [name appendFormat:@" src=\"%@\"", value];
1272                 } else if ([[self nodeName] caseInsensitiveCompare:@"input"] == NSOrderedSame) {
1273                     value = [(DOMElement *)self getAttribute:@"name"];
1274                     if ([value length])
1275                         [name appendFormat:@" name=\"%@\"", value];
1276                     value = [(DOMElement *)self getAttribute:@"type"];
1277                     if ([value length])
1278                         [name appendFormat:@" type=\"%@\"", value];
1279                 } else if ([[self nodeName] caseInsensitiveCompare:@"form"] == NSOrderedSame) {
1280                     value = [(DOMElement *)self getAttribute:@"action"];
1281                     if ([value length])
1282                         [name appendFormat:@" action=\"%@\"", value];
1283                 }
1284                 [name appendString:@">"];
1285                 return name;
1286             }
1287             return [NSString stringWithFormat:@"<%@>", [[self nodeName] lowercaseString]];
1288         }
1289         case DOM_TEXT_NODE: {
1290             if ([self _isWhitespace])
1291                 return UI_STRING("(whitespace)", "whitespace text node name");
1292             NSString *value = [[self nodeValue] _webkit_stringByCollapsingWhitespaceCharacters];
1293             CFStringTrimWhitespace((CFMutableStringRef)value);
1294             return [NSString stringWithFormat:UI_STRING("\"%@\"", "text node name with text included"), value];
1295         }
1296     }
1297     return [[self nodeName] lowercaseString];
1298 }
1299 @end