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