WebCore:
[WebKit-https.git] / WebKit / mac / WebCoreSupport / WebInspectorClient.mm
1 /*
2  * Copyright (C) 2006, 2007 Apple 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 "WebInspectorClient.h"
30
31 #import "WebFrameInternal.h"
32 #import "WebFrameView.h"
33 #import "WebLocalizableStrings.h"
34 #import "WebNodeHighlight.h"
35 #import "WebPreferences.h"
36 #import "WebTypesInternal.h"
37 #import "WebView.h"
38 #import "WebViewInternal.h"
39 #import "WebViewPrivate.h"
40
41 #import <AppKit/NSWindowController.h>
42
43 #import <WebCore/InspectorController.h>
44 #import <WebCore/Page.h>
45
46 #import <WebKit/DOMCore.h>
47 #import <WebKit/DOMExtensions.h>
48
49 #import <WebKitSystemInterface.h>
50
51 using namespace WebCore;
52
53 @interface WebInspectorWindowController : NSWindowController {
54 @private
55     WebView *_inspectedWebView;
56     WebView *_webView;
57     NSImageView *_shadowView;
58     WebNodeHighlight *_currentHighlight;
59     BOOL _attachedToInspectedWebView;
60     BOOL _shouldAttach;
61     BOOL _visible;
62     BOOL _movingWindows;
63 }
64 - (id)initWithInspectedWebView:(WebView *)webView;
65 - (BOOL)inspectorVisible;
66 - (WebView *)webView;
67 - (void)attach;
68 - (void)detach;
69 - (void)highlightAndScrollToNode:(DOMNode *)node;
70 - (void)highlightNode:(DOMNode *)node;
71 - (void)hideHighlight;
72 @end
73
74 #pragma mark -
75
76 WebInspectorClient::WebInspectorClient(WebView *webView)
77 : m_webView(webView)
78 {
79 }
80
81 void WebInspectorClient::inspectorDestroyed()
82 {
83     [[m_windowController.get() webView] close];
84     delete this;
85 }
86
87 Page* WebInspectorClient::createPage()
88 {
89     if (!m_windowController)
90         m_windowController.adoptNS([[WebInspectorWindowController alloc] initWithInspectedWebView:m_webView]);
91
92     return core([m_windowController.get() webView]);
93 }
94
95 String WebInspectorClient::localizedStringsURL()
96 {
97     NSString *path = [[NSBundle bundleWithIdentifier:@"com.apple.WebCore"] pathForResource:@"InspectorLocalizedStrings" ofType:@"js"];
98     if (path)
99         return [[NSURL fileURLWithPath:path] absoluteString];
100     return String();
101 }
102
103 void WebInspectorClient::showWindow()
104 {
105     updateWindowTitle();
106     [m_windowController.get() showWindow:nil];
107 }
108
109 void WebInspectorClient::closeWindow()
110 {
111     [m_windowController.get() close];
112 }
113
114 void WebInspectorClient::attachWindow()
115 {
116     [m_windowController.get() attach];
117 }
118
119 void WebInspectorClient::detachWindow()
120 {
121     [m_windowController.get() detach];
122 }
123
124 void WebInspectorClient::highlight(Node* node)
125 {
126     [m_windowController.get() highlightAndScrollToNode:kit(node)];
127 }
128
129 void WebInspectorClient::hideHighlight()
130 {
131     [m_windowController.get() hideHighlight];
132 }
133
134 void WebInspectorClient::inspectedURLChanged(const String& newURL)
135 {
136     m_inspectedURL = newURL;
137     updateWindowTitle();
138 }
139
140 void WebInspectorClient::updateWindowTitle() const
141 {
142     NSString *title = [NSString stringWithFormat:UI_STRING("Web Inspector — %@", "Web Inspector window title"), (NSString *)m_inspectedURL];
143     [[m_windowController.get() window] setTitle:title];
144 }
145
146 #pragma mark -
147
148 #define WebKitInspectorAttachedViewHeightKey @"WebKitInspectorAttachedViewHeight"
149
150 @implementation WebInspectorWindowController
151 - (id)init
152 {
153     if (![super initWithWindow:nil])
154         return nil;
155
156     // Keep preferences separate from the rest of the client, making sure we are using expected preference values.
157     // One reason this is good is that it keeps the inspector out of history via "private browsing".
158
159     WebPreferences *preferences = [[WebPreferences alloc] init];
160     [preferences setAutosaves:NO];
161     [preferences setPrivateBrowsingEnabled:YES];
162     [preferences setLoadsImagesAutomatically:YES];
163     [preferences setJavaScriptEnabled:YES];
164     [preferences setAllowsAnimatedImages:YES];
165     [preferences setLoadsImagesAutomatically:YES];
166     [preferences setPlugInsEnabled:NO];
167     [preferences setJavaEnabled:NO];
168     [preferences setUserStyleSheetEnabled:NO];
169     [preferences setTabsToLinks:NO];
170     [preferences setMinimumFontSize:0];
171     [preferences setMinimumLogicalFontSize:9];
172
173     _webView = [[WebView alloc] init];
174     [_webView setPreferences:preferences];
175     [_webView setDrawsBackground:NO];
176     [_webView setProhibitsMainFrameScrolling:YES];
177
178     [preferences release];
179
180     NSString *path = [[NSBundle bundleWithIdentifier:@"com.apple.WebCore"] pathForResource:@"inspector" ofType:@"html" inDirectory:@"inspector"];
181     NSURLRequest *request = [[NSURLRequest alloc] initWithURL:[NSURL fileURLWithPath:path]];
182     [[_webView mainFrame] loadRequest:request];
183     [request release];
184
185     [self setWindowFrameAutosaveName:@"Web Inspector 2"];
186     return self;
187 }
188
189 - (id)initWithInspectedWebView:(WebView *)webView
190 {
191     if (![self init])
192         return nil;
193
194     // Don't retain to avoid a circular reference
195     _inspectedWebView = webView;
196     return self;
197 }
198
199 - (void)dealloc
200 {
201     [_shadowView release];
202     [_webView release];
203     [super dealloc];
204 }
205
206 #pragma mark -
207
208 - (BOOL)inspectorVisible
209 {
210     return _visible;
211 }
212
213 - (WebView *)webView
214 {
215     return _webView;
216 }
217
218 - (NSWindow *)window
219 {
220     NSWindow *window = [super window];
221     if (window)
222         return window;
223
224     NSUInteger styleMask = (NSTitledWindowMask | NSClosableWindowMask | NSMiniaturizableWindowMask | NSResizableWindowMask);
225
226 #ifndef BUILDING_ON_TIGER
227     styleMask |= NSTexturedBackgroundWindowMask;
228 #endif
229
230     window = [[NSWindow alloc] initWithContentRect:NSMakeRect(60.0, 200.0, 750.0, 650.0) styleMask:styleMask backing:NSBackingStoreBuffered defer:YES];
231     [window setDelegate:self];
232     [window setMinSize:NSMakeSize(400.0, 400.0)];
233
234 #ifndef BUILDING_ON_TIGER
235     [window setAutorecalculatesContentBorderThickness:NO forEdge:NSMaxYEdge];
236     [window setContentBorderThickness:40. forEdge:NSMaxYEdge];
237
238     WKNSWindowMakeBottomCornersSquare(window);
239 #endif
240
241     [self setWindow:window];
242     [window release];
243
244     return window;
245 }
246
247 #pragma mark -
248
249 - (BOOL)windowShouldClose:(id)sender
250 {
251     _visible = NO;
252
253     [_inspectedWebView page]->inspectorController()->setWindowVisible(false);
254
255     [_currentHighlight detachHighlight];
256     [_currentHighlight setDelegate:nil];
257     [_currentHighlight release];
258     _currentHighlight = nil;
259
260     return YES;
261 }
262
263 - (void)close
264 {
265     if (!_visible)
266         return;
267
268     _visible = NO;
269
270     [_inspectedWebView page]->inspectorController()->setWindowVisible(false);
271
272     if (!_movingWindows) {
273         [_currentHighlight detachHighlight];
274         [_currentHighlight setDelegate:nil];
275         [_currentHighlight release];
276         _currentHighlight = nil;
277     }
278
279     if (_attachedToInspectedWebView) {
280         if ([_inspectedWebView _isClosed])
281             return;
282
283         WebFrameView *frameView = [[_inspectedWebView mainFrame] frameView];
284
285         NSRect frameViewRect = [frameView frame];
286         NSRect finalFrameViewRect = NSMakeRect(0, 0, NSWidth(frameViewRect), NSHeight([_inspectedWebView frame]));
287         NSMutableDictionary *frameViewAnimationInfo = [[NSMutableDictionary alloc] init];
288         [frameViewAnimationInfo setObject:frameView forKey:NSViewAnimationTargetKey];
289         [frameViewAnimationInfo setObject:[NSValue valueWithRect:finalFrameViewRect] forKey:NSViewAnimationEndFrameKey];
290
291         ASSERT(_shadowView);
292         NSRect shadowFrame = [_shadowView frame];
293         shadowFrame = NSMakeRect(0, NSMinY(frameViewRect) - NSHeight(shadowFrame), NSWidth(frameViewRect), NSHeight(shadowFrame));
294         [_shadowView setFrame:shadowFrame];
295
296         [_shadowView removeFromSuperview];
297         [_inspectedWebView addSubview:_shadowView positioned:NSWindowAbove relativeTo:_webView];
298
299         NSRect finalShadowRect = NSMakeRect(0, -NSHeight(shadowFrame), NSWidth(shadowFrame), NSHeight(shadowFrame));
300         NSMutableDictionary *shadowAnimationInfo = [[NSMutableDictionary alloc] init];
301         [shadowAnimationInfo setObject:_shadowView forKey:NSViewAnimationTargetKey];
302         [shadowAnimationInfo setObject:[NSValue valueWithRect:finalShadowRect] forKey:NSViewAnimationEndFrameKey];
303
304         NSArray *animationInfo = [[NSArray alloc] initWithObjects:frameViewAnimationInfo, shadowAnimationInfo, nil];
305         [frameViewAnimationInfo release];
306         [shadowAnimationInfo release];
307
308         NSViewAnimation *slideAnimation = [[NSViewAnimation alloc] initWithViewAnimations:animationInfo]; // released in animationDidEnd
309         [animationInfo release];
310
311         [slideAnimation setAnimationBlockingMode:NSAnimationBlocking];
312         [slideAnimation setDelegate:self];
313
314         [[_inspectedWebView window] display]; // display once to make sure we start in a good state
315         [slideAnimation startAnimation];
316     } else {
317         [super close];
318     }
319 }
320
321 - (IBAction)showWindow:(id)sender
322 {
323     if (_visible) {
324         if (!_attachedToInspectedWebView)
325             [super showWindow:sender]; // call super so the window will be ordered front if needed
326         return;
327     }
328
329     _visible = YES;
330
331     [_inspectedWebView page]->inspectorController()->setWindowVisible(true);
332
333     if (_shouldAttach) {
334         WebFrameView *frameView = [[_inspectedWebView mainFrame] frameView];
335
336         NSRect frameViewRect = [frameView frame];
337         float attachedHeight = [[NSUserDefaults standardUserDefaults] integerForKey:WebKitInspectorAttachedViewHeightKey];
338         attachedHeight = MAX(300.0, MIN(attachedHeight, (NSHeight(frameViewRect) * 0.6)));
339
340         [_webView removeFromSuperview];
341         [_inspectedWebView addSubview:_webView positioned:NSWindowBelow relativeTo:(NSView*)frameView];
342         [_webView setFrame:NSMakeRect(0, 0, NSWidth(frameViewRect), attachedHeight)];
343         [_webView setAutoresizingMask:(NSViewWidthSizable | NSViewHeightSizable | NSViewMaxYMargin)];
344
345         [frameView setAutoresizingMask:(NSViewWidthSizable | NSViewHeightSizable | NSViewMinYMargin)];
346
347         NSRect finalFrameViewRect = NSMakeRect(0, attachedHeight, NSWidth(frameViewRect), NSHeight(frameViewRect) - attachedHeight);
348         NSMutableDictionary *frameViewAnimationInfo = [[NSMutableDictionary alloc] init];
349         [frameViewAnimationInfo setObject:frameView forKey:NSViewAnimationTargetKey];
350         [frameViewAnimationInfo setObject:[NSValue valueWithRect:finalFrameViewRect] forKey:NSViewAnimationEndFrameKey];
351
352         if (!_shadowView) {
353             NSString *imagePath = [[NSBundle bundleWithIdentifier:@"com.apple.WebCore"] pathForResource:@"attachedShadow" ofType:@"png" inDirectory:@"inspector/Images"];
354             NSImage *image = [[NSImage alloc] initWithContentsOfFile:imagePath];
355             _shadowView = [[NSImageView alloc] initWithFrame:NSMakeRect(0, -[image size].height, NSWidth(frameViewRect), [image size].height)];
356             [_shadowView setImage:image];
357             [_shadowView setImageScaling:NSScaleToFit];
358             [_shadowView setImageFrameStyle:NSImageFrameNone];
359             [_shadowView setAutoresizingMask:(NSViewWidthSizable | NSViewHeightSizable | NSViewMaxYMargin)];
360         }
361
362         NSRect shadowFrame = [_shadowView frame];
363         shadowFrame = NSMakeRect(0, -NSHeight(shadowFrame), NSWidth(frameViewRect), NSHeight(shadowFrame));
364         [_shadowView setFrame:shadowFrame];
365
366         [_shadowView removeFromSuperview];
367         [_inspectedWebView addSubview:_shadowView positioned:NSWindowAbove relativeTo:_webView];
368
369         NSRect finalShadowRect = NSMakeRect(0, attachedHeight - NSHeight(shadowFrame), NSWidth(shadowFrame), NSHeight(shadowFrame));
370         NSMutableDictionary *shadowAnimationInfo = [[NSMutableDictionary alloc] init];
371         [shadowAnimationInfo setObject:_shadowView forKey:NSViewAnimationTargetKey];
372         [shadowAnimationInfo setObject:[NSValue valueWithRect:finalShadowRect] forKey:NSViewAnimationEndFrameKey];
373
374         NSArray *animationInfo = [[NSArray alloc] initWithObjects:frameViewAnimationInfo, shadowAnimationInfo, nil];
375         [frameViewAnimationInfo release];
376         [shadowAnimationInfo release];
377
378         NSViewAnimation *slideAnimation = [[NSViewAnimation alloc] initWithViewAnimations:animationInfo]; // released in animationDidEnd
379         [animationInfo release];
380
381         [slideAnimation setAnimationBlockingMode:NSAnimationBlocking];
382         [slideAnimation setDelegate:self];
383
384         _attachedToInspectedWebView = YES;
385
386         [[_inspectedWebView window] display]; // display once to make sure we start in a good state
387         [slideAnimation startAnimation];
388     } else {
389         _attachedToInspectedWebView = NO;
390
391         NSView *contentView = [[self window] contentView];
392         [_webView setFrame:[contentView frame]];
393         [_webView setAutoresizingMask:(NSViewWidthSizable | NSViewHeightSizable)];
394         [_webView removeFromSuperview];
395         [contentView addSubview:_webView];
396
397         [super showWindow:nil];
398     }
399 }
400
401 #pragma mark -
402
403 - (void)attach
404 {
405     if (_attachedToInspectedWebView)
406         return;
407
408     _shouldAttach = YES;
409
410     if (_visible) {
411         _movingWindows = YES;
412         [self close];
413         _movingWindows = NO;
414     }
415
416     [self showWindow:nil];
417 }
418
419 - (void)detach
420 {
421     if (!_attachedToInspectedWebView)
422         return;
423
424     _shouldAttach = NO;
425
426     if (_visible) {
427         _movingWindows = YES; // set back to NO in animationDidEnd
428         [self close];
429     } else {
430         [self showWindow:nil];
431     }
432 }
433
434 #pragma mark -
435
436 - (void)highlightAndScrollToNode:(DOMNode *)node
437 {
438     NSRect bounds = [node boundingBox];
439     if (!NSIsEmptyRect(bounds)) {
440         // FIXME: this needs to use the frame the node coordinates are in
441         NSRect visible = [[[[_inspectedWebView mainFrame] frameView] documentView] visibleRect];
442         BOOL needsScroll = !NSContainsRect(visible, bounds) && !NSContainsRect(bounds, visible);
443
444         // only scroll if the bounds isn't in the visible rect and dosen't contain the visible rect
445         if (needsScroll) {
446             // scroll to the parent element if we aren't focused on an element
447             DOMElement *element;
448             if ([node isKindOfClass:[DOMElement class]])
449                 element = (DOMElement *)node;
450             else
451                 element = (DOMElement *)[node parentNode];
452             [element scrollIntoViewIfNeeded:YES];
453
454             // give time for the scroll to happen
455             [self performSelector:@selector(highlightNode:) withObject:node afterDelay:0.25];
456         } else
457             [self highlightNode:node];
458     }
459 }
460
461 - (void)highlightNode:(DOMNode *)node
462 {
463     // The scrollview's content view stays around between page navigations, so target it
464     NSView *view = [[[[[_inspectedWebView mainFrame] frameView] documentView] enclosingScrollView] contentView];
465     if (![view window])
466         return; // skip the highlight if we have no window (e.g. hidden tab)
467
468     if (!_currentHighlight) {
469         _currentHighlight = [[WebNodeHighlight alloc] initWithTargetView:view];
470         [_currentHighlight setDelegate:self];
471         [_currentHighlight attachHighlight];
472     }
473
474     [_currentHighlight show];
475
476     [_currentHighlight setHighlightedNode:node];
477
478     // FIXME: this is a hack until we hook up a didDraw and didScroll call in WebHTMLView
479     [[_currentHighlight highlightView] setNeedsDisplay:YES];
480 }
481
482 - (void)hideHighlight
483 {
484     if (!_currentHighlight)
485         return;
486     [_currentHighlight hide];
487     [_currentHighlight setHighlightedNode:nil];
488 }
489
490 #pragma mark -
491
492 - (void)animationDidEnd:(NSAnimation*)animation
493 {
494     [animation release];
495
496     [_shadowView removeFromSuperview];
497
498     if (_movingWindows) {
499         _movingWindows = NO;
500         [self showWindow:nil];
501     }
502 }
503
504
505 #pragma mark -
506
507 // These methods can be used by UI elements such as menu items and toolbar buttons when the inspector is the key window.
508
509 // This method is really only implemented to keep any UI elements enabled.
510 - (void)showWebInspector:(id)sender
511 {
512     [_inspectedWebView page]->inspectorController()->show();
513 }
514
515 - (void)showErrorConsole:(id)sender
516 {
517     [_inspectedWebView page]->inspectorController()->showConsole();
518 }
519
520 - (void)showNetworkTimeline:(id)sender
521 {
522     [_inspectedWebView page]->inspectorController()->showTimeline();
523 }
524
525 @end