| /* |
| * Copyright (C) 2006, 2007 Apple Inc. All rights reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions |
| * are met: |
| * |
| * 1. Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * 2. Redistributions in binary form must reproduce the above copyright |
| * notice, this list of conditions and the following disclaimer in the |
| * documentation and/or other materials provided with the distribution. |
| * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of |
| * its contributors may be used to endorse or promote products derived |
| * from this software without specific prior written permission. |
| * |
| * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY |
| * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED |
| * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE |
| * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY |
| * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES |
| * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; |
| * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND |
| * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF |
| * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| */ |
| |
| #import "WebInspectorClient.h" |
| |
| #import "WebFrameInternal.h" |
| #import "WebFrameView.h" |
| #import "WebLocalizableStrings.h" |
| #import "WebNodeHighlight.h" |
| #import "WebPreferences.h" |
| #import "WebTypesInternal.h" |
| #import "WebView.h" |
| #import "WebViewInternal.h" |
| #import "WebViewPrivate.h" |
| |
| #import <AppKit/NSWindowController.h> |
| |
| #import <WebCore/InspectorController.h> |
| #import <WebCore/Page.h> |
| |
| #import <WebKit/DOMCore.h> |
| #import <WebKit/DOMExtensions.h> |
| |
| #import <WebKitSystemInterface.h> |
| |
| using namespace WebCore; |
| |
| @interface WebInspectorWindowController : NSWindowController { |
| @private |
| WebView *_inspectedWebView; |
| WebView *_webView; |
| NSImageView *_shadowView; |
| WebNodeHighlight *_currentHighlight; |
| BOOL _attachedToInspectedWebView; |
| BOOL _shouldAttach; |
| BOOL _visible; |
| BOOL _movingWindows; |
| } |
| - (id)initWithInspectedWebView:(WebView *)webView; |
| - (BOOL)inspectorVisible; |
| - (WebView *)webView; |
| - (void)attach; |
| - (void)detach; |
| - (void)highlightAndScrollToNode:(DOMNode *)node; |
| - (void)highlightNode:(DOMNode *)node; |
| - (void)hideHighlight; |
| @end |
| |
| #pragma mark - |
| |
| WebInspectorClient::WebInspectorClient(WebView *webView) |
| : m_webView(webView) |
| { |
| } |
| |
| void WebInspectorClient::inspectorDestroyed() |
| { |
| [[m_windowController.get() webView] close]; |
| delete this; |
| } |
| |
| Page* WebInspectorClient::createPage() |
| { |
| if (!m_windowController) |
| m_windowController.adoptNS([[WebInspectorWindowController alloc] initWithInspectedWebView:m_webView]); |
| |
| return core([m_windowController.get() webView]); |
| } |
| |
| String WebInspectorClient::localizedStringsURL() |
| { |
| NSString *path = [[NSBundle bundleWithIdentifier:@"com.apple.WebCore"] pathForResource:@"InspectorLocalizedStrings" ofType:@"js"]; |
| if (path) |
| return [[NSURL fileURLWithPath:path] absoluteString]; |
| return String(); |
| } |
| |
| void WebInspectorClient::showWindow() |
| { |
| updateWindowTitle(); |
| [m_windowController.get() showWindow:nil]; |
| } |
| |
| void WebInspectorClient::closeWindow() |
| { |
| [m_windowController.get() close]; |
| } |
| |
| void WebInspectorClient::attachWindow() |
| { |
| [m_windowController.get() attach]; |
| } |
| |
| void WebInspectorClient::detachWindow() |
| { |
| [m_windowController.get() detach]; |
| } |
| |
| void WebInspectorClient::highlight(Node* node) |
| { |
| [m_windowController.get() highlightAndScrollToNode:kit(node)]; |
| } |
| |
| void WebInspectorClient::hideHighlight() |
| { |
| [m_windowController.get() hideHighlight]; |
| } |
| |
| void WebInspectorClient::inspectedURLChanged(const String& newURL) |
| { |
| m_inspectedURL = newURL; |
| updateWindowTitle(); |
| } |
| |
| void WebInspectorClient::updateWindowTitle() const |
| { |
| NSString *title = [NSString stringWithFormat:UI_STRING("Web Inspector — %@", "Web Inspector window title"), (NSString *)m_inspectedURL]; |
| [[m_windowController.get() window] setTitle:title]; |
| } |
| |
| #pragma mark - |
| |
| #define WebKitInspectorAttachedViewHeightKey @"WebKitInspectorAttachedViewHeight" |
| |
| @implementation WebInspectorWindowController |
| - (id)init |
| { |
| if (![super initWithWindow:nil]) |
| return nil; |
| |
| // Keep preferences separate from the rest of the client, making sure we are using expected preference values. |
| // One reason this is good is that it keeps the inspector out of history via "private browsing". |
| |
| WebPreferences *preferences = [[WebPreferences alloc] init]; |
| [preferences setAutosaves:NO]; |
| [preferences setPrivateBrowsingEnabled:YES]; |
| [preferences setLoadsImagesAutomatically:YES]; |
| [preferences setAuthorAndUserStylesEnabled:YES]; |
| [preferences setJavaScriptEnabled:YES]; |
| [preferences setAllowsAnimatedImages:YES]; |
| [preferences setLoadsImagesAutomatically:YES]; |
| [preferences setPlugInsEnabled:NO]; |
| [preferences setJavaEnabled:NO]; |
| [preferences setUserStyleSheetEnabled:NO]; |
| [preferences setTabsToLinks:NO]; |
| [preferences setMinimumFontSize:0]; |
| [preferences setMinimumLogicalFontSize:9]; |
| |
| _webView = [[WebView alloc] init]; |
| [_webView setPreferences:preferences]; |
| [_webView setDrawsBackground:NO]; |
| [_webView setProhibitsMainFrameScrolling:YES]; |
| |
| [preferences release]; |
| |
| NSString *path = [[NSBundle bundleWithIdentifier:@"com.apple.WebCore"] pathForResource:@"inspector" ofType:@"html" inDirectory:@"inspector"]; |
| NSURLRequest *request = [[NSURLRequest alloc] initWithURL:[NSURL fileURLWithPath:path]]; |
| [[_webView mainFrame] loadRequest:request]; |
| [request release]; |
| |
| [self setWindowFrameAutosaveName:@"Web Inspector 2"]; |
| return self; |
| } |
| |
| - (id)initWithInspectedWebView:(WebView *)webView |
| { |
| if (![self init]) |
| return nil; |
| |
| // Don't retain to avoid a circular reference |
| _inspectedWebView = webView; |
| return self; |
| } |
| |
| - (void)dealloc |
| { |
| [_shadowView release]; |
| [_webView release]; |
| [super dealloc]; |
| } |
| |
| #pragma mark - |
| |
| - (BOOL)inspectorVisible |
| { |
| return _visible; |
| } |
| |
| - (WebView *)webView |
| { |
| return _webView; |
| } |
| |
| - (NSWindow *)window |
| { |
| NSWindow *window = [super window]; |
| if (window) |
| return window; |
| |
| NSUInteger styleMask = (NSTitledWindowMask | NSClosableWindowMask | NSMiniaturizableWindowMask | NSResizableWindowMask); |
| |
| #ifndef BUILDING_ON_TIGER |
| styleMask |= NSTexturedBackgroundWindowMask; |
| #endif |
| |
| window = [[NSWindow alloc] initWithContentRect:NSMakeRect(60.0, 200.0, 750.0, 650.0) styleMask:styleMask backing:NSBackingStoreBuffered defer:YES]; |
| [window setDelegate:self]; |
| [window setMinSize:NSMakeSize(400.0, 400.0)]; |
| |
| #ifndef BUILDING_ON_TIGER |
| [window setAutorecalculatesContentBorderThickness:NO forEdge:NSMaxYEdge]; |
| [window setContentBorderThickness:40. forEdge:NSMaxYEdge]; |
| |
| WKNSWindowMakeBottomCornersSquare(window); |
| #endif |
| |
| [self setWindow:window]; |
| [window release]; |
| |
| return window; |
| } |
| |
| #pragma mark - |
| |
| - (BOOL)windowShouldClose:(id)sender |
| { |
| _visible = NO; |
| |
| [_inspectedWebView page]->inspectorController()->setWindowVisible(false); |
| |
| [_currentHighlight detachHighlight]; |
| [_currentHighlight setDelegate:nil]; |
| [_currentHighlight release]; |
| _currentHighlight = nil; |
| |
| return YES; |
| } |
| |
| - (void)close |
| { |
| if (!_visible) |
| return; |
| |
| _visible = NO; |
| |
| [_inspectedWebView page]->inspectorController()->setWindowVisible(false); |
| |
| if (!_movingWindows) { |
| [_currentHighlight detachHighlight]; |
| [_currentHighlight setDelegate:nil]; |
| [_currentHighlight release]; |
| _currentHighlight = nil; |
| } |
| |
| if (_attachedToInspectedWebView) { |
| if ([_inspectedWebView _isClosed]) |
| return; |
| |
| WebFrameView *frameView = [[_inspectedWebView mainFrame] frameView]; |
| |
| NSRect frameViewRect = [frameView frame]; |
| NSRect finalFrameViewRect = NSMakeRect(0, 0, NSWidth(frameViewRect), NSHeight([_inspectedWebView frame])); |
| NSMutableDictionary *frameViewAnimationInfo = [[NSMutableDictionary alloc] init]; |
| [frameViewAnimationInfo setObject:frameView forKey:NSViewAnimationTargetKey]; |
| [frameViewAnimationInfo setObject:[NSValue valueWithRect:finalFrameViewRect] forKey:NSViewAnimationEndFrameKey]; |
| |
| ASSERT(_shadowView); |
| NSRect shadowFrame = [_shadowView frame]; |
| shadowFrame = NSMakeRect(0, NSMinY(frameViewRect) - NSHeight(shadowFrame), NSWidth(frameViewRect), NSHeight(shadowFrame)); |
| [_shadowView setFrame:shadowFrame]; |
| |
| [_shadowView removeFromSuperview]; |
| [_inspectedWebView addSubview:_shadowView positioned:NSWindowAbove relativeTo:_webView]; |
| |
| NSRect finalShadowRect = NSMakeRect(0, -NSHeight(shadowFrame), NSWidth(shadowFrame), NSHeight(shadowFrame)); |
| NSMutableDictionary *shadowAnimationInfo = [[NSMutableDictionary alloc] init]; |
| [shadowAnimationInfo setObject:_shadowView forKey:NSViewAnimationTargetKey]; |
| [shadowAnimationInfo setObject:[NSValue valueWithRect:finalShadowRect] forKey:NSViewAnimationEndFrameKey]; |
| |
| NSArray *animationInfo = [[NSArray alloc] initWithObjects:frameViewAnimationInfo, shadowAnimationInfo, nil]; |
| [frameViewAnimationInfo release]; |
| [shadowAnimationInfo release]; |
| |
| NSViewAnimation *slideAnimation = [[NSViewAnimation alloc] initWithViewAnimations:animationInfo]; // released in animationDidEnd |
| [animationInfo release]; |
| |
| [slideAnimation setAnimationBlockingMode:NSAnimationBlocking]; |
| [slideAnimation setDelegate:self]; |
| |
| [[_inspectedWebView window] display]; // display once to make sure we start in a good state |
| [slideAnimation startAnimation]; |
| } else { |
| [super close]; |
| } |
| } |
| |
| - (IBAction)showWindow:(id)sender |
| { |
| if (_visible) { |
| if (!_attachedToInspectedWebView) |
| [super showWindow:sender]; // call super so the window will be ordered front if needed |
| return; |
| } |
| |
| _visible = YES; |
| |
| [_inspectedWebView page]->inspectorController()->setWindowVisible(true); |
| |
| if (_shouldAttach) { |
| WebFrameView *frameView = [[_inspectedWebView mainFrame] frameView]; |
| |
| NSRect frameViewRect = [frameView frame]; |
| float attachedHeight = [[NSUserDefaults standardUserDefaults] integerForKey:WebKitInspectorAttachedViewHeightKey]; |
| attachedHeight = MAX(300.0, MIN(attachedHeight, (NSHeight(frameViewRect) * 0.6))); |
| |
| [_webView removeFromSuperview]; |
| [_inspectedWebView addSubview:_webView positioned:NSWindowBelow relativeTo:(NSView*)frameView]; |
| [_webView setFrame:NSMakeRect(0, 0, NSWidth(frameViewRect), attachedHeight)]; |
| [_webView setAutoresizingMask:(NSViewWidthSizable | NSViewHeightSizable | NSViewMaxYMargin)]; |
| |
| [frameView setAutoresizingMask:(NSViewWidthSizable | NSViewHeightSizable | NSViewMinYMargin)]; |
| |
| NSRect finalFrameViewRect = NSMakeRect(0, attachedHeight, NSWidth(frameViewRect), NSHeight(frameViewRect) - attachedHeight); |
| NSMutableDictionary *frameViewAnimationInfo = [[NSMutableDictionary alloc] init]; |
| [frameViewAnimationInfo setObject:frameView forKey:NSViewAnimationTargetKey]; |
| [frameViewAnimationInfo setObject:[NSValue valueWithRect:finalFrameViewRect] forKey:NSViewAnimationEndFrameKey]; |
| |
| if (!_shadowView) { |
| NSString *imagePath = [[NSBundle bundleWithIdentifier:@"com.apple.WebCore"] pathForResource:@"attachedShadow" ofType:@"png" inDirectory:@"inspector/Images"]; |
| NSImage *image = [[NSImage alloc] initWithContentsOfFile:imagePath]; |
| _shadowView = [[NSImageView alloc] initWithFrame:NSMakeRect(0, -[image size].height, NSWidth(frameViewRect), [image size].height)]; |
| [_shadowView setImage:image]; |
| [_shadowView setImageScaling:NSScaleToFit]; |
| [_shadowView setImageFrameStyle:NSImageFrameNone]; |
| [_shadowView setAutoresizingMask:(NSViewWidthSizable | NSViewHeightSizable | NSViewMaxYMargin)]; |
| } |
| |
| NSRect shadowFrame = [_shadowView frame]; |
| shadowFrame = NSMakeRect(0, -NSHeight(shadowFrame), NSWidth(frameViewRect), NSHeight(shadowFrame)); |
| [_shadowView setFrame:shadowFrame]; |
| |
| [_shadowView removeFromSuperview]; |
| [_inspectedWebView addSubview:_shadowView positioned:NSWindowAbove relativeTo:_webView]; |
| |
| NSRect finalShadowRect = NSMakeRect(0, attachedHeight - NSHeight(shadowFrame), NSWidth(shadowFrame), NSHeight(shadowFrame)); |
| NSMutableDictionary *shadowAnimationInfo = [[NSMutableDictionary alloc] init]; |
| [shadowAnimationInfo setObject:_shadowView forKey:NSViewAnimationTargetKey]; |
| [shadowAnimationInfo setObject:[NSValue valueWithRect:finalShadowRect] forKey:NSViewAnimationEndFrameKey]; |
| |
| NSArray *animationInfo = [[NSArray alloc] initWithObjects:frameViewAnimationInfo, shadowAnimationInfo, nil]; |
| [frameViewAnimationInfo release]; |
| [shadowAnimationInfo release]; |
| |
| NSViewAnimation *slideAnimation = [[NSViewAnimation alloc] initWithViewAnimations:animationInfo]; // released in animationDidEnd |
| [animationInfo release]; |
| |
| [slideAnimation setAnimationBlockingMode:NSAnimationBlocking]; |
| [slideAnimation setDelegate:self]; |
| |
| _attachedToInspectedWebView = YES; |
| |
| [[_inspectedWebView window] display]; // display once to make sure we start in a good state |
| [slideAnimation startAnimation]; |
| } else { |
| _attachedToInspectedWebView = NO; |
| |
| NSView *contentView = [[self window] contentView]; |
| [_webView setFrame:[contentView frame]]; |
| [_webView setAutoresizingMask:(NSViewWidthSizable | NSViewHeightSizable)]; |
| [_webView removeFromSuperview]; |
| [contentView addSubview:_webView]; |
| |
| [super showWindow:nil]; |
| } |
| } |
| |
| #pragma mark - |
| |
| - (void)attach |
| { |
| if (_attachedToInspectedWebView) |
| return; |
| |
| _shouldAttach = YES; |
| |
| if (_visible) { |
| _movingWindows = YES; |
| [self close]; |
| _movingWindows = NO; |
| } |
| |
| [self showWindow:nil]; |
| } |
| |
| - (void)detach |
| { |
| if (!_attachedToInspectedWebView) |
| return; |
| |
| _shouldAttach = NO; |
| |
| if (_visible) { |
| _movingWindows = YES; // set back to NO in animationDidEnd |
| [self close]; |
| } else { |
| [self showWindow:nil]; |
| } |
| } |
| |
| #pragma mark - |
| |
| - (void)highlightAndScrollToNode:(DOMNode *)node |
| { |
| NSRect bounds = [node boundingBox]; |
| if (!NSIsEmptyRect(bounds)) { |
| // FIXME: this needs to use the frame the node coordinates are in |
| NSRect visible = [[[[_inspectedWebView mainFrame] frameView] documentView] visibleRect]; |
| BOOL needsScroll = !NSContainsRect(visible, bounds) && !NSContainsRect(bounds, visible); |
| |
| // only scroll if the bounds isn't in the visible rect and dosen't contain the visible rect |
| if (needsScroll) { |
| // scroll to the parent element if we aren't focused on an element |
| DOMElement *element; |
| if ([node isKindOfClass:[DOMElement class]]) |
| element = (DOMElement *)node; |
| else |
| element = (DOMElement *)[node parentNode]; |
| [element scrollIntoViewIfNeeded:YES]; |
| |
| // give time for the scroll to happen |
| [self performSelector:@selector(highlightNode:) withObject:node afterDelay:0.25]; |
| } else |
| [self highlightNode:node]; |
| } |
| } |
| |
| - (void)highlightNode:(DOMNode *)node |
| { |
| // The scrollview's content view stays around between page navigations, so target it |
| NSView *view = [[[[[_inspectedWebView mainFrame] frameView] documentView] enclosingScrollView] contentView]; |
| if (![view window]) |
| return; // skip the highlight if we have no window (e.g. hidden tab) |
| |
| if (!_currentHighlight) { |
| _currentHighlight = [[WebNodeHighlight alloc] initWithTargetView:view]; |
| [_currentHighlight setDelegate:self]; |
| [_currentHighlight attachHighlight]; |
| } |
| |
| [_currentHighlight show]; |
| |
| [_currentHighlight setHighlightedNode:node]; |
| |
| // FIXME: this is a hack until we hook up a didDraw and didScroll call in WebHTMLView |
| [[_currentHighlight highlightView] setNeedsDisplay:YES]; |
| } |
| |
| - (void)hideHighlight |
| { |
| if (!_currentHighlight) |
| return; |
| [_currentHighlight hide]; |
| [_currentHighlight setHighlightedNode:nil]; |
| } |
| |
| #pragma mark - |
| |
| - (void)animationDidEnd:(NSAnimation*)animation |
| { |
| [animation release]; |
| |
| [_shadowView removeFromSuperview]; |
| |
| if (_movingWindows) { |
| _movingWindows = NO; |
| [self showWindow:nil]; |
| } |
| } |
| |
| |
| #pragma mark - |
| |
| // These methods can be used by UI elements such as menu items and toolbar buttons when the inspector is the key window. |
| |
| // This method is really only implemented to keep any UI elements enabled. |
| - (void)showWebInspector:(id)sender |
| { |
| [_inspectedWebView page]->inspectorController()->show(); |
| } |
| |
| - (void)showErrorConsole:(id)sender |
| { |
| [_inspectedWebView page]->inspectorController()->showConsole(); |
| } |
| |
| - (void)showNetworkTimeline:(id)sender |
| { |
| [_inspectedWebView page]->inspectorController()->showTimeline(); |
| } |
| |
| @end |