blob: 453cd1adf3a605f3fc4fd226a8619a518ee2b23c [file] [log] [blame]
/*
* 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