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