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