Home | History | Annotate | Download | only in WebCoreSupport
      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 
     45 using namespace WebCore;
     46 
     47 static 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 
     71 WebInspectorClient::WebInspectorClient(WebView *webView)
     72 : m_webView(webView)
     73 {
     74 }
     75 
     76 void WebInspectorClient::inspectorDestroyed()
     77 {
     78     [[m_windowController.get() webView] close];
     79     delete this;
     80 }
     81 
     82 Page* 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 
     91 String 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 
     99 String WebInspectorClient::hiddenPanels()
    100 {
    101     NSString *hiddenPanels = [[NSUserDefaults standardUserDefaults] stringForKey:@"WebKitInspectorHiddenPanels"];
    102     if (hiddenPanels)
    103         return hiddenPanels;
    104     return String();
    105 }
    106 
    107 void WebInspectorClient::showWindow()
    108 {
    109     updateWindowTitle();
    110     [m_windowController.get() showWindow:nil];
    111 }
    112 
    113 void WebInspectorClient::closeWindow()
    114 {
    115     [m_windowController.get() close];
    116 }
    117 
    118 void WebInspectorClient::attachWindow()
    119 {
    120     [m_windowController.get() attach];
    121 }
    122 
    123 void WebInspectorClient::detachWindow()
    124 {
    125     [m_windowController.get() detach];
    126 }
    127 
    128 void WebInspectorClient::setAttachedWindowHeight(unsigned height)
    129 {
    130     [m_windowController.get() setAttachedWindowHeight:height];
    131 }
    132 
    133 void WebInspectorClient::highlight(Node* node)
    134 {
    135     [m_windowController.get() highlightNode:kit(node)];
    136 }
    137 
    138 void WebInspectorClient::hideHighlight()
    139 {
    140     [m_windowController.get() hideHighlight];
    141 }
    142 
    143 void WebInspectorClient::inspectedURLChanged(const String& newURL)
    144 {
    145     m_inspectedURL = newURL;
    146     updateWindowTitle();
    147 }
    148 
    149 void 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 
    155 void 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