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