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