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