1/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2// vim:set ts=2 sts=2 sw=2 et cin: 3// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. 4// 5// Redistribution and use in source and binary forms, with or without 6// modification, are permitted provided that the following conditions are 7// met: 8// 9// * Redistributions of source code must retain the above copyright 10// notice, this list of conditions and the following disclaimer. 11// * Redistributions in binary form must reproduce the above 12// copyright notice, this list of conditions and the following disclaimer 13// in the documentation and/or other materials provided with the 14// distribution. 15// * Neither the name of Google Inc. nor the names of its 16// contributors may be used to endorse or promote products derived from 17// this software without specific prior written permission. 18// 19// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 20// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 21// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 22// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 23// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 24// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 25// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 26// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 27// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 28// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 31#include "base/basictypes.h" 32#include "nsCocoaUtils.h" 33#include "PluginModuleChild.h" 34#include "nsDebug.h" 35#include "PluginInterposeOSX.h" 36#include <set> 37#import <AppKit/AppKit.h> 38#import <objc/runtime.h> 39#import <Carbon/Carbon.h> 40 41using namespace mozilla::plugins; 42 43namespace mac_plugin_interposing { 44 45int32_t NSCursorInfo::mNativeCursorsSupported = -1; 46 47// This constructor may be called from the browser process or the plugin 48// process. 49NSCursorInfo::NSCursorInfo() 50 : mType(TypeArrow) 51 , mHotSpot(nsPoint(0, 0)) 52 , mCustomImageData(NULL) 53 , mCustomImageDataLength(0) 54{ 55} 56 57NSCursorInfo::NSCursorInfo(NSCursor* aCursor) 58 : mType(TypeArrow) 59 , mHotSpot(nsPoint(0, 0)) 60 , mCustomImageData(NULL) 61 , mCustomImageDataLength(0) 62{ 63 // This constructor is only ever called from the plugin process, so the 64 // following is safe. 65 if (!GetNativeCursorsSupported()) { 66 return; 67 } 68 69 NSPoint hotSpotCocoa = [aCursor hotSpot]; 70 mHotSpot = nsPoint(hotSpotCocoa.x, hotSpotCocoa.y); 71 72 Class nsCursorClass = [NSCursor class]; 73 if ([aCursor isEqual:[NSCursor arrowCursor]]) { 74 mType = TypeArrow; 75 } else if ([aCursor isEqual:[NSCursor closedHandCursor]]) { 76 mType = TypeClosedHand; 77 } else if ([aCursor isEqual:[NSCursor crosshairCursor]]) { 78 mType = TypeCrosshair; 79 } else if ([aCursor isEqual:[NSCursor disappearingItemCursor]]) { 80 mType = TypeDisappearingItem; 81 } else if ([aCursor isEqual:[NSCursor IBeamCursor]]) { 82 mType = TypeIBeam; 83 } else if ([aCursor isEqual:[NSCursor openHandCursor]]) { 84 mType = TypeOpenHand; 85 } else if ([aCursor isEqual:[NSCursor pointingHandCursor]]) { 86 mType = TypePointingHand; 87 } else if ([aCursor isEqual:[NSCursor resizeDownCursor]]) { 88 mType = TypeResizeDown; 89 } else if ([aCursor isEqual:[NSCursor resizeLeftCursor]]) { 90 mType = TypeResizeLeft; 91 } else if ([aCursor isEqual:[NSCursor resizeLeftRightCursor]]) { 92 mType = TypeResizeLeftRight; 93 } else if ([aCursor isEqual:[NSCursor resizeRightCursor]]) { 94 mType = TypeResizeRight; 95 } else if ([aCursor isEqual:[NSCursor resizeUpCursor]]) { 96 mType = TypeResizeUp; 97 } else if ([aCursor isEqual:[NSCursor resizeUpDownCursor]]) { 98 mType = TypeResizeUpDown; 99 // The following cursor types are only supported on OS X 10.6 and up. 100 } else if ([nsCursorClass respondsToSelector:@selector(contextualMenuCursor)] && 101 [aCursor isEqual:[nsCursorClass performSelector:@selector(contextualMenuCursor)]]) { 102 mType = TypeContextualMenu; 103 } else if ([nsCursorClass respondsToSelector:@selector(dragCopyCursor)] && 104 [aCursor isEqual:[nsCursorClass performSelector:@selector(dragCopyCursor)]]) { 105 mType = TypeDragCopy; 106 } else if ([nsCursorClass respondsToSelector:@selector(dragLinkCursor)] && 107 [aCursor isEqual:[nsCursorClass performSelector:@selector(dragLinkCursor)]]) { 108 mType = TypeDragLink; 109 } else if ([nsCursorClass respondsToSelector:@selector(operationNotAllowedCursor)] && 110 [aCursor isEqual:[nsCursorClass performSelector:@selector(operationNotAllowedCursor)]]) { 111 mType = TypeNotAllowed; 112 } else { 113 NSImage* image = [aCursor image]; 114 NSArray* reps = image ? [image representations] : nil; 115 NSUInteger repsCount = reps ? [reps count] : 0; 116 if (!repsCount) { 117 // If we have a custom cursor with no image representations, assume we 118 // need a transparent cursor. 119 mType = TypeTransparent; 120 } else { 121 CGImageRef cgImage = nil; 122 // XXX We don't know how to deal with a cursor that doesn't have a 123 // bitmap image representation. For now we fall back to an arrow 124 // cursor. 125 for (NSUInteger i = 0; i < repsCount; ++i) { 126 id rep = [reps objectAtIndex:i]; 127 if ([rep isKindOfClass:[NSBitmapImageRep class]]) { 128 cgImage = [(NSBitmapImageRep*)rep CGImage]; 129 break; 130 } 131 } 132 if (cgImage) { 133 CFMutableDataRef data = ::CFDataCreateMutable(kCFAllocatorDefault, 0); 134 if (data) { 135 CGImageDestinationRef dest = ::CGImageDestinationCreateWithData(data, 136 kUTTypePNG, 137 1, 138 NULL); 139 if (dest) { 140 ::CGImageDestinationAddImage(dest, cgImage, NULL); 141 if (::CGImageDestinationFinalize(dest)) { 142 uint32_t dataLength = (uint32_t) ::CFDataGetLength(data); 143 mCustomImageData = (uint8_t*) moz_xmalloc(dataLength); 144 ::CFDataGetBytes(data, ::CFRangeMake(0, dataLength), mCustomImageData); 145 mCustomImageDataLength = dataLength; 146 mType = TypeCustom; 147 } 148 ::CFRelease(dest); 149 } 150 ::CFRelease(data); 151 } 152 } 153 if (!mCustomImageData) { 154 mType = TypeArrow; 155 } 156 } 157 } 158} 159 160NSCursorInfo::NSCursorInfo(const Cursor* aCursor) 161 : mType(TypeArrow) 162 , mHotSpot(nsPoint(0, 0)) 163 , mCustomImageData(NULL) 164 , mCustomImageDataLength(0) 165{ 166 // This constructor is only ever called from the plugin process, so the 167 // following is safe. 168 if (!GetNativeCursorsSupported()) { 169 return; 170 } 171 172 mHotSpot = nsPoint(aCursor->hotSpot.h, aCursor->hotSpot.v); 173 174 int width = 16, height = 16; 175 int bytesPerPixel = 4; 176 int rowBytes = width * bytesPerPixel; 177 int bitmapSize = height * rowBytes; 178 179 bool isTransparent = true; 180 181 uint8_t* bitmap = (uint8_t*) moz_xmalloc(bitmapSize); 182 // The way we create 'bitmap' is largely "borrowed" from Chrome's 183 // WebCursor::InitFromCursor(). 184 for (int y = 0; y < height; ++y) { 185 unsigned short data = aCursor->data[y]; 186 unsigned short mask = aCursor->mask[y]; 187 // Change 'data' and 'mask' from big-endian to little-endian, but output 188 // big-endian data below. 189 data = ((data << 8) & 0xFF00) | ((data >> 8) & 0x00FF); 190 mask = ((mask << 8) & 0xFF00) | ((mask >> 8) & 0x00FF); 191 // It'd be nice to use a gray-scale bitmap. But 192 // CGBitmapContextCreateImage() (used below) won't work with one that also 193 // has alpha values. 194 for (int x = 0; x < width; ++x) { 195 int offset = (y * rowBytes) + (x * bytesPerPixel); 196 // Color value 197 if (data & 0x8000) { 198 bitmap[offset] = 0x0; 199 bitmap[offset + 1] = 0x0; 200 bitmap[offset + 2] = 0x0; 201 } else { 202 bitmap[offset] = 0xFF; 203 bitmap[offset + 1] = 0xFF; 204 bitmap[offset + 2] = 0xFF; 205 } 206 // Mask value 207 if (mask & 0x8000) { 208 bitmap[offset + 3] = 0xFF; 209 isTransparent = false; 210 } else { 211 bitmap[offset + 3] = 0x0; 212 } 213 data <<= 1; 214 mask <<= 1; 215 } 216 } 217 218 if (isTransparent) { 219 // If aCursor is transparent, we don't need to serialize custom cursor 220 // data over IPC. 221 mType = TypeTransparent; 222 } else { 223 CGColorSpaceRef color = ::CGColorSpaceCreateDeviceRGB(); 224 if (color) { 225 CGContextRef context = 226 ::CGBitmapContextCreate(bitmap, 227 width, 228 height, 229 8, 230 rowBytes, 231 color, 232 kCGImageAlphaPremultipliedLast | 233 kCGBitmapByteOrder32Big); 234 if (context) { 235 CGImageRef image = ::CGBitmapContextCreateImage(context); 236 if (image) { 237 ::CFMutableDataRef data = ::CFDataCreateMutable(kCFAllocatorDefault, 0); 238 if (data) { 239 CGImageDestinationRef dest = 240 ::CGImageDestinationCreateWithData(data, 241 kUTTypePNG, 242 1, 243 NULL); 244 if (dest) { 245 ::CGImageDestinationAddImage(dest, image, NULL); 246 if (::CGImageDestinationFinalize(dest)) { 247 uint32_t dataLength = (uint32_t) ::CFDataGetLength(data); 248 mCustomImageData = (uint8_t*) moz_xmalloc(dataLength); 249 ::CFDataGetBytes(data, 250 ::CFRangeMake(0, dataLength), 251 mCustomImageData); 252 mCustomImageDataLength = dataLength; 253 mType = TypeCustom; 254 } 255 ::CFRelease(dest); 256 } 257 ::CFRelease(data); 258 } 259 ::CGImageRelease(image); 260 } 261 ::CGContextRelease(context); 262 } 263 ::CGColorSpaceRelease(color); 264 } 265 } 266 267 free(bitmap); 268} 269 270NSCursorInfo::~NSCursorInfo() 271{ 272 if (mCustomImageData) { 273 free(mCustomImageData); 274 } 275} 276 277NSCursor* NSCursorInfo::GetNSCursor() const 278{ 279 NSCursor* retval = nil; 280 281 Class nsCursorClass = [NSCursor class]; 282 switch(mType) { 283 case TypeArrow: 284 retval = [NSCursor arrowCursor]; 285 break; 286 case TypeClosedHand: 287 retval = [NSCursor closedHandCursor]; 288 break; 289 case TypeCrosshair: 290 retval = [NSCursor crosshairCursor]; 291 break; 292 case TypeDisappearingItem: 293 retval = [NSCursor disappearingItemCursor]; 294 break; 295 case TypeIBeam: 296 retval = [NSCursor IBeamCursor]; 297 break; 298 case TypeOpenHand: 299 retval = [NSCursor openHandCursor]; 300 break; 301 case TypePointingHand: 302 retval = [NSCursor pointingHandCursor]; 303 break; 304 case TypeResizeDown: 305 retval = [NSCursor resizeDownCursor]; 306 break; 307 case TypeResizeLeft: 308 retval = [NSCursor resizeLeftCursor]; 309 break; 310 case TypeResizeLeftRight: 311 retval = [NSCursor resizeLeftRightCursor]; 312 break; 313 case TypeResizeRight: 314 retval = [NSCursor resizeRightCursor]; 315 break; 316 case TypeResizeUp: 317 retval = [NSCursor resizeUpCursor]; 318 break; 319 case TypeResizeUpDown: 320 retval = [NSCursor resizeUpDownCursor]; 321 break; 322 // The following four cursor types are only supported on OS X 10.6 and up. 323 case TypeContextualMenu: { 324 if ([nsCursorClass respondsToSelector:@selector(contextualMenuCursor)]) { 325 retval = [nsCursorClass performSelector:@selector(contextualMenuCursor)]; 326 } 327 break; 328 } 329 case TypeDragCopy: { 330 if ([nsCursorClass respondsToSelector:@selector(dragCopyCursor)]) { 331 retval = [nsCursorClass performSelector:@selector(dragCopyCursor)]; 332 } 333 break; 334 } 335 case TypeDragLink: { 336 if ([nsCursorClass respondsToSelector:@selector(dragLinkCursor)]) { 337 retval = [nsCursorClass performSelector:@selector(dragLinkCursor)]; 338 } 339 break; 340 } 341 case TypeNotAllowed: { 342 if ([nsCursorClass respondsToSelector:@selector(operationNotAllowedCursor)]) { 343 retval = [nsCursorClass performSelector:@selector(operationNotAllowedCursor)]; 344 } 345 break; 346 } 347 case TypeTransparent: 348 retval = GetTransparentCursor(); 349 break; 350 default: 351 break; 352 } 353 354 if (!retval && mCustomImageData && mCustomImageDataLength) { 355 CGDataProviderRef provider = ::CGDataProviderCreateWithData(NULL, 356 (const void*)mCustomImageData, 357 mCustomImageDataLength, 358 NULL); 359 if (provider) { 360 CGImageRef cgImage = ::CGImageCreateWithPNGDataProvider(provider, 361 NULL, 362 false, 363 kCGRenderingIntentDefault); 364 if (cgImage) { 365 NSBitmapImageRep* rep = [[NSBitmapImageRep alloc] initWithCGImage:cgImage]; 366 if (rep) { 367 NSImage* image = [[NSImage alloc] init]; 368 if (image) { 369 [image addRepresentation:rep]; 370 retval = [[[NSCursor alloc] initWithImage:image 371 hotSpot:NSMakePoint(mHotSpot.x, mHotSpot.y)] 372 autorelease]; 373 [image release]; 374 } 375 [rep release]; 376 } 377 ::CGImageRelease(cgImage); 378 } 379 ::CFRelease(provider); 380 } 381 } 382 383 // Fall back to an arrow cursor if need be. 384 if (!retval) { 385 retval = [NSCursor arrowCursor]; 386 } 387 388 return retval; 389} 390 391// Get a transparent cursor with the appropriate hot spot. We need one if 392// (for example) we have a custom cursor with no image data. 393NSCursor* NSCursorInfo::GetTransparentCursor() const 394{ 395 NSCursor* retval = nil; 396 397 int width = 16, height = 16; 398 int bytesPerPixel = 2; 399 int rowBytes = width * bytesPerPixel; 400 int dataSize = height * rowBytes; 401 402 uint8_t* data = (uint8_t*) moz_xmalloc(dataSize); 403 for (int y = 0; y < height; ++y) { 404 for (int x = 0; x < width; ++x) { 405 int offset = (y * rowBytes) + (x * bytesPerPixel); 406 data[offset] = 0x7E; // Arbitrary gray-scale value 407 data[offset + 1] = 0; // Alpha value to make us transparent 408 } 409 } 410 411 NSBitmapImageRep* imageRep = 412 [[[NSBitmapImageRep alloc] initWithBitmapDataPlanes:nil 413 pixelsWide:width 414 pixelsHigh:height 415 bitsPerSample:8 416 samplesPerPixel:2 417 hasAlpha:YES 418 isPlanar:NO 419 colorSpaceName:NSCalibratedWhiteColorSpace 420 bytesPerRow:rowBytes 421 bitsPerPixel:16] 422 autorelease]; 423 if (imageRep) { 424 uint8_t* repDataPtr = [imageRep bitmapData]; 425 if (repDataPtr) { 426 memcpy(repDataPtr, data, dataSize); 427 NSImage *image = 428 [[[NSImage alloc] initWithSize:NSMakeSize(width, height)] 429 autorelease]; 430 if (image) { 431 [image addRepresentation:imageRep]; 432 retval = 433 [[[NSCursor alloc] initWithImage:image 434 hotSpot:NSMakePoint(mHotSpot.x, mHotSpot.y)] 435 autorelease]; 436 } 437 } 438 } 439 440 free(data); 441 442 // Fall back to an arrow cursor if (for some reason) the above code failed. 443 if (!retval) { 444 retval = [NSCursor arrowCursor]; 445 } 446 447 return retval; 448} 449 450NSCursorInfo::Type NSCursorInfo::GetType() const 451{ 452 return mType; 453} 454 455const char* NSCursorInfo::GetTypeName() const 456{ 457 switch(mType) { 458 case TypeCustom: 459 return "TypeCustom"; 460 case TypeArrow: 461 return "TypeArrow"; 462 case TypeClosedHand: 463 return "TypeClosedHand"; 464 case TypeContextualMenu: 465 return "TypeContextualMenu"; 466 case TypeCrosshair: 467 return "TypeCrosshair"; 468 case TypeDisappearingItem: 469 return "TypeDisappearingItem"; 470 case TypeDragCopy: 471 return "TypeDragCopy"; 472 case TypeDragLink: 473 return "TypeDragLink"; 474 case TypeIBeam: 475 return "TypeIBeam"; 476 case TypeNotAllowed: 477 return "TypeNotAllowed"; 478 case TypeOpenHand: 479 return "TypeOpenHand"; 480 case TypePointingHand: 481 return "TypePointingHand"; 482 case TypeResizeDown: 483 return "TypeResizeDown"; 484 case TypeResizeLeft: 485 return "TypeResizeLeft"; 486 case TypeResizeLeftRight: 487 return "TypeResizeLeftRight"; 488 case TypeResizeRight: 489 return "TypeResizeRight"; 490 case TypeResizeUp: 491 return "TypeResizeUp"; 492 case TypeResizeUpDown: 493 return "TypeResizeUpDown"; 494 case TypeTransparent: 495 return "TypeTransparent"; 496 default: 497 break; 498 } 499 return "TypeUnknown"; 500} 501 502nsPoint NSCursorInfo::GetHotSpot() const 503{ 504 return mHotSpot; 505} 506 507uint8_t* NSCursorInfo::GetCustomImageData() const 508{ 509 return mCustomImageData; 510} 511 512uint32_t NSCursorInfo::GetCustomImageDataLength() const 513{ 514 return mCustomImageDataLength; 515} 516 517void NSCursorInfo::SetType(Type aType) 518{ 519 mType = aType; 520} 521 522void NSCursorInfo::SetHotSpot(nsPoint aHotSpot) 523{ 524 mHotSpot = aHotSpot; 525} 526 527void NSCursorInfo::SetCustomImageData(uint8_t* aData, uint32_t aDataLength) 528{ 529 if (mCustomImageData) { 530 free(mCustomImageData); 531 } 532 if (aDataLength) { 533 mCustomImageData = (uint8_t*) moz_xmalloc(aDataLength); 534 memcpy(mCustomImageData, aData, aDataLength); 535 } else { 536 mCustomImageData = NULL; 537 } 538 mCustomImageDataLength = aDataLength; 539} 540 541// This should never be called from the browser process -- only from the 542// plugin process. 543bool NSCursorInfo::GetNativeCursorsSupported() 544{ 545 if (mNativeCursorsSupported == -1) { 546 ENSURE_PLUGIN_THREAD(false); 547 PluginModuleChild *pmc = PluginModuleChild::GetChrome(); 548 if (pmc) { 549 bool result = pmc->GetNativeCursorsSupported(); 550 if (result) { 551 mNativeCursorsSupported = 1; 552 } else { 553 mNativeCursorsSupported = 0; 554 } 555 } 556 } 557 return (mNativeCursorsSupported == 1); 558} 559 560} // namespace mac_plugin_interposing 561 562namespace mac_plugin_interposing { 563namespace parent { 564 565// Tracks plugin windows currently visible. 566std::set<uint32_t> plugin_visible_windows_set_; 567// Tracks full screen windows currently visible. 568std::set<uint32_t> plugin_fullscreen_windows_set_; 569// Tracks modal windows currently visible. 570std::set<uint32_t> plugin_modal_windows_set_; 571 572void OnPluginShowWindow(uint32_t window_id, 573 CGRect window_bounds, 574 bool modal) { 575 plugin_visible_windows_set_.insert(window_id); 576 577 if (modal) 578 plugin_modal_windows_set_.insert(window_id); 579 580 CGRect main_display_bounds = ::CGDisplayBounds(CGMainDisplayID()); 581 582 if (CGRectEqualToRect(window_bounds, main_display_bounds) && 583 (plugin_fullscreen_windows_set_.find(window_id) == 584 plugin_fullscreen_windows_set_.end())) { 585 plugin_fullscreen_windows_set_.insert(window_id); 586 587 nsCocoaUtils::HideOSChromeOnScreen(true); 588 } 589} 590 591static void ActivateProcess(pid_t pid) { 592 ProcessSerialNumber process; 593 OSStatus status = ::GetProcessForPID(pid, &process); 594 595 if (status == noErr) { 596 SetFrontProcess(&process); 597 } else { 598 NS_WARNING("Unable to get process for pid."); 599 } 600} 601 602// Must be called on the UI thread. 603// If plugin_pid is -1, the browser will be the active process on return, 604// otherwise that process will be given focus back before this function returns. 605static void ReleasePluginFullScreen(pid_t plugin_pid) { 606 // Releasing full screen only works if we are the frontmost process; grab 607 // focus, but give it back to the plugin process if requested. 608 ActivateProcess(base::GetCurrentProcId()); 609 610 nsCocoaUtils::HideOSChromeOnScreen(false); 611 612 if (plugin_pid != -1) { 613 ActivateProcess(plugin_pid); 614 } 615} 616 617void OnPluginHideWindow(uint32_t window_id, pid_t aPluginPid) { 618 bool had_windows = !plugin_visible_windows_set_.empty(); 619 plugin_visible_windows_set_.erase(window_id); 620 bool browser_needs_activation = had_windows && 621 plugin_visible_windows_set_.empty(); 622 623 plugin_modal_windows_set_.erase(window_id); 624 if (plugin_fullscreen_windows_set_.find(window_id) != 625 plugin_fullscreen_windows_set_.end()) { 626 plugin_fullscreen_windows_set_.erase(window_id); 627 pid_t plugin_pid = browser_needs_activation ? -1 : aPluginPid; 628 browser_needs_activation = false; 629 ReleasePluginFullScreen(plugin_pid); 630 } 631 632 if (browser_needs_activation) { 633 ActivateProcess(getpid()); 634 } 635} 636 637void OnSetCursor(const NSCursorInfo& cursorInfo) 638{ 639 NSCursor* aCursor = cursorInfo.GetNSCursor(); 640 if (aCursor) { 641 [aCursor set]; 642 } 643} 644 645void OnShowCursor(bool show) 646{ 647 if (show) { 648 [NSCursor unhide]; 649 } else { 650 [NSCursor hide]; 651 } 652} 653 654void OnPushCursor(const NSCursorInfo& cursorInfo) 655{ 656 NSCursor* aCursor = cursorInfo.GetNSCursor(); 657 if (aCursor) { 658 [aCursor push]; 659 } 660} 661 662void OnPopCursor() 663{ 664 [NSCursor pop]; 665} 666 667} // namespace parent 668} // namespace mac_plugin_interposing 669 670namespace mac_plugin_interposing { 671namespace child { 672 673// TODO(stuartmorgan): Make this an IPC to order the plugin process above the 674// browser process only if the browser is current frontmost. 675void FocusPluginProcess() { 676 ProcessSerialNumber this_process, front_process; 677 if ((GetCurrentProcess(&this_process) != noErr) || 678 (GetFrontProcess(&front_process) != noErr)) { 679 return; 680 } 681 682 Boolean matched = false; 683 if ((SameProcess(&this_process, &front_process, &matched) == noErr) && 684 !matched) { 685 SetFrontProcess(&this_process); 686 } 687} 688 689void NotifyBrowserOfPluginShowWindow(uint32_t window_id, CGRect bounds, 690 bool modal) { 691 ENSURE_PLUGIN_THREAD_VOID(); 692 693 PluginModuleChild *pmc = PluginModuleChild::GetChrome(); 694 if (pmc) 695 pmc->PluginShowWindow(window_id, modal, bounds); 696} 697 698void NotifyBrowserOfPluginHideWindow(uint32_t window_id, CGRect bounds) { 699 ENSURE_PLUGIN_THREAD_VOID(); 700 701 PluginModuleChild *pmc = PluginModuleChild::GetChrome(); 702 if (pmc) 703 pmc->PluginHideWindow(window_id); 704} 705 706void NotifyBrowserOfSetCursor(NSCursorInfo& aCursorInfo) 707{ 708 ENSURE_PLUGIN_THREAD_VOID(); 709 PluginModuleChild *pmc = PluginModuleChild::GetChrome(); 710 if (pmc) { 711 pmc->SetCursor(aCursorInfo); 712 } 713} 714 715void NotifyBrowserOfShowCursor(bool show) 716{ 717 ENSURE_PLUGIN_THREAD_VOID(); 718 PluginModuleChild *pmc = PluginModuleChild::GetChrome(); 719 if (pmc) { 720 pmc->ShowCursor(show); 721 } 722} 723 724void NotifyBrowserOfPushCursor(NSCursorInfo& aCursorInfo) 725{ 726 ENSURE_PLUGIN_THREAD_VOID(); 727 PluginModuleChild *pmc = PluginModuleChild::GetChrome(); 728 if (pmc) { 729 pmc->PushCursor(aCursorInfo); 730 } 731} 732 733void NotifyBrowserOfPopCursor() 734{ 735 ENSURE_PLUGIN_THREAD_VOID(); 736 PluginModuleChild *pmc = PluginModuleChild::GetChrome(); 737 if (pmc) { 738 pmc->PopCursor(); 739 } 740} 741 742struct WindowInfo { 743 uint32_t window_id; 744 CGRect bounds; 745 explicit WindowInfo(NSWindow* aWindow) { 746 NSInteger window_num = [aWindow windowNumber]; 747 window_id = window_num > 0 ? window_num : 0; 748 bounds = NSRectToCGRect([aWindow frame]); 749 } 750}; 751 752static void OnPluginWindowClosed(const WindowInfo& window_info) { 753 if (window_info.window_id == 0) 754 return; 755 mac_plugin_interposing::child::NotifyBrowserOfPluginHideWindow(window_info.window_id, 756 window_info.bounds); 757} 758 759static void OnPluginWindowShown(const WindowInfo& window_info, BOOL is_modal) { 760 // The window id is 0 if it has never been shown (including while it is the 761 // process of being shown for the first time); when that happens, we'll catch 762 // it in _setWindowNumber instead. 763 static BOOL s_pending_display_is_modal = NO; 764 if (window_info.window_id == 0) { 765 if (is_modal) 766 s_pending_display_is_modal = YES; 767 return; 768 } 769 if (s_pending_display_is_modal) { 770 is_modal = YES; 771 s_pending_display_is_modal = NO; 772 } 773 mac_plugin_interposing::child::NotifyBrowserOfPluginShowWindow( 774 window_info.window_id, window_info.bounds, is_modal); 775} 776 777static BOOL OnSetCursor(NSCursorInfo &aInfo) 778{ 779 if (NSCursorInfo::GetNativeCursorsSupported()) { 780 NotifyBrowserOfSetCursor(aInfo); 781 return YES; 782 } 783 return NO; 784} 785 786static BOOL OnHideCursor() 787{ 788 if (NSCursorInfo::GetNativeCursorsSupported()) { 789 NotifyBrowserOfShowCursor(NO); 790 return YES; 791 } 792 return NO; 793} 794 795static BOOL OnUnhideCursor() 796{ 797 if (NSCursorInfo::GetNativeCursorsSupported()) { 798 NotifyBrowserOfShowCursor(YES); 799 return YES; 800 } 801 return NO; 802} 803 804static BOOL OnPushCursor(NSCursorInfo &aInfo) 805{ 806 if (NSCursorInfo::GetNativeCursorsSupported()) { 807 NotifyBrowserOfPushCursor(aInfo); 808 return YES; 809 } 810 return NO; 811} 812 813static BOOL OnPopCursor() 814{ 815 if (NSCursorInfo::GetNativeCursorsSupported()) { 816 NotifyBrowserOfPopCursor(); 817 return YES; 818 } 819 return NO; 820} 821 822} // namespace child 823} // namespace mac_plugin_interposing 824 825using namespace mac_plugin_interposing::child; 826 827@interface NSWindow (PluginInterposing) 828- (void)pluginInterpose_orderOut:(id)sender; 829- (void)pluginInterpose_orderFront:(id)sender; 830- (void)pluginInterpose_makeKeyAndOrderFront:(id)sender; 831- (void)pluginInterpose_setWindowNumber:(NSInteger)num; 832@end 833 834@implementation NSWindow (PluginInterposing) 835 836- (void)pluginInterpose_orderOut:(id)sender { 837 WindowInfo window_info(self); 838 [self pluginInterpose_orderOut:sender]; 839 OnPluginWindowClosed(window_info); 840} 841 842- (void)pluginInterpose_orderFront:(id)sender { 843 mac_plugin_interposing::child::FocusPluginProcess(); 844 [self pluginInterpose_orderFront:sender]; 845 OnPluginWindowShown(WindowInfo(self), NO); 846} 847 848- (void)pluginInterpose_makeKeyAndOrderFront:(id)sender { 849 mac_plugin_interposing::child::FocusPluginProcess(); 850 [self pluginInterpose_makeKeyAndOrderFront:sender]; 851 OnPluginWindowShown(WindowInfo(self), NO); 852} 853 854- (void)pluginInterpose_setWindowNumber:(NSInteger)num { 855 if (num > 0) 856 mac_plugin_interposing::child::FocusPluginProcess(); 857 [self pluginInterpose_setWindowNumber:num]; 858 if (num > 0) 859 OnPluginWindowShown(WindowInfo(self), NO); 860} 861 862@end 863 864@interface NSApplication (PluginInterposing) 865- (NSInteger)pluginInterpose_runModalForWindow:(NSWindow*)window; 866@end 867 868@implementation NSApplication (PluginInterposing) 869 870- (NSInteger)pluginInterpose_runModalForWindow:(NSWindow*)window { 871 mac_plugin_interposing::child::FocusPluginProcess(); 872 // This is out-of-order relative to the other calls, but runModalForWindow: 873 // won't return until the window closes, and the order only matters for 874 // full-screen windows. 875 OnPluginWindowShown(WindowInfo(window), YES); 876 return [self pluginInterpose_runModalForWindow:window]; 877} 878 879@end 880 881// Hook commands to manipulate the current cursor, so that they can be passed 882// from the child process to the parent process. These commands have no 883// effect unless they're performed in the parent process. 884@interface NSCursor (PluginInterposing) 885- (void)pluginInterpose_set; 886- (void)pluginInterpose_push; 887- (void)pluginInterpose_pop; 888+ (NSCursor*)pluginInterpose_currentCursor; 889+ (void)pluginInterpose_hide; 890+ (void)pluginInterpose_unhide; 891+ (void)pluginInterpose_pop; 892@end 893 894// Cache the results of [NSCursor set], [NSCursor push] and [NSCursor pop]. 895// The last element is always the current cursor. 896static NSMutableArray* gCursorStack = nil; 897 898static BOOL initCursorStack() 899{ 900 if (!gCursorStack) { 901 gCursorStack = [[NSMutableArray arrayWithCapacity:5] retain]; 902 } 903 return (gCursorStack != NULL); 904} 905 906static NSCursor* currentCursorFromCache() 907{ 908 if (!initCursorStack()) 909 return nil; 910 return (NSCursor*) [gCursorStack lastObject]; 911} 912 913static void setCursorInCache(NSCursor* aCursor) 914{ 915 if (!initCursorStack() || !aCursor) 916 return; 917 NSUInteger count = [gCursorStack count]; 918 if (count) { 919 [gCursorStack replaceObjectAtIndex:count - 1 withObject:aCursor]; 920 } else { 921 [gCursorStack addObject:aCursor]; 922 } 923} 924 925static void pushCursorInCache(NSCursor* aCursor) 926{ 927 if (!initCursorStack() || !aCursor) 928 return; 929 [gCursorStack addObject:aCursor]; 930} 931 932static void popCursorInCache() 933{ 934 if (!initCursorStack()) 935 return; 936 // Apple's doc on the +[NSCursor pop] method says: "If the current cursor 937 // is the only cursor on the stack, this method does nothing." 938 if ([gCursorStack count] > 1) { 939 [gCursorStack removeLastObject]; 940 } 941} 942 943@implementation NSCursor (PluginInterposing) 944 945- (void)pluginInterpose_set 946{ 947 NSCursorInfo info(self); 948 OnSetCursor(info); 949 setCursorInCache(self); 950 [self pluginInterpose_set]; 951} 952 953- (void)pluginInterpose_push 954{ 955 NSCursorInfo info(self); 956 OnPushCursor(info); 957 pushCursorInCache(self); 958 [self pluginInterpose_push]; 959} 960 961- (void)pluginInterpose_pop 962{ 963 OnPopCursor(); 964 popCursorInCache(); 965 [self pluginInterpose_pop]; 966} 967 968// The currentCursor method always returns nil when running in a background 969// process. But this may confuse plugins (notably Flash, see bug 621117). So 970// if we get a nil return from the "call to super", we return a cursor that's 971// been cached by previous calls to set or push. According to Apple's docs, 972// currentCursor "only returns the cursor set by your application using 973// NSCursor methods". So we don't need to worry about changes to the cursor 974// made by other methods like SetThemeCursor(). 975+ (NSCursor*)pluginInterpose_currentCursor 976{ 977 NSCursor* retval = [self pluginInterpose_currentCursor]; 978 if (!retval) { 979 retval = currentCursorFromCache(); 980 } 981 return retval; 982} 983 984+ (void)pluginInterpose_hide 985{ 986 OnHideCursor(); 987 [self pluginInterpose_hide]; 988} 989 990+ (void)pluginInterpose_unhide 991{ 992 OnUnhideCursor(); 993 [self pluginInterpose_unhide]; 994} 995 996+ (void)pluginInterpose_pop 997{ 998 OnPopCursor(); 999 popCursorInCache(); 1000 [self pluginInterpose_pop]; 1001} 1002 1003@end 1004 1005static void ExchangeMethods(Class target_class, 1006 BOOL class_method, 1007 SEL original, 1008 SEL replacement) { 1009 Method m1; 1010 Method m2; 1011 if (class_method) { 1012 m1 = class_getClassMethod(target_class, original); 1013 m2 = class_getClassMethod(target_class, replacement); 1014 } else { 1015 m1 = class_getInstanceMethod(target_class, original); 1016 m2 = class_getInstanceMethod(target_class, replacement); 1017 } 1018 1019 if (m1 == m2) 1020 return; 1021 1022 if (m1 && m2) 1023 method_exchangeImplementations(m1, m2); 1024 else 1025 NS_NOTREACHED("Cocoa swizzling failed"); 1026} 1027 1028namespace mac_plugin_interposing { 1029namespace child { 1030 1031void SetUpCocoaInterposing() { 1032 Class nswindow_class = [NSWindow class]; 1033 ExchangeMethods(nswindow_class, NO, @selector(orderOut:), 1034 @selector(pluginInterpose_orderOut:)); 1035 ExchangeMethods(nswindow_class, NO, @selector(orderFront:), 1036 @selector(pluginInterpose_orderFront:)); 1037 ExchangeMethods(nswindow_class, NO, @selector(makeKeyAndOrderFront:), 1038 @selector(pluginInterpose_makeKeyAndOrderFront:)); 1039 ExchangeMethods(nswindow_class, NO, @selector(_setWindowNumber:), 1040 @selector(pluginInterpose_setWindowNumber:)); 1041 1042 ExchangeMethods([NSApplication class], NO, @selector(runModalForWindow:), 1043 @selector(pluginInterpose_runModalForWindow:)); 1044 1045 Class nscursor_class = [NSCursor class]; 1046 ExchangeMethods(nscursor_class, NO, @selector(set), 1047 @selector(pluginInterpose_set)); 1048 ExchangeMethods(nscursor_class, NO, @selector(push), 1049 @selector(pluginInterpose_push)); 1050 ExchangeMethods(nscursor_class, NO, @selector(pop), 1051 @selector(pluginInterpose_pop)); 1052 ExchangeMethods(nscursor_class, YES, @selector(currentCursor), 1053 @selector(pluginInterpose_currentCursor)); 1054 ExchangeMethods(nscursor_class, YES, @selector(hide), 1055 @selector(pluginInterpose_hide)); 1056 ExchangeMethods(nscursor_class, YES, @selector(unhide), 1057 @selector(pluginInterpose_unhide)); 1058 ExchangeMethods(nscursor_class, YES, @selector(pop), 1059 @selector(pluginInterpose_pop)); 1060} 1061 1062} // namespace child 1063} // namespace mac_plugin_interposing 1064 1065// Called from plugin_child_interpose.mm, which hooks calls to 1066// SetCursor() (the QuickDraw call) from the plugin child process. 1067extern "C" NS_VISIBILITY_DEFAULT BOOL 1068mac_plugin_interposing_child_OnSetCursor(const Cursor* cursor) 1069{ 1070 NSCursorInfo info(cursor); 1071 return OnSetCursor(info); 1072} 1073 1074// Called from plugin_child_interpose.mm, which hooks calls to 1075// SetThemeCursor() (the Appearance Manager call) from the plugin child 1076// process. 1077extern "C" NS_VISIBILITY_DEFAULT BOOL 1078mac_plugin_interposing_child_OnSetThemeCursor(ThemeCursor cursor) 1079{ 1080 NSCursorInfo info; 1081 switch (cursor) { 1082 case kThemeArrowCursor: 1083 info.SetType(NSCursorInfo::TypeArrow); 1084 break; 1085 case kThemeCopyArrowCursor: 1086 info.SetType(NSCursorInfo::TypeDragCopy); 1087 break; 1088 case kThemeAliasArrowCursor: 1089 info.SetType(NSCursorInfo::TypeDragLink); 1090 break; 1091 case kThemeContextualMenuArrowCursor: 1092 info.SetType(NSCursorInfo::TypeContextualMenu); 1093 break; 1094 case kThemeIBeamCursor: 1095 info.SetType(NSCursorInfo::TypeIBeam); 1096 break; 1097 case kThemeCrossCursor: 1098 case kThemePlusCursor: 1099 info.SetType(NSCursorInfo::TypeCrosshair); 1100 break; 1101 case kThemeWatchCursor: 1102 case kThemeSpinningCursor: 1103 info.SetType(NSCursorInfo::TypeArrow); 1104 break; 1105 case kThemeClosedHandCursor: 1106 info.SetType(NSCursorInfo::TypeClosedHand); 1107 break; 1108 case kThemeOpenHandCursor: 1109 info.SetType(NSCursorInfo::TypeOpenHand); 1110 break; 1111 case kThemePointingHandCursor: 1112 case kThemeCountingUpHandCursor: 1113 case kThemeCountingDownHandCursor: 1114 case kThemeCountingUpAndDownHandCursor: 1115 info.SetType(NSCursorInfo::TypePointingHand); 1116 break; 1117 case kThemeResizeLeftCursor: 1118 info.SetType(NSCursorInfo::TypeResizeLeft); 1119 break; 1120 case kThemeResizeRightCursor: 1121 info.SetType(NSCursorInfo::TypeResizeRight); 1122 break; 1123 case kThemeResizeLeftRightCursor: 1124 info.SetType(NSCursorInfo::TypeResizeLeftRight); 1125 break; 1126 case kThemeNotAllowedCursor: 1127 info.SetType(NSCursorInfo::TypeNotAllowed); 1128 break; 1129 case kThemeResizeUpCursor: 1130 info.SetType(NSCursorInfo::TypeResizeUp); 1131 break; 1132 case kThemeResizeDownCursor: 1133 info.SetType(NSCursorInfo::TypeResizeDown); 1134 break; 1135 case kThemeResizeUpDownCursor: 1136 info.SetType(NSCursorInfo::TypeResizeUpDown); 1137 break; 1138 case kThemePoofCursor: 1139 info.SetType(NSCursorInfo::TypeDisappearingItem); 1140 break; 1141 default: 1142 info.SetType(NSCursorInfo::TypeArrow); 1143 break; 1144 } 1145 return OnSetCursor(info); 1146} 1147 1148extern "C" NS_VISIBILITY_DEFAULT BOOL 1149mac_plugin_interposing_child_OnHideCursor() 1150{ 1151 return OnHideCursor(); 1152} 1153 1154extern "C" NS_VISIBILITY_DEFAULT BOOL 1155mac_plugin_interposing_child_OnShowCursor() 1156{ 1157 return OnUnhideCursor(); 1158} 1159