diff -r 000000000000 -r dd21522fd290 webengine/osswebengine/WebKit/Plugins/WebBaseNetscapePluginView.mm --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/webengine/osswebengine/WebKit/Plugins/WebBaseNetscapePluginView.mm Mon Mar 30 12:54:55 2009 +0300 @@ -0,0 +1,3203 @@ +/* + * Copyright (C) 2005, 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. + */ + +#ifndef __LP64__ + +#import "WebBaseNetscapePluginView.h" + +#import "WebDataSourceInternal.h" +#import "WebDefaultUIDelegate.h" +#import "WebFrameBridge.h" +#import "WebFrameInternal.h" +#import "WebFrameView.h" +#import "WebGraphicsExtras.h" +#import "WebKitLogging.h" +#import "WebKitNSStringExtras.h" +#import "WebKitSystemInterface.h" +#import "WebNSDataExtras.h" +#import "WebNSDictionaryExtras.h" +#import "WebNSObjectExtras.h" +#import "WebNSURLExtras.h" +#import "WebNSURLRequestExtras.h" +#import "WebNSViewExtras.h" +#import "WebNetscapePluginPackage.h" +#import "WebNetscapePluginStream.h" +#import "WebNullPluginView.h" +#import "WebPreferences.h" +#import "WebViewInternal.h" +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import + +using namespace WebCore; + +// Send null events 50 times a second when active, so plug-ins like Flash get high frame rates. +#define NullEventIntervalActive 0.02 +#define NullEventIntervalNotActive 0.25 + +#define LoginWindowDidSwitchFromUserNotification @"WebLoginWindowDidSwitchFromUserNotification" +#define LoginWindowDidSwitchToUserNotification @"WebLoginWindowDidSwitchToUserNotification" + +SOFT_LINK_FRAMEWORK(OpenGL) +SOFT_LINK_FRAMEWORK(AGL) + +SOFT_LINK(OpenGL, CGLGetOffScreen, CGLError, (CGLContextObj ctx, GLsizei *width, GLsizei *height, GLint *rowbytes, void **baseaddr), (ctx, width, height, rowbytes, baseaddr)) +SOFT_LINK(OpenGL, CGLSetOffScreen, CGLError, (CGLContextObj ctx, GLsizei width, GLsizei height, GLint rowbytes, void *baseaddr), (ctx, width, height, rowbytes, baseaddr)) +SOFT_LINK(OpenGL, glViewport, void, (GLint x, GLint y, GLsizei width, GLsizei height), (x, y, width, height)) +SOFT_LINK(AGL, aglCreateContext, AGLContext, (AGLPixelFormat pix, AGLContext share), (pix, share)) +SOFT_LINK(AGL, aglSetWindowRef, GLboolean, (AGLContext ctx, WindowRef window), (ctx, window)) +SOFT_LINK(AGL, aglSetDrawable, GLboolean, (AGLContext ctx, AGLDrawable draw), (ctx, draw)) +#ifndef BUILDING_ON_TIGER +SOFT_LINK(AGL, aglChoosePixelFormat, AGLPixelFormat, (const void *gdevs, GLint ndev, const GLint *attribs), (gdevs, ndev, attribs)) +#else +SOFT_LINK(AGL, aglChoosePixelFormat, AGLPixelFormat, (const AGLDevice *gdevs, GLint ndev, const GLint *attribs), (gdevs, ndev, attribs)) +#endif +SOFT_LINK(AGL, aglDestroyPixelFormat, void, (AGLPixelFormat pix), (pix)) +SOFT_LINK(AGL, aglDestroyContext, GLboolean, (AGLContext ctx), (ctx)) +SOFT_LINK(AGL, aglGetCGLContext, GLboolean, (AGLContext ctx, void **cgl_ctx), (ctx, cgl_ctx)) +SOFT_LINK(AGL, aglGetCurrentContext, AGLContext, (void), ()) +SOFT_LINK(AGL, aglSetCurrentContext, GLboolean, (AGLContext ctx), (ctx)) +SOFT_LINK(AGL, aglGetError, GLenum, (void), ()) +SOFT_LINK(AGL, aglUpdateContext, GLboolean, (AGLContext ctx), (ctx)) +SOFT_LINK(AGL, aglErrorString, const GLubyte *, (GLenum code), (code)) + +@interface WebBaseNetscapePluginView (Internal) +- (void)_viewHasMoved; +- (NPError)_createPlugin; +- (void)_destroyPlugin; +- (NSBitmapImageRep *)_printedPluginBitmap; +- (BOOL)_createAGLContextIfNeeded; +- (BOOL)_createWindowedAGLContext; +- (BOOL)_createWindowlessAGLContext; +- (CGLContextObj)_cglContext; +- (BOOL)_getAGLOffscreenBuffer:(GLvoid **)outBuffer width:(GLsizei *)outWidth height:(GLsizei *)outHeight; +- (void)_destroyAGLContext; +- (void)_reshapeAGLWindow; +- (void)_hideAGLWindow; +- (NSImage *)_aglOffscreenImageForDrawingInRect:(NSRect)drawingInRect; +- (void)_redeliverStream; +@end + +static WebBaseNetscapePluginView *currentPluginView = nil; + +typedef struct OpaquePortState* PortState; + +#ifndef NP_NO_QUICKDRAW + +// QuickDraw is not available in 64-bit + +typedef struct { + GrafPtr oldPort; + GDHandle oldDevice; + Point oldOrigin; + RgnHandle oldClipRegion; + RgnHandle oldVisibleRegion; + RgnHandle clipRegion; + BOOL forUpdate; +} PortState_QD; + +#endif /* NP_NO_QUICKDRAW */ + +typedef struct { + CGContextRef context; +} PortState_CG; + +typedef struct { + AGLContext oldContext; +} PortState_GL; + +@interface WebPluginRequest : NSObject +{ + NSURLRequest *_request; + NSString *_frameName; + void *_notifyData; + BOOL _didStartFromUserGesture; + BOOL _sendNotification; +} + +- (id)initWithRequest:(NSURLRequest *)request frameName:(NSString *)frameName notifyData:(void *)notifyData sendNotification:(BOOL)sendNotification didStartFromUserGesture:(BOOL)currentEventIsUserGesture; + +- (NSURLRequest *)request; +- (NSString *)frameName; +- (void *)notifyData; +- (BOOL)isCurrentEventUserGesture; +- (BOOL)sendNotification; + +@end + +@interface NSData (WebPluginDataExtras) +- (BOOL)_web_startsWithBlankLine; +- (NSInteger)_web_locationAfterFirstBlankLine; +@end + +static OSStatus TSMEventHandler(EventHandlerCallRef inHandlerRef, EventRef inEvent, void *pluginView); + +@interface WebBaseNetscapePluginView (ForwardDeclarations) +- (void)setWindowIfNecessary; +- (NPError)loadRequest:(NSMutableURLRequest *)request inTarget:(const char *)cTarget withNotifyData:(void *)notifyData sendNotification:(BOOL)sendNotification; +@end + +@implementation WebBaseNetscapePluginView + ++ (void)initialize +{ +#ifndef BUILDING_ON_TIGER + WebCoreObjCFinalizeOnMainThread(self); +#endif + WKSendUserChangeNotifications(); +} + +#pragma mark EVENTS + ++ (void)getCarbonEvent:(EventRecord *)carbonEvent +{ + carbonEvent->what = nullEvent; + carbonEvent->message = 0; + carbonEvent->when = TickCount(); + + GetGlobalMouse(&carbonEvent->where); + carbonEvent->where.h = static_cast(carbonEvent->where.h * HIGetScaleFactor()); + carbonEvent->where.v = static_cast(carbonEvent->where.v * HIGetScaleFactor()); + carbonEvent->modifiers = GetCurrentKeyModifiers(); + if (!Button()) + carbonEvent->modifiers |= btnState; +} + +- (void)getCarbonEvent:(EventRecord *)carbonEvent +{ + [[self class] getCarbonEvent:carbonEvent]; +} + +- (EventModifiers)modifiersForEvent:(NSEvent *)event +{ + EventModifiers modifiers; + unsigned int modifierFlags = [event modifierFlags]; + NSEventType eventType = [event type]; + + modifiers = 0; + + if (eventType != NSLeftMouseDown && eventType != NSRightMouseDown) + modifiers |= btnState; + + if (modifierFlags & NSCommandKeyMask) + modifiers |= cmdKey; + + if (modifierFlags & NSShiftKeyMask) + modifiers |= shiftKey; + + if (modifierFlags & NSAlphaShiftKeyMask) + modifiers |= alphaLock; + + if (modifierFlags & NSAlternateKeyMask) + modifiers |= optionKey; + + if (modifierFlags & NSControlKeyMask || eventType == NSRightMouseDown) + modifiers |= controlKey; + + return modifiers; +} + +- (void)getCarbonEvent:(EventRecord *)carbonEvent withEvent:(NSEvent *)cocoaEvent +{ + if (WKConvertNSEventToCarbonEvent(carbonEvent, cocoaEvent)) { + carbonEvent->where.h = static_cast(carbonEvent->where.h * HIGetScaleFactor()); + carbonEvent->where.v = static_cast(carbonEvent->where.v * HIGetScaleFactor()); + return; + } + + NSPoint where = [[cocoaEvent window] convertBaseToScreen:[cocoaEvent locationInWindow]]; + + carbonEvent->what = nullEvent; + carbonEvent->message = 0; + carbonEvent->when = (UInt32)([cocoaEvent timestamp] * 60); // seconds to ticks + carbonEvent->where.h = (short)where.x; + carbonEvent->where.v = (short)(NSMaxY([[[NSScreen screens] objectAtIndex:0] frame]) - where.y); + carbonEvent->modifiers = [self modifiersForEvent:cocoaEvent]; +} + +- (BOOL)superviewsHaveSuperviews +{ + NSView *contentView = [[self window] contentView]; + NSView *view; + for (view = self; view != nil; view = [view superview]) { + if (view == contentView) { + return YES; + } + } + return NO; +} + +#ifndef NP_NO_QUICKDRAW + +// The WindowRef created by -[NSWindow windowRef] has a QuickDraw GrafPort that covers +// the entire window frame (or structure region to use the Carbon term) rather then just the window content. +// We can remove this when is fixed. +- (void)fixWindowPort +{ + ASSERT(drawingModel == NPDrawingModelQuickDraw); + + NSWindow *currentWindow = [self currentWindow]; + if ([currentWindow isKindOfClass:objc_getClass("NSCarbonWindow")]) + return; + + float windowHeight = [currentWindow frame].size.height; + NSView *contentView = [currentWindow contentView]; + NSRect contentRect = [contentView convertRect:[contentView frame] toView:nil]; // convert to window-relative coordinates + + CGrafPtr oldPort; + GetPort(&oldPort); + SetPort(GetWindowPort((WindowRef)[currentWindow windowRef])); + + MovePortTo(static_cast(contentRect.origin.x), /* Flip Y */ static_cast(windowHeight - NSMaxY(contentRect))); + PortSize(static_cast(contentRect.size.width), static_cast(contentRect.size.height)); + + SetPort(oldPort); +} + +static UInt32 getQDPixelFormatForBitmapContext(CGContextRef context) +{ + UInt32 byteOrder = CGBitmapContextGetBitmapInfo(context) & kCGBitmapByteOrderMask; + if (byteOrder == kCGBitmapByteOrderDefault) + switch (CGBitmapContextGetBitsPerPixel(context)) { + case 16: + byteOrder = kCGBitmapByteOrder16Host; + break; + case 32: + byteOrder = kCGBitmapByteOrder32Host; + break; + } + switch (byteOrder) { + case kCGBitmapByteOrder16Little: + return k16LE555PixelFormat; + case kCGBitmapByteOrder32Little: + return k32BGRAPixelFormat; + case kCGBitmapByteOrder16Big: + return k16BE555PixelFormat; + case kCGBitmapByteOrder32Big: + return k32ARGBPixelFormat; + } + ASSERT_NOT_REACHED(); + return 0; +} + +static inline void getNPRect(const CGRect& cgr, NPRect& npr) +{ + npr.top = static_cast(cgr.origin.y); + npr.left = static_cast(cgr.origin.x); + npr.bottom = static_cast(CGRectGetMaxY(cgr)); + npr.right = static_cast(CGRectGetMaxX(cgr)); +} + +#endif + +static inline void getNPRect(const NSRect& nr, NPRect& npr) +{ + npr.top = static_cast(nr.origin.y); + npr.left = static_cast(nr.origin.x); + npr.bottom = static_cast(NSMaxY(nr)); + npr.right = static_cast(NSMaxX(nr)); +} + +- (NSRect)visibleRect +{ + // WebCore may impose an additional clip (via CSS overflow or clip properties). Fetch + // that clip now. + return NSIntersectionRect([self convertRect:[element _windowClipRect] fromView:nil], [super visibleRect]); +} + +- (PortState)saveAndSetNewPortStateForUpdate:(BOOL)forUpdate +{ + ASSERT([self currentWindow] != nil); + +#ifndef NP_NO_QUICKDRAW + // If drawing with QuickDraw, fix the window port so that it has the same bounds as the NSWindow's + // content view. This makes it easier to convert between AppKit view and QuickDraw port coordinates. + if (drawingModel == NPDrawingModelQuickDraw) + [self fixWindowPort]; +#endif + + WindowRef windowRef = (WindowRef)[[self currentWindow] windowRef]; + ASSERT(windowRef); + + // Use AppKit to convert view coordinates to NSWindow coordinates. + NSRect boundsInWindow = [self convertRect:[self bounds] toView:nil]; + NSRect visibleRectInWindow = [self convertRect:[self visibleRect] toView:nil]; + + // Flip Y to convert NSWindow coordinates to top-left-based window coordinates. + float borderViewHeight = [[self currentWindow] frame].size.height; + boundsInWindow.origin.y = borderViewHeight - NSMaxY(boundsInWindow); + visibleRectInWindow.origin.y = borderViewHeight - NSMaxY(visibleRectInWindow); + +#ifndef NP_NO_QUICKDRAW + // Look at the Carbon port to convert top-left-based window coordinates into top-left-based content coordinates. + if (drawingModel == NPDrawingModelQuickDraw) { + ::Rect portBounds; + CGrafPtr port = GetWindowPort(windowRef); + GetPortBounds(port, &portBounds); + + PixMap *pix = *GetPortPixMap(port); + boundsInWindow.origin.x += pix->bounds.left - portBounds.left; + boundsInWindow.origin.y += pix->bounds.top - portBounds.top; + visibleRectInWindow.origin.x += pix->bounds.left - portBounds.left; + visibleRectInWindow.origin.y += pix->bounds.top - portBounds.top; + } +#endif + + window.x = (int32)boundsInWindow.origin.x; + window.y = (int32)boundsInWindow.origin.y; + window.width = static_cast(NSWidth(boundsInWindow)); + window.height = static_cast(NSHeight(boundsInWindow)); + + // "Clip-out" the plug-in when: + // 1) it's not really in a window or off-screen or has no height or width. + // 2) window.x is a "big negative number" which is how WebCore expresses off-screen widgets. + // 3) the window is miniaturized or the app is hidden + // 4) we're inside of viewWillMoveToWindow: with a nil window. In this case, superviews may already have nil + // superviews and nil windows and results from convertRect:toView: are incorrect. + NSWindow *realWindow = [self window]; + if (window.width <= 0 || window.height <= 0 || window.x < -100000 + || realWindow == nil || [realWindow isMiniaturized] + || [NSApp isHidden] + || ![self superviewsHaveSuperviews] + || [self isHiddenOrHasHiddenAncestor]) { + + // The following code tries to give plug-ins the same size they will eventually have. + // The specifiedWidth and specifiedHeight variables are used to predict the size that + // WebCore will eventually resize us to. + + // The QuickTime plug-in has problems if you give it a width or height of 0. + // Since other plug-ins also might have the same sort of trouble, we make sure + // to always give plug-ins a size other than 0,0. + + if (window.width <= 0) + window.width = specifiedWidth > 0 ? specifiedWidth : 100; + if (window.height <= 0) + window.height = specifiedHeight > 0 ? specifiedHeight : 100; + + window.clipRect.bottom = window.clipRect.top; + window.clipRect.left = window.clipRect.right; + } else { + getNPRect(visibleRectInWindow, window.clipRect); + } + + // Save the port state, set up the port for entry into the plugin + PortState portState; + switch (drawingModel) { +#ifndef NP_NO_QUICKDRAW + case NPDrawingModelQuickDraw: { + // Set up NS_Port. + ::Rect portBounds; + CGrafPtr port = GetWindowPort(windowRef); + GetPortBounds(port, &portBounds); + nPort.qdPort.port = port; + nPort.qdPort.portx = (int32)-boundsInWindow.origin.x; + nPort.qdPort.porty = (int32)-boundsInWindow.origin.y; + window.window = &nPort; + + PortState_QD *qdPortState = (PortState_QD*)malloc(sizeof(PortState_QD)); + portState = (PortState)qdPortState; + + GetGWorld(&qdPortState->oldPort, &qdPortState->oldDevice); + + qdPortState->oldOrigin.h = portBounds.left; + qdPortState->oldOrigin.v = portBounds.top; + + qdPortState->oldClipRegion = NewRgn(); + GetPortClipRegion(port, qdPortState->oldClipRegion); + + qdPortState->oldVisibleRegion = NewRgn(); + GetPortVisibleRegion(port, qdPortState->oldVisibleRegion); + + RgnHandle clipRegion = NewRgn(); + qdPortState->clipRegion = clipRegion; + + CGContextRef currentContext = (CGContextRef)[[NSGraphicsContext currentContext] graphicsPort]; + if (currentContext && WKCGContextIsBitmapContext(currentContext)) { + // We use WKCGContextIsBitmapContext here, because if we just called CGBitmapContextGetData + // on any context, we'd log to the console every time. But even if WKCGContextIsBitmapContext + // returns true, it still might not be a context we need to create a GWorld for; for example + // transparency layers will return true, but return 0 for CGBitmapContextGetData. + void* offscreenData = CGBitmapContextGetData(currentContext); + if (offscreenData) { + // If the current context is an offscreen bitmap, then create a GWorld for it. + ::Rect offscreenBounds; + offscreenBounds.top = 0; + offscreenBounds.left = 0; + offscreenBounds.right = CGBitmapContextGetWidth(currentContext); + offscreenBounds.bottom = CGBitmapContextGetHeight(currentContext); + GWorldPtr newOffscreenGWorld; + QDErr err = NewGWorldFromPtr(&newOffscreenGWorld, + getQDPixelFormatForBitmapContext(currentContext), &offscreenBounds, 0, 0, 0, + static_cast(offscreenData), CGBitmapContextGetBytesPerRow(currentContext)); + ASSERT(newOffscreenGWorld && !err); + if (!err) { + if (offscreenGWorld) + DisposeGWorld(offscreenGWorld); + offscreenGWorld = newOffscreenGWorld; + + SetGWorld(offscreenGWorld, NULL); + + port = offscreenGWorld; + + nPort.qdPort.port = port; + boundsInWindow = [self bounds]; + + // Generate a QD origin based on the current affine transform for currentContext. + CGAffineTransform offscreenMatrix = CGContextGetCTM(currentContext); + CGPoint origin = {0,0}; + CGPoint axisFlip = {1,1}; + origin = CGPointApplyAffineTransform(origin, offscreenMatrix); + axisFlip = CGPointApplyAffineTransform(axisFlip, offscreenMatrix); + + // Quartz bitmaps have origins at the bottom left, but the axes may be inverted, so handle that. + origin.x = offscreenBounds.left - origin.x * (axisFlip.x - origin.x); + origin.y = offscreenBounds.bottom + origin.y * (axisFlip.y - origin.y); + + nPort.qdPort.portx = static_cast(-boundsInWindow.origin.x + origin.x); + nPort.qdPort.porty = static_cast(-boundsInWindow.origin.y - origin.y); + window.x = 0; + window.y = 0; + window.window = &nPort; + + // Use the clip bounds from the context instead of the bounds we created + // from the window above. + getNPRect(CGContextGetClipBoundingBox(currentContext), window.clipRect); + } + } + } + + MacSetRectRgn(clipRegion, + window.clipRect.left + nPort.qdPort.portx, window.clipRect.top + nPort.qdPort.porty, + window.clipRect.right + nPort.qdPort.portx, window.clipRect.bottom + nPort.qdPort.porty); + + // Clip to dirty region so plug-in does not draw over already-drawn regions of the window that are + // not going to be redrawn this update. This forces plug-ins to play nice with z-index ordering. + if (forUpdate) { + RgnHandle viewClipRegion = NewRgn(); + + // Get list of dirty rects from the opaque ancestor -- WebKit does some tricks with invalidation and + // display to enable z-ordering for NSViews; a side-effect of this is that only the WebHTMLView + // knows about the true set of dirty rects. + NSView *opaqueAncestor = [self opaqueAncestor]; + const NSRect *dirtyRects; + NSInteger dirtyRectCount, dirtyRectIndex; + [opaqueAncestor getRectsBeingDrawn:&dirtyRects count:&dirtyRectCount]; + + for (dirtyRectIndex = 0; dirtyRectIndex < dirtyRectCount; dirtyRectIndex++) { + NSRect dirtyRect = [self convertRect:dirtyRects[dirtyRectIndex] fromView:opaqueAncestor]; + if (!NSEqualSizes(dirtyRect.size, NSZeroSize)) { + // Create a region for this dirty rect + RgnHandle dirtyRectRegion = NewRgn(); + SetRectRgn(dirtyRectRegion, static_cast(NSMinX(dirtyRect)), static_cast(NSMinY(dirtyRect)), static_cast(NSMaxX(dirtyRect)), static_cast(NSMaxY(dirtyRect))); + + // Union this dirty rect with the rest of the dirty rects + UnionRgn(viewClipRegion, dirtyRectRegion, viewClipRegion); + DisposeRgn(dirtyRectRegion); + } + } + + // Intersect the dirty region with the clip region, so that we only draw over dirty parts + SectRgn(clipRegion, viewClipRegion, clipRegion); + DisposeRgn(viewClipRegion); + } + + // Switch to the port and set it up. + SetPort(port); + PenNormal(); + ForeColor(blackColor); + BackColor(whiteColor); + SetOrigin(nPort.qdPort.portx, nPort.qdPort.porty); + SetPortClipRegion(nPort.qdPort.port, clipRegion); + + if (forUpdate) { + // AppKit may have tried to help us by doing a BeginUpdate. + // But the invalid region at that level didn't include AppKit's notion of what was not valid. + // We reset the port's visible region to counteract what BeginUpdate did. + SetPortVisibleRegion(nPort.qdPort.port, clipRegion); + InvalWindowRgn(windowRef, clipRegion); + } + + qdPortState->forUpdate = forUpdate; + break; + } +#endif /* NP_NO_QUICKDRAW */ + + case NPDrawingModelCoreGraphics: { + // A CoreGraphics plugin's window may only be set while the plugin view is being updated + ASSERT(forUpdate && [NSView focusView] == self); + + CGContextRef context = static_cast([[NSGraphicsContext currentContext] graphicsPort]); + + PortState_CG *cgPortState = (PortState_CG *)malloc(sizeof(PortState_CG)); + portState = (PortState)cgPortState; + cgPortState->context = context; + + // Update the plugin's window/context + nPort.cgPort.window = windowRef; + nPort.cgPort.context = context; + window.window = &nPort.cgPort; + + // Save current graphics context's state; will be restored by -restorePortState: + CGContextSaveGState(context); + + // Get list of dirty rects from the opaque ancestor -- WebKit does some tricks with invalidation and + // display to enable z-ordering for NSViews; a side-effect of this is that only the WebHTMLView + // knows about the true set of dirty rects. + NSView *opaqueAncestor = [self opaqueAncestor]; + const NSRect *dirtyRects; + NSInteger count; + [opaqueAncestor getRectsBeingDrawn:&dirtyRects count:&count]; + Vector convertedDirtyRects; + convertedDirtyRects.resize(count); + for (int i = 0; i < count; ++i) + reinterpret_cast(convertedDirtyRects[i]) = [self convertRect:dirtyRects[i] fromView:opaqueAncestor]; + CGContextClipToRects(context, convertedDirtyRects.data(), count); + + break; + } + + case NPDrawingModelOpenGL: { + // An OpenGL plugin's window may only be set while the plugin view is being updated + ASSERT(forUpdate && [NSView focusView] == self); + + // Clear the "current" window and context -- they will be assigned below (if all goes well) + nPort.aglPort.window = NULL; + nPort.aglPort.context = NULL; + + // Create AGL context if needed + if (![self _createAGLContextIfNeeded]) { + LOG_ERROR("Could not create AGL context"); + return NULL; + } + + // Update the plugin's window/context + nPort.aglPort.window = windowRef; + nPort.aglPort.context = [self _cglContext]; + window.window = &nPort.aglPort; + + // Save/set current AGL context + PortState_GL *glPortState = (PortState_GL *)malloc(sizeof(PortState_GL)); + portState = (PortState)glPortState; + glPortState->oldContext = aglGetCurrentContext(); + aglSetCurrentContext(aglContext); + + // Adjust viewport according to clip + switch (window.type) { + case NPWindowTypeWindow: + glViewport(static_cast(NSMinX(boundsInWindow) - NSMinX(visibleRectInWindow)), + static_cast(NSMaxY(visibleRectInWindow) - NSMaxY(boundsInWindow)), + window.width, window.height); + break; + + case NPWindowTypeDrawable: { + GLsizei width, height; + if ([self _getAGLOffscreenBuffer:NULL width:&width height:&height]) + glViewport(0, 0, width, height); + break; + } + + default: + ASSERT_NOT_REACHED(); + break; + } + break; + } + + default: + ASSERT_NOT_REACHED(); + portState = NULL; + break; + } + + return portState; +} + +- (PortState)saveAndSetNewPortState +{ + return [self saveAndSetNewPortStateForUpdate:NO]; +} + +- (void)restorePortState:(PortState)portState +{ + ASSERT([self currentWindow]); + ASSERT(portState); + + switch (drawingModel) { +#ifndef NP_NO_QUICKDRAW + case NPDrawingModelQuickDraw: { + PortState_QD *qdPortState = (PortState_QD *)portState; + WindowRef windowRef = (WindowRef)[[self currentWindow] windowRef]; + CGrafPtr port = GetWindowPort(windowRef); + + SetPort(port); + + if (qdPortState->forUpdate) + ValidWindowRgn(windowRef, qdPortState->clipRegion); + + SetOrigin(qdPortState->oldOrigin.h, qdPortState->oldOrigin.v); + + SetPortClipRegion(port, qdPortState->oldClipRegion); + if (qdPortState->forUpdate) + SetPortVisibleRegion(port, qdPortState->oldVisibleRegion); + + DisposeRgn(qdPortState->oldClipRegion); + DisposeRgn(qdPortState->oldVisibleRegion); + DisposeRgn(qdPortState->clipRegion); + + SetGWorld(qdPortState->oldPort, qdPortState->oldDevice); + break; + } +#endif /* NP_NO_QUICKDRAW */ + + case NPDrawingModelCoreGraphics: + ASSERT([NSView focusView] == self); + ASSERT(((PortState_CG *)portState)->context == nPort.cgPort.context); + CGContextRestoreGState(nPort.cgPort.context); + break; + + case NPDrawingModelOpenGL: + aglSetCurrentContext(((PortState_GL *)portState)->oldContext); + break; + + default: + ASSERT_NOT_REACHED(); + break; + } +} + +- (BOOL)sendEvent:(EventRecord *)event +{ + if (![self window]) + return NO; + ASSERT(event); + + // If at any point the user clicks or presses a key from within a plugin, set the + // currentEventIsUserGesture flag to true. This is important to differentiate legitimate + // window.open() calls; we still want to allow those. See rdar://problem/4010765 + if (event->what == mouseDown || event->what == keyDown || event->what == mouseUp || event->what == autoKey) + currentEventIsUserGesture = YES; + + suspendKeyUpEvents = NO; + + if (!isStarted) + return NO; + + ASSERT(NPP_HandleEvent); + + // Make sure we don't call NPP_HandleEvent while we're inside NPP_SetWindow. + // We probably don't want more general reentrancy protection; we are really + // protecting only against this one case, which actually comes up when + // you first install the SVG viewer plug-in. + if (inSetWindow) + return NO; + + Frame* frame = core([self webFrame]); + if (!frame) + return NO; + Page* page = frame->page(); + if (!page) + return NO; + + bool wasDeferring = page->defersLoading(); + if (!wasDeferring) + page->setDefersLoading(true); + + // Can only send updateEvt to CoreGraphics and OpenGL plugins when actually drawing + ASSERT((drawingModel != NPDrawingModelCoreGraphics && drawingModel != NPDrawingModelOpenGL) || event->what != updateEvt || [NSView focusView] == self); + + BOOL updating = event->what == updateEvt; + PortState portState; + if ((drawingModel != NPDrawingModelCoreGraphics && drawingModel != NPDrawingModelOpenGL) || event->what == updateEvt) { + // In CoreGraphics or OpenGL mode, the port state only needs to be saved/set when redrawing the plug-in view. The plug-in is not + // allowed to draw at any other time. + portState = [self saveAndSetNewPortStateForUpdate:updating]; + + // We may have changed the window, so inform the plug-in. + [self setWindowIfNecessary]; + } else + portState = NULL; + +#if !defined(NDEBUG) && !defined(NP_NO_QUICKDRAW) + // Draw green to help debug. + // If we see any green we know something's wrong. + // Note that PaintRect() only works for QuickDraw plugins; otherwise the current QD port is undefined. + if (drawingModel == NPDrawingModelQuickDraw && !isTransparent && event->what == updateEvt) { + ForeColor(greenColor); + const ::Rect bigRect = { -10000, -10000, 10000, 10000 }; + PaintRect(&bigRect); + ForeColor(blackColor); + } +#endif + + // Temporarily retain self in case the plug-in view is released while sending an event. + [[self retain] autorelease]; + + BOOL acceptedEvent; + [self willCallPlugInFunction]; + { + KJS::JSLock::DropAllLocks dropAllLocks; + acceptedEvent = NPP_HandleEvent(plugin, event); + } + [self didCallPlugInFunction]; + + currentEventIsUserGesture = NO; + + if (portState) { + if ([self currentWindow]) + [self restorePortState:portState]; + free(portState); + } + + if (!wasDeferring) + page->setDefersLoading(false); + + return acceptedEvent; +} + +- (void)sendActivateEvent:(BOOL)activate +{ + EventRecord event; + + [self getCarbonEvent:&event]; + event.what = activateEvt; + WindowRef windowRef = (WindowRef)[[self window] windowRef]; + event.message = (unsigned long)windowRef; + if (activate) { + event.modifiers |= activeFlag; + } + + BOOL acceptedEvent; + acceptedEvent = [self sendEvent:&event]; + + LOG(PluginEvents, "NPP_HandleEvent(activateEvent): %d isActive: %d", acceptedEvent, activate); +} + +- (BOOL)sendUpdateEvent +{ + EventRecord event; + + [self getCarbonEvent:&event]; + event.what = updateEvt; + WindowRef windowRef = (WindowRef)[[self window] windowRef]; + event.message = (unsigned long)windowRef; + + BOOL acceptedEvent = [self sendEvent:&event]; + + LOG(PluginEvents, "NPP_HandleEvent(updateEvt): %d", acceptedEvent); + + return acceptedEvent; +} + +-(void)sendNullEvent +{ + EventRecord event; + + [self getCarbonEvent:&event]; + + // Plug-in should not react to cursor position when not active or when a menu is down. + MenuTrackingData trackingData; + OSStatus error = GetMenuTrackingData(NULL, &trackingData); + + // Plug-in should not react to cursor position when the actual window is not key. + if (![[self window] isKeyWindow] || (error == noErr && trackingData.menu)) { + // FIXME: Does passing a v and h of -1 really prevent it from reacting to the cursor position? + event.where.v = -1; + event.where.h = -1; + } + + [self sendEvent:&event]; +} + +- (void)stopNullEvents +{ + [nullEventTimer invalidate]; + [nullEventTimer release]; + nullEventTimer = nil; +} + +- (void)restartNullEvents +{ + ASSERT([self window]); + + if (nullEventTimer) + [self stopNullEvents]; + + if (!isStarted || [[self window] isMiniaturized]) + return; + + NSTimeInterval interval; + + // If the plugin is completely obscured (scrolled out of view, for example), then we will + // send null events at a reduced rate. + interval = !isCompletelyObscured ? NullEventIntervalActive : NullEventIntervalNotActive; + nullEventTimer = [[NSTimer scheduledTimerWithTimeInterval:interval + target:self + selector:@selector(sendNullEvent) + userInfo:nil + repeats:YES] retain]; +} + +- (BOOL)acceptsFirstResponder +{ + return YES; +} + +- (void)installKeyEventHandler +{ + static const EventTypeSpec sTSMEvents[] = + { + { kEventClassTextInput, kEventTextInputUnicodeForKeyEvent } + }; + + if (!keyEventHandler) { + InstallEventHandler(GetWindowEventTarget((WindowRef)[[self window] windowRef]), + NewEventHandlerUPP(TSMEventHandler), + GetEventTypeCount(sTSMEvents), + sTSMEvents, + self, + &keyEventHandler); + } +} + +- (void)removeKeyEventHandler +{ + if (keyEventHandler) { + RemoveEventHandler(keyEventHandler); + keyEventHandler = NULL; + } +} + +- (void)setHasFocus:(BOOL)flag +{ + if (hasFocus != flag) { + hasFocus = flag; + EventRecord event; + [self getCarbonEvent:&event]; + BOOL acceptedEvent; + if (hasFocus) { + event.what = getFocusEvent; + acceptedEvent = [self sendEvent:&event]; + LOG(PluginEvents, "NPP_HandleEvent(getFocusEvent): %d", acceptedEvent); + [self installKeyEventHandler]; + } else { + event.what = loseFocusEvent; + acceptedEvent = [self sendEvent:&event]; + LOG(PluginEvents, "NPP_HandleEvent(loseFocusEvent): %d", acceptedEvent); + [self removeKeyEventHandler]; + } + } +} + +- (BOOL)becomeFirstResponder +{ + [self setHasFocus:YES]; + return YES; +} + +- (BOOL)resignFirstResponder +{ + [self setHasFocus:NO]; + return YES; +} + +// AppKit doesn't call mouseDown or mouseUp on right-click. Simulate control-click +// mouseDown and mouseUp so plug-ins get the right-click event as they do in Carbon (3125743). +- (void)rightMouseDown:(NSEvent *)theEvent +{ + [self mouseDown:theEvent]; +} + +- (void)rightMouseUp:(NSEvent *)theEvent +{ + [self mouseUp:theEvent]; +} + +- (void)mouseDown:(NSEvent *)theEvent +{ + EventRecord event; + + [self getCarbonEvent:&event withEvent:theEvent]; + event.what = mouseDown; + + BOOL acceptedEvent; + acceptedEvent = [self sendEvent:&event]; + + LOG(PluginEvents, "NPP_HandleEvent(mouseDown): %d pt.v=%d, pt.h=%d", acceptedEvent, event.where.v, event.where.h); +} + +- (void)mouseUp:(NSEvent *)theEvent +{ + EventRecord event; + + [self getCarbonEvent:&event withEvent:theEvent]; + event.what = mouseUp; + + BOOL acceptedEvent; + acceptedEvent = [self sendEvent:&event]; + + LOG(PluginEvents, "NPP_HandleEvent(mouseUp): %d pt.v=%d, pt.h=%d", acceptedEvent, event.where.v, event.where.h); +} + +- (void)mouseEntered:(NSEvent *)theEvent +{ + EventRecord event; + + [self getCarbonEvent:&event withEvent:theEvent]; + event.what = adjustCursorEvent; + + BOOL acceptedEvent; + acceptedEvent = [self sendEvent:&event]; + + LOG(PluginEvents, "NPP_HandleEvent(mouseEntered): %d", acceptedEvent); +} + +- (void)mouseExited:(NSEvent *)theEvent +{ + EventRecord event; + + [self getCarbonEvent:&event withEvent:theEvent]; + event.what = adjustCursorEvent; + + BOOL acceptedEvent; + acceptedEvent = [self sendEvent:&event]; + + // Set cursor back to arrow cursor. Because NSCursor doesn't know about changes that the plugin made, we could get confused about what we think the + // current cursor is otherwise. Therefore we have no choice but to unconditionally reset the cursor when the mouse exits the plugin. + [[NSCursor arrowCursor] set]; + + LOG(PluginEvents, "NPP_HandleEvent(mouseExited): %d", acceptedEvent); +} + +- (void)mouseDragged:(NSEvent *)theEvent +{ + // Do nothing so that other responders don't respond to the drag that initiated in this view. +} + +- (UInt32)keyMessageForEvent:(NSEvent *)event +{ + NSData *data = [[event characters] dataUsingEncoding:CFStringConvertEncodingToNSStringEncoding(CFStringGetSystemEncoding())]; + if (!data) { + return 0; + } + UInt8 characterCode; + [data getBytes:&characterCode length:1]; + UInt16 keyCode = [event keyCode]; + return keyCode << 8 | characterCode; +} + +- (void)keyUp:(NSEvent *)theEvent +{ + WKSendKeyEventToTSM(theEvent); + + // TSM won't send keyUp events so we have to send them ourselves. + // Only send keyUp events after we receive the TSM callback because this is what plug-in expect from OS 9. + if (!suspendKeyUpEvents) { + EventRecord event; + + [self getCarbonEvent:&event withEvent:theEvent]; + event.what = keyUp; + + if (event.message == 0) { + event.message = [self keyMessageForEvent:theEvent]; + } + + [self sendEvent:&event]; + } +} + +- (void)keyDown:(NSEvent *)theEvent +{ + suspendKeyUpEvents = YES; + WKSendKeyEventToTSM(theEvent); +} + +static OSStatus TSMEventHandler(EventHandlerCallRef inHandlerRef, EventRef inEvent, void *pluginView) +{ + EventRef rawKeyEventRef; + OSStatus status = GetEventParameter(inEvent, kEventParamTextInputSendKeyboardEvent, typeEventRef, NULL, sizeof(EventRef), NULL, &rawKeyEventRef); + if (status != noErr) { + LOG_ERROR("GetEventParameter failed with error: %d", status); + return noErr; + } + + // Two-pass read to allocate/extract Mac charCodes + ByteCount numBytes; + status = GetEventParameter(rawKeyEventRef, kEventParamKeyMacCharCodes, typeChar, NULL, 0, &numBytes, NULL); + if (status != noErr) { + LOG_ERROR("GetEventParameter failed with error: %d", status); + return noErr; + } + char *buffer = (char *)malloc(numBytes); + status = GetEventParameter(rawKeyEventRef, kEventParamKeyMacCharCodes, typeChar, NULL, numBytes, NULL, buffer); + if (status != noErr) { + LOG_ERROR("GetEventParameter failed with error: %d", status); + free(buffer); + return noErr; + } + + EventRef cloneEvent = CopyEvent(rawKeyEventRef); + unsigned i; + for (i = 0; i < numBytes; i++) { + status = SetEventParameter(cloneEvent, kEventParamKeyMacCharCodes, typeChar, 1 /* one char code */, &buffer[i]); + if (status != noErr) { + LOG_ERROR("SetEventParameter failed with error: %d", status); + free(buffer); + return noErr; + } + + EventRecord eventRec; + if (ConvertEventRefToEventRecord(cloneEvent, &eventRec)) { + BOOL acceptedEvent; + acceptedEvent = [(WebBaseNetscapePluginView *)pluginView sendEvent:&eventRec]; + + LOG(PluginEvents, "NPP_HandleEvent(keyDown): %d charCode:%c keyCode:%lu", + acceptedEvent, (char) (eventRec.message & charCodeMask), (eventRec.message & keyCodeMask)); + + // We originally thought that if the plug-in didn't accept this event, + // we should pass it along so that keyboard scrolling, for example, will work. + // In practice, this is not a good idea, because plug-ins tend to eat the event but return false. + // MacIE handles each key event twice because of this, but we will emulate the other browsers instead. + } + } + ReleaseEvent(cloneEvent); + + free(buffer); + + return noErr; +} + +// Fake up command-modified events so cut, copy, paste and select all menus work. +- (void)sendModifierEventWithKeyCode:(int)keyCode character:(char)character +{ + EventRecord event; + [self getCarbonEvent:&event]; + event.what = keyDown; + event.modifiers |= cmdKey; + event.message = keyCode << 8 | character; + [self sendEvent:&event]; +} + +- (void)cut:(id)sender +{ + [self sendModifierEventWithKeyCode:7 character:'x']; +} + +- (void)copy:(id)sender +{ + [self sendModifierEventWithKeyCode:8 character:'c']; +} + +- (void)paste:(id)sender +{ + [self sendModifierEventWithKeyCode:9 character:'v']; +} + +- (void)selectAll:(id)sender +{ + [self sendModifierEventWithKeyCode:0 character:'a']; +} + +#pragma mark WEB_NETSCAPE_PLUGIN + +- (BOOL)isNewWindowEqualToOldWindow +{ + if (window.x != lastSetWindow.x) + return NO; + if (window.y != lastSetWindow.y) + return NO; + if (window.width != lastSetWindow.width) + return NO; + if (window.height != lastSetWindow.height) + return NO; + if (window.clipRect.top != lastSetWindow.clipRect.top) + return NO; + if (window.clipRect.left != lastSetWindow.clipRect.left) + return NO; + if (window.clipRect.bottom != lastSetWindow.clipRect.bottom) + return NO; + if (window.clipRect.right != lastSetWindow.clipRect.right) + return NO; + if (window.type != lastSetWindow.type) + return NO; + + switch (drawingModel) { +#ifndef NP_NO_QUICKDRAW + case NPDrawingModelQuickDraw: + if (nPort.qdPort.portx != lastSetPort.qdPort.portx) + return NO; + if (nPort.qdPort.porty != lastSetPort.qdPort.porty) + return NO; + if (nPort.qdPort.port != lastSetPort.qdPort.port) + return NO; + break; +#endif /* NP_NO_QUICKDRAW */ + + case NPDrawingModelCoreGraphics: + if (nPort.cgPort.window != lastSetPort.cgPort.window) + return NO; + if (nPort.cgPort.context != lastSetPort.cgPort.context) + return NO; + break; + + case NPDrawingModelOpenGL: + if (nPort.aglPort.window != lastSetPort.aglPort.window) + return NO; + if (nPort.aglPort.context != lastSetPort.aglPort.context) + return NO; + break; + + default: + ASSERT_NOT_REACHED(); + break; + } + + return YES; +} + +- (void)updateAndSetWindow +{ + if (drawingModel == NPDrawingModelCoreGraphics || drawingModel == NPDrawingModelOpenGL) { + // Can only update CoreGraphics and OpenGL plugins while redrawing the plugin view + [self setNeedsDisplay:YES]; + return; + } + + // Can't update the plugin if it has not started (or has been stopped) + if (!isStarted) + return; + + PortState portState = [self saveAndSetNewPortState]; + if (portState) { + [self setWindowIfNecessary]; + [self restorePortState:portState]; + free(portState); + } +} + +- (void)setWindowIfNecessary +{ + if (!isStarted) { + return; + } + + if (![self isNewWindowEqualToOldWindow]) { + // Make sure we don't call NPP_HandleEvent while we're inside NPP_SetWindow. + // We probably don't want more general reentrancy protection; we are really + // protecting only against this one case, which actually comes up when + // you first install the SVG viewer plug-in. + NPError npErr; + ASSERT(!inSetWindow); + + inSetWindow = YES; + + // A CoreGraphics or OpenGL plugin's window may only be set while the plugin is being updated + ASSERT((drawingModel != NPDrawingModelCoreGraphics && drawingModel != NPDrawingModelOpenGL) || [NSView focusView] == self); + + [self willCallPlugInFunction]; + { + KJS::JSLock::DropAllLocks dropAllLocks; + npErr = NPP_SetWindow(plugin, &window); + } + [self didCallPlugInFunction]; + inSetWindow = NO; + +#ifndef NDEBUG + switch (drawingModel) { +#ifndef NP_NO_QUICKDRAW + case NPDrawingModelQuickDraw: + LOG(Plugins, "NPP_SetWindow (QuickDraw): %d, port=0x%08x, window.x:%d window.y:%d window.width:%d window.height:%d", + npErr, (int)nPort.qdPort.port, (int)window.x, (int)window.y, (int)window.width, (int)window.height); + break; +#endif /* NP_NO_QUICKDRAW */ + + case NPDrawingModelCoreGraphics: + LOG(Plugins, "NPP_SetWindow (CoreGraphics): %d, window=%p, context=%p, window.x:%d window.y:%d window.width:%d window.height:%d", + npErr, nPort.cgPort.window, nPort.cgPort.context, (int)window.x, (int)window.y, (int)window.width, (int)window.height); + break; + + case NPDrawingModelOpenGL: + LOG(Plugins, "NPP_SetWindow (CoreGraphics): %d, window=%p, context=%p, window.x:%d window.y:%d window.width:%d window.height:%d", + npErr, nPort.aglPort.window, nPort.aglPort.context, (int)window.x, (int)window.y, (int)window.width, (int)window.height); + break; + + default: + ASSERT_NOT_REACHED(); + break; + } +#endif /* !defined(NDEBUG) */ + + lastSetWindow = window; + lastSetPort = nPort; + } +} + +- (void)removeTrackingRect +{ + if (trackingTag) { + [self removeTrackingRect:trackingTag]; + trackingTag = 0; + + // Do the following after setting trackingTag to 0 so we don't re-enter. + + // Balance the retain in resetTrackingRect. Use autorelease in case we hold + // the last reference to the window during tear-down, to avoid crashing AppKit. + [[self window] autorelease]; + } +} + +- (void)resetTrackingRect +{ + [self removeTrackingRect]; + if (isStarted) { + // Retain the window so that removeTrackingRect can work after the window is closed. + [[self window] retain]; + trackingTag = [self addTrackingRect:[self bounds] owner:self userData:nil assumeInside:NO]; + } +} + ++ (void)setCurrentPluginView:(WebBaseNetscapePluginView *)view +{ + currentPluginView = view; +} + ++ (WebBaseNetscapePluginView *)currentPluginView +{ + return currentPluginView; +} + +- (BOOL)canStart +{ + return YES; +} + +- (void)didStart +{ + if (_loadManually) { + [self _redeliverStream]; + return; + } + + // If the OBJECT/EMBED tag has no SRC, the URL is passed to us as "". + // Check for this and don't start a load in this case. + if (sourceURL != nil && ![sourceURL _web_isEmpty]) { + NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:sourceURL]; + [request _web_setHTTPReferrer:core([self webFrame])->loader()->outgoingReferrer()]; + [self loadRequest:request inTarget:nil withNotifyData:nil sendNotification:NO]; + } +} + +- (void)addWindowObservers +{ + ASSERT([self window]); + + NSWindow *theWindow = [self window]; + + NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter]; + [notificationCenter addObserver:self selector:@selector(windowWillClose:) + name:NSWindowWillCloseNotification object:theWindow]; + [notificationCenter addObserver:self selector:@selector(windowBecameKey:) + name:NSWindowDidBecomeKeyNotification object:theWindow]; + [notificationCenter addObserver:self selector:@selector(windowResignedKey:) + name:NSWindowDidResignKeyNotification object:theWindow]; + [notificationCenter addObserver:self selector:@selector(windowDidMiniaturize:) + name:NSWindowDidMiniaturizeNotification object:theWindow]; + [notificationCenter addObserver:self selector:@selector(windowDidDeminiaturize:) + name:NSWindowDidDeminiaturizeNotification object:theWindow]; + + [notificationCenter addObserver:self selector:@selector(loginWindowDidSwitchFromUser:) + name:LoginWindowDidSwitchFromUserNotification object:nil]; + [notificationCenter addObserver:self selector:@selector(loginWindowDidSwitchToUser:) + name:LoginWindowDidSwitchToUserNotification object:nil]; +} + +- (void)removeWindowObservers +{ + NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter]; + [notificationCenter removeObserver:self name:NSWindowWillCloseNotification object:nil]; + [notificationCenter removeObserver:self name:NSWindowDidBecomeKeyNotification object:nil]; + [notificationCenter removeObserver:self name:NSWindowDidResignKeyNotification object:nil]; + [notificationCenter removeObserver:self name:NSWindowDidMiniaturizeNotification object:nil]; + [notificationCenter removeObserver:self name:NSWindowDidDeminiaturizeNotification object:nil]; + [notificationCenter removeObserver:self name:LoginWindowDidSwitchFromUserNotification object:nil]; + [notificationCenter removeObserver:self name:LoginWindowDidSwitchToUserNotification object:nil]; +} + +- (BOOL)start +{ + ASSERT([self currentWindow]); + + if (isStarted) + return YES; + + if (![self canStart]) + return NO; + + ASSERT([self webView]); + + if (![[[self webView] preferences] arePlugInsEnabled]) + return NO; + + // Open the plug-in package so it remains loaded while our plugin uses it + [pluginPackage open]; + + // Initialize drawingModel to an invalid value so that we can detect when the plugin does not specify a drawingModel + drawingModel = (NPDrawingModel)-1; + + // Plug-ins are "windowed" by default. On MacOS, windowed plug-ins share the same window and graphics port as the main + // browser window. Windowless plug-ins are rendered off-screen, then copied into the main browser window. + window.type = NPWindowTypeWindow; + + NPError npErr = [self _createPlugin]; + if (npErr != NPERR_NO_ERROR) { + LOG_ERROR("NPP_New failed with error: %d", npErr); + [self _destroyPlugin]; + [pluginPackage close]; + return NO; + } + + if (drawingModel == (NPDrawingModel)-1) { +#ifndef NP_NO_QUICKDRAW + // Default to QuickDraw if the plugin did not specify a drawing model. + drawingModel = NPDrawingModelQuickDraw; +#else + // QuickDraw is not available, so we can't default to it. We could default to CoreGraphics instead, but + // if the plugin did not specify the CoreGraphics drawing model then it must be one of the old QuickDraw + // plugins. Thus, the plugin is unsupported and should not be started. Destroy it here and bail out. + LOG(Plugins, "Plugin only supports QuickDraw, but QuickDraw is unavailable: %@", pluginPackage); + [self _destroyPlugin]; + [pluginPackage close]; + return NO; +#endif + } + + isStarted = YES; + + [self updateAndSetWindow]; + + if ([self window]) { + [self addWindowObservers]; + if ([[self window] isKeyWindow]) { + [self sendActivateEvent:YES]; + } + [self restartNullEvents]; + } + + [self resetTrackingRect]; + + [self didStart]; + + return YES; +} + +- (void)stop +{ + // If we're already calling a plug-in function, do not call NPP_Destroy(). The plug-in function we are calling + // may assume that its instance->pdata, or other memory freed by NPP_Destroy(), is valid and unchanged until said + // plugin-function returns. + // See . + if (pluginFunctionCallDepth > 0) { + shouldStopSoon = YES; + return; + } + + [self removeTrackingRect]; + + if (!isStarted) + return; + + isStarted = NO; + // To stop active streams it's necessary to invoke makeObjectsPerformSelector on a copy + // of streams. This is because calling -[WebNetscapePluginStream stop] also has the side effect + // of removing a stream from this collection. + NSArray *streamsCopy = [streams copy]; + [streamsCopy makeObjectsPerformSelector:@selector(stop)]; + [streamsCopy release]; + + // Stop the null events + [self stopNullEvents]; + + // Stop notifications and callbacks. + [self removeWindowObservers]; + [[pendingFrameLoads allKeys] makeObjectsPerformSelector:@selector(_setInternalLoadDelegate:) withObject:nil]; + [NSObject cancelPreviousPerformRequestsWithTarget:self]; + + // Setting the window type to 0 ensures that NPP_SetWindow will be called if the plug-in is restarted. + lastSetWindow.type = (NPWindowType)0; + + [self _destroyPlugin]; + [pluginPackage close]; + + // We usually remove the key event handler in resignFirstResponder but it is possible that resignFirstResponder + // may never get called so we can't completely rely on it. + [self removeKeyEventHandler]; + + if (drawingModel == NPDrawingModelOpenGL) + [self _destroyAGLContext]; +} + +- (BOOL)isStarted +{ + return isStarted; +} + +- (WebDataSource *)dataSource +{ + WebFrame *webFrame = kit(core(element)->document()->frame()); + return [webFrame _dataSource]; +} + +- (WebFrame *)webFrame +{ + return [[self dataSource] webFrame]; +} + +- (WebView *)webView +{ + return [[self webFrame] webView]; +} + +- (NSWindow *)currentWindow +{ + return [self window] ? [self window] : [[self webView] hostWindow]; +} + +- (NPP)plugin +{ + return plugin; +} + +- (WebNetscapePluginPackage *)pluginPackage +{ + return pluginPackage; +} + +- (void)setPluginPackage:(WebNetscapePluginPackage *)thePluginPackage; +{ + [thePluginPackage retain]; + [pluginPackage release]; + pluginPackage = thePluginPackage; + + NPP_New = [pluginPackage NPP_New]; + NPP_Destroy = [pluginPackage NPP_Destroy]; + NPP_SetWindow = [pluginPackage NPP_SetWindow]; + NPP_NewStream = [pluginPackage NPP_NewStream]; + NPP_WriteReady = [pluginPackage NPP_WriteReady]; + NPP_Write = [pluginPackage NPP_Write]; + NPP_StreamAsFile = [pluginPackage NPP_StreamAsFile]; + NPP_DestroyStream = [pluginPackage NPP_DestroyStream]; + NPP_HandleEvent = [pluginPackage NPP_HandleEvent]; + NPP_URLNotify = [pluginPackage NPP_URLNotify]; + NPP_GetValue = [pluginPackage NPP_GetValue]; + NPP_SetValue = [pluginPackage NPP_SetValue]; + NPP_Print = [pluginPackage NPP_Print]; +} + +- (void)setMIMEType:(NSString *)theMIMEType +{ + NSString *type = [theMIMEType copy]; + [MIMEType release]; + MIMEType = type; +} + +- (void)setBaseURL:(NSURL *)theBaseURL +{ + [theBaseURL retain]; + [baseURL release]; + baseURL = theBaseURL; +} + +- (void)setAttributeKeys:(NSArray *)keys andValues:(NSArray *)values; +{ + ASSERT([keys count] == [values count]); + + // Convert the attributes to 2 C string arrays. + // These arrays are passed to NPP_New, but the strings need to be + // modifiable and live the entire life of the plugin. + + // The Java plug-in requires the first argument to be the base URL + if ([MIMEType isEqualToString:@"application/x-java-applet"]) { + cAttributes = (char **)malloc(([keys count] + 1) * sizeof(char *)); + cValues = (char **)malloc(([values count] + 1) * sizeof(char *)); + cAttributes[0] = strdup("DOCBASE"); + cValues[0] = strdup([baseURL _web_URLCString]); + argsCount++; + } else { + cAttributes = (char **)malloc([keys count] * sizeof(char *)); + cValues = (char **)malloc([values count] * sizeof(char *)); + } + + BOOL isWMP = [[[pluginPackage bundle] bundleIdentifier] isEqualToString:@"com.microsoft.WMP.defaultplugin"]; + + unsigned i; + unsigned count = [keys count]; + for (i = 0; i < count; i++) { + NSString *key = [keys objectAtIndex:i]; + NSString *value = [values objectAtIndex:i]; + if ([key _webkit_isCaseInsensitiveEqualToString:@"height"]) { + specifiedHeight = [value intValue]; + } else if ([key _webkit_isCaseInsensitiveEqualToString:@"width"]) { + specifiedWidth = [value intValue]; + } + // Avoid Window Media Player crash when these attributes are present. + if (isWMP && ([key _webkit_isCaseInsensitiveEqualToString:@"SAMIStyle"] || [key _webkit_isCaseInsensitiveEqualToString:@"SAMILang"])) { + continue; + } + cAttributes[argsCount] = strdup([key UTF8String]); + cValues[argsCount] = strdup([value UTF8String]); + LOG(Plugins, "%@ = %@", key, value); + argsCount++; + } +} + +- (void)setMode:(int)theMode +{ + mode = theMode; +} + +#pragma mark NSVIEW + +- (id)initWithFrame:(NSRect)frame + pluginPackage:(WebNetscapePluginPackage *)thePluginPackage + URL:(NSURL *)theURL + baseURL:(NSURL *)theBaseURL + MIMEType:(NSString *)MIME + attributeKeys:(NSArray *)keys + attributeValues:(NSArray *)values + loadManually:(BOOL)loadManually + DOMElement:(DOMElement *)anElement +{ + [super initWithFrame:frame]; + + streams = [[NSMutableArray alloc] init]; + pendingFrameLoads = [[NSMutableDictionary alloc] init]; + + // load the plug-in if it is not already loaded + if (![thePluginPackage load]) { + [self release]; + return nil; + } + [self setPluginPackage:thePluginPackage]; + + element = [anElement retain]; + sourceURL = [theURL retain]; + + [self setMIMEType:MIME]; + [self setBaseURL:theBaseURL]; + [self setAttributeKeys:keys andValues:values]; + if (loadManually) + [self setMode:NP_FULL]; + else + [self setMode:NP_EMBED]; + + _loadManually = loadManually; + + return self; +} + +- (id)initWithFrame:(NSRect)frame +{ + ASSERT_NOT_REACHED(); + return nil; +} + +- (void)fini +{ +#ifndef NP_NO_QUICKDRAW + if (offscreenGWorld) + DisposeGWorld(offscreenGWorld); +#endif + + unsigned i; + for (i = 0; i < argsCount; i++) { + free(cAttributes[i]); + free(cValues[i]); + } + free(cAttributes); + free(cValues); +} + +- (void)disconnectStream:(WebBaseNetscapePluginStream*)stream +{ + [streams removeObjectIdenticalTo:stream]; +} + +- (void)dealloc +{ + ASSERT(!isStarted); + + [sourceURL release]; + [_manualStream release]; + [_error release]; + + [pluginPackage release]; + [streams release]; + [MIMEType release]; + [baseURL release]; + [pendingFrameLoads release]; + [element release]; + + ASSERT(!plugin); + ASSERT(!aglWindow); + ASSERT(!aglContext); + + [self fini]; + + [super dealloc]; +} + +- (void)finalize +{ + ASSERT_MAIN_THREAD(); + ASSERT(!isStarted); + + [self fini]; + + [super finalize]; +} + +- (void)drawRect:(NSRect)rect +{ + if (!isStarted) { + return; + } + + if ([NSGraphicsContext currentContextDrawingToScreen]) + [self sendUpdateEvent]; + else { + NSBitmapImageRep *printedPluginBitmap = [self _printedPluginBitmap]; + if (printedPluginBitmap) { + // Flip the bitmap before drawing because the QuickDraw port is flipped relative + // to this view. + CGContextRef cgContext = (CGContextRef)[[NSGraphicsContext currentContext] graphicsPort]; + CGContextSaveGState(cgContext); + NSRect bounds = [self bounds]; + CGContextTranslateCTM(cgContext, 0.0f, NSHeight(bounds)); + CGContextScaleCTM(cgContext, 1.0f, -1.0f); + [printedPluginBitmap drawInRect:bounds]; + CGContextRestoreGState(cgContext); + } + } + + // If this is a windowless OpenGL plugin, blit its contents back into this view. The plug-in just drew into the offscreen context. + if (drawingModel == NPDrawingModelOpenGL && window.type == NPWindowTypeDrawable) { + NSImage *aglOffscreenImage = [self _aglOffscreenImageForDrawingInRect:rect]; + if (aglOffscreenImage) { + // Flip the context before drawing because the CGL context is flipped relative to this view. + CGContextRef cgContext = (CGContextRef)[[NSGraphicsContext currentContext] graphicsPort]; + CGContextSaveGState(cgContext); + NSRect bounds = [self bounds]; + CGContextTranslateCTM(cgContext, 0.0f, NSHeight(bounds)); + CGContextScaleCTM(cgContext, 1.0f, -1.0f); + + // Copy 'rect' from the offscreen buffer to this view (the flip above makes this sort of tricky) + NSRect flippedRect = rect; + flippedRect.origin.y = NSMaxY(bounds) - NSMaxY(flippedRect); + [aglOffscreenImage drawInRect:flippedRect fromRect:flippedRect operation:NSCompositeSourceOver fraction:1.0f]; + CGContextRestoreGState(cgContext); + } + } +} + +- (BOOL)isFlipped +{ + return YES; +} + +- (void)renewGState +{ + [super renewGState]; + + // -renewGState is called whenever the view's geometry changes. It's a little hacky to override this method, but + // much safer than walking up the view hierarchy and observing frame/bounds changed notifications, since you don't + // have to track subsequent changes to the view hierarchy and add/remove notification observers. + // NSOpenGLView uses the exact same technique to reshape its OpenGL surface. + [self _viewHasMoved]; +} + +#ifndef NP_NO_QUICKDRAW +-(void)tellQuickTimeToChill +{ + ASSERT(drawingModel == NPDrawingModelQuickDraw); + + // Make a call to the secret QuickDraw API that makes QuickTime calm down. + WindowRef windowRef = (WindowRef)[[self window] windowRef]; + if (!windowRef) { + return; + } + CGrafPtr port = GetWindowPort(windowRef); + ::Rect bounds; + GetPortBounds(port, &bounds); + WKCallDrawingNotification(port, &bounds); +} +#endif /* NP_NO_QUICKDRAW */ + +- (void)viewWillMoveToWindow:(NSWindow *)newWindow +{ +#ifndef NP_NO_QUICKDRAW + if (drawingModel == NPDrawingModelQuickDraw) + [self tellQuickTimeToChill]; +#endif + + // We must remove the tracking rect before we move to the new window. + // Once we move to the new window, it will be too late. + [self removeTrackingRect]; + [self removeWindowObservers]; + + // Workaround for: resignFirstResponder is not sent to first responder view when it is removed from the window + [self setHasFocus:NO]; + + if (!newWindow) { + // Hide the AGL child window + if (drawingModel == NPDrawingModelOpenGL) + [self _hideAGLWindow]; + + if ([[self webView] hostWindow]) { + // View will be moved out of the actual window but it still has a host window. + [self stopNullEvents]; + } else { + // View will have no associated windows. + [self stop]; + + // Stop observing WebPreferencesChangedNotification -- we only need to observe this when installed in the view hierarchy. + // When not in the view hierarchy, -viewWillMoveToWindow: and -viewDidMoveToWindow will start/stop the plugin as needed. + [[NSNotificationCenter defaultCenter] removeObserver:self name:WebPreferencesChangedNotification object:nil]; + } + } +} + +- (void)viewWillMoveToSuperview:(NSView *)newSuperview +{ + if (!newSuperview) { + // Stop the plug-in when it is removed from its superview. It is not sufficient to do this in -viewWillMoveToWindow:nil, because + // the WebView might still has a hostWindow at that point, which prevents the plug-in from being destroyed. + // There is no need to start the plug-in when moving into a superview. -viewDidMoveToWindow takes care of that. + [self stop]; + + // Stop observing WebPreferencesChangedNotification -- we only need to observe this when installed in the view hierarchy. + // When not in the view hierarchy, -viewWillMoveToWindow: and -viewDidMoveToWindow will start/stop the plugin as needed. + [[NSNotificationCenter defaultCenter] removeObserver:self name:WebPreferencesChangedNotification object:nil]; + } +} + +- (void)viewDidMoveToWindow +{ + [self resetTrackingRect]; + + if ([self window]) { + // While in the view hierarchy, observe WebPreferencesChangedNotification so that we can start/stop depending + // on whether plugins are enabled. + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(preferencesHaveChanged:) + name:WebPreferencesChangedNotification + object:nil]; + + // View moved to an actual window. Start it if not already started. + [self start]; + [self restartNullEvents]; + [self addWindowObservers]; + } else if ([[self webView] hostWindow]) { + // View moved out of an actual window, but still has a host window. + // Call setWindow to explicitly "clip out" the plug-in from sight. + // FIXME: It would be nice to do this where we call stopNullEvents in viewWillMoveToWindow. + [self updateAndSetWindow]; + } +} + +- (void)viewWillMoveToHostWindow:(NSWindow *)hostWindow +{ + if (!hostWindow && ![self window]) { + // View will have no associated windows. + [self stop]; + + // Remove WebPreferencesChangedNotification observer -- we will observe once again when we move back into the window + [[NSNotificationCenter defaultCenter] removeObserver:self name:WebPreferencesChangedNotification object:nil]; + } +} + +- (void)viewDidMoveToHostWindow +{ + if ([[self webView] hostWindow]) { + // View now has an associated window. Start it if not already started. + [self start]; + } +} + +#pragma mark NOTIFICATIONS + +- (void)windowWillClose:(NSNotification *)notification +{ + [self stop]; +} + +- (void)windowBecameKey:(NSNotification *)notification +{ + [self sendActivateEvent:YES]; + [self setNeedsDisplay:YES]; + [self restartNullEvents]; + SetUserFocusWindow((WindowRef)[[self window] windowRef]); +} + +- (void)windowResignedKey:(NSNotification *)notification +{ + [self sendActivateEvent:NO]; + [self setNeedsDisplay:YES]; + [self restartNullEvents]; +} + +- (void)windowDidMiniaturize:(NSNotification *)notification +{ + [self stopNullEvents]; +} + +- (void)windowDidDeminiaturize:(NSNotification *)notification +{ + [self restartNullEvents]; +} + +- (void)loginWindowDidSwitchFromUser:(NSNotification *)notification +{ + [self stopNullEvents]; +} + +-(void)loginWindowDidSwitchToUser:(NSNotification *)notification +{ + [self restartNullEvents]; +} + +- (void)preferencesHaveChanged:(NSNotification *)notification +{ + WebPreferences *preferences = [[self webView] preferences]; + BOOL arePlugInsEnabled = [preferences arePlugInsEnabled]; + + if ([notification object] == preferences && isStarted != arePlugInsEnabled) { + if (arePlugInsEnabled) { + if ([self currentWindow]) { + [self start]; + } + } else { + [self stop]; + [self setNeedsDisplay:YES]; + } + } +} + +- (NPObject *)createPluginScriptableObject +{ + if (!NPP_GetValue || ![self isStarted]) + return NULL; + + NPObject *value = NULL; + NPError error; + [self willCallPlugInFunction]; + { + KJS::JSLock::DropAllLocks dropAllLocks; + error = NPP_GetValue(plugin, NPPVpluginScriptableNPObject, &value); + } + [self didCallPlugInFunction]; + if (error != NPERR_NO_ERROR) + return NULL; + + return value; +} + +- (void)willCallPlugInFunction +{ + ASSERT(plugin); + + // Could try to prevent infinite recursion here, but it's probably not worth the effort. + pluginFunctionCallDepth++; +} + +- (void)didCallPlugInFunction +{ + ASSERT(pluginFunctionCallDepth > 0); + pluginFunctionCallDepth--; + + // If -stop was called while we were calling into a plug-in function, and we're no longer + // inside a plug-in function, stop now. + if (pluginFunctionCallDepth == 0 && shouldStopSoon) { + shouldStopSoon = NO; + [self stop]; + } +} + +-(void)pluginView:(NSView *)pluginView receivedResponse:(NSURLResponse *)response +{ + ASSERT(_loadManually); + ASSERT(!_manualStream); + + _manualStream = [[WebNetscapePluginStream alloc] initWithFrameLoader:core([self webFrame])->loader()]; +} + +- (void)pluginView:(NSView *)pluginView receivedData:(NSData *)data +{ + ASSERT(_loadManually); + ASSERT(_manualStream); + + _dataLengthReceived += [data length]; + + if (![self isStarted]) + return; + + if ([_manualStream plugin] == NULL) { + [_manualStream setRequestURL:[[[self dataSource] request] URL]]; + [_manualStream setPlugin:[self plugin]]; + ASSERT([_manualStream plugin]); + [_manualStream startStreamWithResponse:[[self dataSource] response]]; + } + + if ([_manualStream plugin]) + [_manualStream receivedData:data]; +} + +- (void)pluginView:(NSView *)pluginView receivedError:(NSError *)error +{ + ASSERT(_loadManually); + + [error retain]; + [_error release]; + _error = error; + + if (![self isStarted]) { + return; + } + + [_manualStream destroyStreamWithError:error]; +} + +- (void)pluginViewFinishedLoading:(NSView *)pluginView +{ + ASSERT(_loadManually); + ASSERT(_manualStream); + + if ([self isStarted]) + [_manualStream finishedLoadingWithData:[[self dataSource] data]]; +} + +@end + +@implementation WebBaseNetscapePluginView (WebNPPCallbacks) + +- (NSMutableURLRequest *)requestWithURLCString:(const char *)URLCString +{ + if (!URLCString) + return nil; + + CFStringRef string = CFStringCreateWithCString(kCFAllocatorDefault, URLCString, kCFStringEncodingISOLatin1); + ASSERT(string); // All strings should be representable in ISO Latin 1 + + NSString *URLString = [(NSString *)string _web_stringByStrippingReturnCharacters]; + NSURL *URL = [NSURL _web_URLWithDataAsString:URLString relativeToURL:baseURL]; + CFRelease(string); + if (!URL) + return nil; + + NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:URL]; + Frame* frame = core([self webFrame]); + if (!frame) + return nil; + [request _web_setHTTPReferrer:frame->loader()->outgoingReferrer()]; + return request; +} + +- (void)evaluateJavaScriptPluginRequest:(WebPluginRequest *)JSPluginRequest +{ + // FIXME: Is this isStarted check needed here? evaluateJavaScriptPluginRequest should not be called + // if we are stopped since this method is called after a delay and we call + // cancelPreviousPerformRequestsWithTarget inside of stop. + if (!isStarted) { + return; + } + + NSURL *URL = [[JSPluginRequest request] URL]; + NSString *JSString = [URL _webkit_scriptIfJavaScriptURL]; + ASSERT(JSString); + + NSString *result = [[[self webFrame] _bridge] stringByEvaluatingJavaScriptFromString:JSString forceUserGesture:[JSPluginRequest isCurrentEventUserGesture]]; + + // Don't continue if stringByEvaluatingJavaScriptFromString caused the plug-in to stop. + if (!isStarted) { + return; + } + + if ([JSPluginRequest frameName] != nil) { + // FIXME: If the result is a string, we probably want to put that string into the frame. + if ([JSPluginRequest sendNotification]) { + [self willCallPlugInFunction]; + { + KJS::JSLock::DropAllLocks dropAllLocks; + NPP_URLNotify(plugin, [URL _web_URLCString], NPRES_DONE, [JSPluginRequest notifyData]); + } + [self didCallPlugInFunction]; + } + } else if ([result length] > 0) { + // Don't call NPP_NewStream and other stream methods if there is no JS result to deliver. This is what Mozilla does. + NSData *JSData = [result dataUsingEncoding:NSUTF8StringEncoding]; + WebBaseNetscapePluginStream *stream = [[WebBaseNetscapePluginStream alloc] initWithRequestURL:URL + plugin:plugin + notifyData:[JSPluginRequest notifyData] + sendNotification:[JSPluginRequest sendNotification]]; + [stream startStreamResponseURL:URL + expectedContentLength:[JSData length] + lastModifiedDate:nil + MIMEType:@"text/plain" + headers:nil]; + [stream receivedData:JSData]; + [stream finishedLoadingWithData:JSData]; + [stream release]; + } +} + +- (void)webFrame:(WebFrame *)webFrame didFinishLoadWithReason:(NPReason)reason +{ + ASSERT(isStarted); + + WebPluginRequest *pluginRequest = [pendingFrameLoads objectForKey:webFrame]; + ASSERT(pluginRequest != nil); + ASSERT([pluginRequest sendNotification]); + + [self willCallPlugInFunction]; + { + KJS::JSLock::DropAllLocks dropAllLocks; + NPP_URLNotify(plugin, [[[pluginRequest request] URL] _web_URLCString], reason, [pluginRequest notifyData]); + } + [self didCallPlugInFunction]; + + [pendingFrameLoads removeObjectForKey:webFrame]; + [webFrame _setInternalLoadDelegate:nil]; +} + +- (void)webFrame:(WebFrame *)webFrame didFinishLoadWithError:(NSError *)error +{ + NPReason reason = NPRES_DONE; + if (error != nil) { + reason = [WebBaseNetscapePluginStream reasonForError:error]; + } + [self webFrame:webFrame didFinishLoadWithReason:reason]; +} + +- (void)loadPluginRequest:(WebPluginRequest *)pluginRequest +{ + NSURLRequest *request = [pluginRequest request]; + NSString *frameName = [pluginRequest frameName]; + WebFrame *frame = nil; + + NSURL *URL = [request URL]; + NSString *JSString = [URL _webkit_scriptIfJavaScriptURL]; + + ASSERT(frameName || JSString); + + if (frameName) { + // FIXME - need to get rid of this window creation which + // bypasses normal targeted link handling + frame = [[self webFrame] findFrameNamed:frameName]; + + if (frame == nil) { + WebView *currentWebView = [self webView]; + WebView *newWebView = CallUIDelegate(currentWebView, @selector(webView:createWebViewWithRequest:), nil); + + if (!newWebView) { + if ([pluginRequest sendNotification]) { + [self willCallPlugInFunction]; + { + KJS::JSLock::DropAllLocks dropAllLocks; + NPP_URLNotify(plugin, [[[pluginRequest request] URL] _web_URLCString], NPERR_GENERIC_ERROR, [pluginRequest notifyData]); + } + [self didCallPlugInFunction]; + } + return; + } + + frame = [newWebView mainFrame]; + core(frame)->tree()->setName(frameName); + [[newWebView _UIDelegateForwarder] webViewShow:newWebView]; + } + } + + if (JSString) { + ASSERT(frame == nil || [self webFrame] == frame); + [self evaluateJavaScriptPluginRequest:pluginRequest]; + } else { + [frame loadRequest:request]; + if ([pluginRequest sendNotification]) { + // Check if another plug-in view or even this view is waiting for the frame to load. + // If it is, tell it that the load was cancelled because it will be anyway. + WebBaseNetscapePluginView *view = [frame _internalLoadDelegate]; + if (view != nil) { + ASSERT([view isKindOfClass:[WebBaseNetscapePluginView class]]); + [view webFrame:frame didFinishLoadWithReason:NPRES_USER_BREAK]; + } + [pendingFrameLoads _webkit_setObject:pluginRequest forUncopiedKey:frame]; + [frame _setInternalLoadDelegate:self]; + } + } +} + +- (NPError)loadRequest:(NSMutableURLRequest *)request inTarget:(const char *)cTarget withNotifyData:(void *)notifyData sendNotification:(BOOL)sendNotification +{ + NSURL *URL = [request URL]; + + if (!URL) + return NPERR_INVALID_URL; + + NSString *target = nil; + if (cTarget) { + // Find the frame given the target string. + target = (NSString *)CFStringCreateWithCString(kCFAllocatorDefault, cTarget, kCFStringEncodingWindowsLatin1); + } + WebFrame *frame = [self webFrame]; + + // don't let a plugin start any loads if it is no longer part of a document that is being + // displayed unless the loads are in the same frame as the plugin. + if ([[self dataSource] _documentLoader] != [[self webFrame] _frameLoader]->activeDocumentLoader() && + (!cTarget || [frame findFrameNamed:target] != frame)) { + if (target) + CFRelease(target); + return NPERR_GENERIC_ERROR; + } + + NSString *JSString = [URL _webkit_scriptIfJavaScriptURL]; + if (JSString != nil) { + if (![[[self webView] preferences] isJavaScriptEnabled]) { + // Return NPERR_GENERIC_ERROR if JS is disabled. This is what Mozilla does. + return NPERR_GENERIC_ERROR; + } else if (cTarget == NULL && mode == NP_FULL) { + // Don't allow a JavaScript request from a standalone plug-in that is self-targetted + // because this can cause the user to be redirected to a blank page (3424039). + return NPERR_INVALID_PARAM; + } + } + + if (cTarget || JSString) { + // Make when targetting a frame or evaluating a JS string, perform the request after a delay because we don't + // want to potentially kill the plug-in inside of its URL request. + + if (JSString != nil && target != nil && [frame findFrameNamed:target] != frame) { + // For security reasons, only allow JS requests to be made on the frame that contains the plug-in. + CFRelease(target); + return NPERR_INVALID_PARAM; + } + + WebPluginRequest *pluginRequest = [[WebPluginRequest alloc] initWithRequest:request frameName:target notifyData:notifyData sendNotification:sendNotification didStartFromUserGesture:currentEventIsUserGesture]; + [self performSelector:@selector(loadPluginRequest:) withObject:pluginRequest afterDelay:0]; + [pluginRequest release]; + if (target) + CFRelease(target); + } else { + WebNetscapePluginStream *stream = [[WebNetscapePluginStream alloc] initWithRequest:request + plugin:plugin + notifyData:notifyData + sendNotification:sendNotification]; + if (!stream) + return NPERR_INVALID_URL; + + [streams addObject:stream]; + [stream start]; + [stream release]; + } + + return NPERR_NO_ERROR; +} + +-(NPError)getURLNotify:(const char *)URLCString target:(const char *)cTarget notifyData:(void *)notifyData +{ + LOG(Plugins, "NPN_GetURLNotify: %s target: %s", URLCString, cTarget); + + NSMutableURLRequest *request = [self requestWithURLCString:URLCString]; + return [self loadRequest:request inTarget:cTarget withNotifyData:notifyData sendNotification:YES]; +} + +-(NPError)getURL:(const char *)URLCString target:(const char *)cTarget +{ + LOG(Plugins, "NPN_GetURL: %s target: %s", URLCString, cTarget); + + NSMutableURLRequest *request = [self requestWithURLCString:URLCString]; + return [self loadRequest:request inTarget:cTarget withNotifyData:NULL sendNotification:NO]; +} + +- (NPError)_postURL:(const char *)URLCString + target:(const char *)target + len:(UInt32)len + buf:(const char *)buf + file:(NPBool)file + notifyData:(void *)notifyData + sendNotification:(BOOL)sendNotification + allowHeaders:(BOOL)allowHeaders +{ + if (!URLCString || !len || !buf) { + return NPERR_INVALID_PARAM; + } + + NSData *postData = nil; + + if (file) { + // If we're posting a file, buf is either a file URL or a path to the file. + NSString *bufString = (NSString *)CFStringCreateWithCString(kCFAllocatorDefault, buf, kCFStringEncodingWindowsLatin1); + if (!bufString) { + return NPERR_INVALID_PARAM; + } + NSURL *fileURL = [NSURL _web_URLWithDataAsString:bufString]; + NSString *path; + if ([fileURL isFileURL]) { + path = [fileURL path]; + } else { + path = bufString; + } + postData = [NSData dataWithContentsOfFile:[path _webkit_fixedCarbonPOSIXPath]]; + CFRelease(bufString); + if (!postData) { + return NPERR_FILE_NOT_FOUND; + } + } else { + postData = [NSData dataWithBytes:buf length:len]; + } + + if ([postData length] == 0) { + return NPERR_INVALID_PARAM; + } + + NSMutableURLRequest *request = [self requestWithURLCString:URLCString]; + [request setHTTPMethod:@"POST"]; + + if (allowHeaders) { + if ([postData _web_startsWithBlankLine]) { + postData = [postData subdataWithRange:NSMakeRange(1, [postData length] - 1)]; + } else { + NSInteger location = [postData _web_locationAfterFirstBlankLine]; + if (location != NSNotFound) { + // If the blank line is somewhere in the middle of postData, everything before is the header. + NSData *headerData = [postData subdataWithRange:NSMakeRange(0, location)]; + NSMutableDictionary *header = [headerData _webkit_parseRFC822HeaderFields]; + unsigned dataLength = [postData length] - location; + + // Sometimes plugins like to set Content-Length themselves when they post, + // but WebFoundation does not like that. So we will remove the header + // and instead truncate the data to the requested length. + NSString *contentLength = [header objectForKey:@"Content-Length"]; + + if (contentLength != nil) + dataLength = MIN((unsigned)[contentLength intValue], dataLength); + [header removeObjectForKey:@"Content-Length"]; + + if ([header count] > 0) { + [request setAllHTTPHeaderFields:header]; + } + // Everything after the blank line is the actual content of the POST. + postData = [postData subdataWithRange:NSMakeRange(location, dataLength)]; + + } + } + if ([postData length] == 0) { + return NPERR_INVALID_PARAM; + } + } + + // Plug-ins expect to receive uncached data when doing a POST (3347134). + [request setCachePolicy:NSURLRequestReloadIgnoringCacheData]; + [request setHTTPBody:postData]; + + return [self loadRequest:request inTarget:target withNotifyData:notifyData sendNotification:sendNotification]; +} + +- (NPError)postURLNotify:(const char *)URLCString + target:(const char *)target + len:(UInt32)len + buf:(const char *)buf + file:(NPBool)file + notifyData:(void *)notifyData +{ + LOG(Plugins, "NPN_PostURLNotify: %s", URLCString); + return [self _postURL:URLCString target:target len:len buf:buf file:file notifyData:notifyData sendNotification:YES allowHeaders:YES]; +} + +-(NPError)postURL:(const char *)URLCString + target:(const char *)target + len:(UInt32)len + buf:(const char *)buf + file:(NPBool)file +{ + LOG(Plugins, "NPN_PostURL: %s", URLCString); + // As documented, only allow headers to be specified via NPP_PostURL when using a file. + return [self _postURL:URLCString target:target len:len buf:buf file:file notifyData:NULL sendNotification:NO allowHeaders:file]; +} + +-(NPError)newStream:(NPMIMEType)type target:(const char *)target stream:(NPStream**)stream +{ + LOG(Plugins, "NPN_NewStream"); + return NPERR_GENERIC_ERROR; +} + +-(NPError)write:(NPStream*)stream len:(SInt32)len buffer:(void *)buffer +{ + LOG(Plugins, "NPN_Write"); + return NPERR_GENERIC_ERROR; +} + +-(NPError)destroyStream:(NPStream*)stream reason:(NPReason)reason +{ + LOG(Plugins, "NPN_DestroyStream"); + // This function does a sanity check to ensure that the NPStream provided actually + // belongs to the plug-in that provided it, which fixes a crash in the DivX + // plug-in: | http://bugs.webkit.org/show_bug.cgi?id=13203 + if (!stream || [WebBaseNetscapePluginStream ownerForStream:stream] != plugin) { + LOG(Plugins, "Invalid NPStream passed to NPN_DestroyStream: %p", stream); + return NPERR_INVALID_INSTANCE_ERROR; + } + + WebBaseNetscapePluginStream *browserStream = static_cast(stream->ndata); + [browserStream cancelLoadAndDestroyStreamWithError:[browserStream errorForReason:reason]]; + + return NPERR_NO_ERROR; +} + +- (const char *)userAgent +{ + return [[[self webView] userAgentForURL:baseURL] UTF8String]; +} + +-(void)status:(const char *)message +{ + if (!message) { + LOG_ERROR("NPN_Status passed a NULL status message"); + return; + } + + CFStringRef status = CFStringCreateWithCString(NULL, message, kCFStringEncodingUTF8); + if (!status) { + LOG_ERROR("NPN_Status: the message was not valid UTF-8"); + return; + } + + LOG(Plugins, "NPN_Status: %@", status); + WebView *wv = [self webView]; + [[wv _UIDelegateForwarder] webView:wv setStatusText:(NSString *)status]; + CFRelease(status); +} + +-(void)invalidateRect:(NPRect *)invalidRect +{ + LOG(Plugins, "NPN_InvalidateRect"); + [self setNeedsDisplayInRect:NSMakeRect(invalidRect->left, invalidRect->top, + (float)invalidRect->right - invalidRect->left, (float)invalidRect->bottom - invalidRect->top)]; +} + +-(bool)isOpaque +{ + return YES; +} + +- (void)invalidateRegion:(NPRegion)invalidRegion +{ + LOG(Plugins, "NPN_InvalidateRegion"); + NSRect invalidRect = NSZeroRect; + switch (drawingModel) { +#ifndef NP_NO_QUICKDRAW + case NPDrawingModelQuickDraw: + { + ::Rect qdRect; + GetRegionBounds((NPQDRegion)invalidRegion, &qdRect); + invalidRect = NSMakeRect(qdRect.left, qdRect.top, qdRect.right - qdRect.left, qdRect.bottom - qdRect.top); + } + break; +#endif /* NP_NO_QUICKDRAW */ + + case NPDrawingModelCoreGraphics: + case NPDrawingModelOpenGL: + { + CGRect cgRect = CGPathGetBoundingBox((NPCGRegion)invalidRegion); + invalidRect = *(NSRect *)&cgRect; + } + break; + + default: + ASSERT_NOT_REACHED(); + break; + } + + [self setNeedsDisplayInRect:invalidRect]; +} + +-(void)forceRedraw +{ + LOG(Plugins, "forceRedraw"); + [self setNeedsDisplay:YES]; + [[self window] displayIfNeeded]; +} + +- (NPError)getVariable:(NPNVariable)variable value:(void *)value +{ + switch (variable) { + case NPNVWindowNPObject: + { + Frame* frame = core([self webFrame]); + NPObject* windowScriptObject = frame ? frame->windowScriptNPObject() : 0; + + // Return value is expected to be retained, as described here: + if (windowScriptObject) + _NPN_RetainObject(windowScriptObject); + + void **v = (void **)value; + *v = windowScriptObject; + + return NPERR_NO_ERROR; + } + + case NPNVPluginElementNPObject: + { + if (!element) + return NPERR_GENERIC_ERROR; + + NPObject *plugInScriptObject = (NPObject *)[element _NPObject]; + + // Return value is expected to be retained, as described here: + if (plugInScriptObject) + _NPN_RetainObject(plugInScriptObject); + + void **v = (void **)value; + *v = plugInScriptObject; + + return NPERR_NO_ERROR; + } + + case NPNVpluginDrawingModel: + { + *(NPDrawingModel *)value = drawingModel; + return NPERR_NO_ERROR; + } + +#ifndef NP_NO_QUICKDRAW + case NPNVsupportsQuickDrawBool: + { + *(NPBool *)value = TRUE; + return NPERR_NO_ERROR; + } +#endif /* NP_NO_QUICKDRAW */ + + case NPNVsupportsCoreGraphicsBool: + { + *(NPBool *)value = TRUE; + return NPERR_NO_ERROR; + } + + case NPNVsupportsOpenGLBool: + { + *(NPBool *)value = TRUE; + return NPERR_NO_ERROR; + } + + default: + break; + } + + return NPERR_GENERIC_ERROR; +} + +- (NPError)setVariable:(NPPVariable)variable value:(void *)value +{ + switch (variable) { + case NPPVpluginWindowBool: + { + NPWindowType newWindowType = (value ? NPWindowTypeWindow : NPWindowTypeDrawable); + + // Redisplay if window type is changing (some drawing models can only have their windows set while updating). + if (newWindowType != window.type) + [self setNeedsDisplay:YES]; + + window.type = newWindowType; + } + + case NPPVpluginTransparentBool: + { + BOOL newTransparent = (value != 0); + + // Redisplay if transparency is changing + if (isTransparent != newTransparent) + [self setNeedsDisplay:YES]; + + isTransparent = newTransparent; + + return NPERR_NO_ERROR; + } + + case NPNVpluginDrawingModel: + { + // Can only set drawing model inside NPP_New() + if (self != [[self class] currentPluginView]) + return NPERR_GENERIC_ERROR; + + // Check for valid, supported drawing model + NPDrawingModel newDrawingModel = (NPDrawingModel)(uintptr_t)value; + switch (newDrawingModel) { + // Supported drawing models: +#ifndef NP_NO_QUICKDRAW + case NPDrawingModelQuickDraw: +#endif + case NPDrawingModelCoreGraphics: + case NPDrawingModelOpenGL: + drawingModel = newDrawingModel; + return NPERR_NO_ERROR; + + // Unsupported (or unknown) drawing models: + default: + LOG(Plugins, "Plugin %@ uses unsupported drawing model: %d", pluginPackage, drawingModel); + return NPERR_GENERIC_ERROR; + } + } + + default: + return NPERR_GENERIC_ERROR; + } +} + +@end + +@implementation WebPluginRequest + +- (id)initWithRequest:(NSURLRequest *)request frameName:(NSString *)frameName notifyData:(void *)notifyData sendNotification:(BOOL)sendNotification didStartFromUserGesture:(BOOL)currentEventIsUserGesture +{ + [super init]; + _didStartFromUserGesture = currentEventIsUserGesture; + _request = [request retain]; + _frameName = [frameName retain]; + _notifyData = notifyData; + _sendNotification = sendNotification; + return self; +} + +- (void)dealloc +{ + [_request release]; + [_frameName release]; + [super dealloc]; +} + +- (NSURLRequest *)request +{ + return _request; +} + +- (NSString *)frameName +{ + return _frameName; +} + +- (BOOL)isCurrentEventUserGesture +{ + return _didStartFromUserGesture; +} + +- (BOOL)sendNotification +{ + return _sendNotification; +} + +- (void *)notifyData +{ + return _notifyData; +} + +@end + +@implementation WebBaseNetscapePluginView (Internal) + +- (NPError)_createPlugin +{ + plugin = (NPP)calloc(1, sizeof(NPP_t)); + plugin->ndata = self; + + ASSERT(NPP_New); + + // NPN_New(), which creates the plug-in instance, should never be called while calling a plug-in function for that instance. + ASSERT(pluginFunctionCallDepth == 0); + + [[self class] setCurrentPluginView:self]; + NPError npErr = NPP_New((char *)[MIMEType cString], plugin, mode, argsCount, cAttributes, cValues, NULL); + [[self class] setCurrentPluginView:nil]; + + LOG(Plugins, "NPP_New: %d", npErr); + return npErr; +} + +- (void)_destroyPlugin +{ + NPError npErr; + npErr = NPP_Destroy(plugin, NULL); + LOG(Plugins, "NPP_Destroy: %d", npErr); + + if (Frame* frame = core([self webFrame])) + frame->cleanupScriptObjectsForPlugin(self); + + free(plugin); + plugin = NULL; +} + +- (void)_viewHasMoved +{ + // All of the work this method does may safely be skipped if the view is not in a window. When the view + // is moved back into a window, everything should be set up correctly. + if (![self window]) + return; + + if (drawingModel == NPDrawingModelOpenGL) + [self _reshapeAGLWindow]; + +#ifndef NP_NO_QUICKDRAW + if (drawingModel == NPDrawingModelQuickDraw) + [self tellQuickTimeToChill]; +#endif + [self updateAndSetWindow]; + [self resetTrackingRect]; + + // Check to see if the plugin view is completely obscured (scrolled out of view, for example). + // For performance reasons, we send null events at a lower rate to plugins which are obscured. + BOOL oldIsObscured = isCompletelyObscured; + isCompletelyObscured = NSIsEmptyRect([self visibleRect]); + if (isCompletelyObscured != oldIsObscured) + [self restartNullEvents]; +} + +- (NSBitmapImageRep *)_printedPluginBitmap +{ +#ifdef NP_NO_QUICKDRAW + return nil; +#else + // Cannot print plugins that do not implement NPP_Print + if (!NPP_Print) + return nil; + + // This NSBitmapImageRep will share its bitmap buffer with a GWorld that the plugin will draw into. + // The bitmap is created in 32-bits-per-pixel ARGB format, which is the default GWorld pixel format. + NSBitmapImageRep *bitmap = [[[NSBitmapImageRep alloc] initWithBitmapDataPlanes:NULL + pixelsWide:window.width + pixelsHigh:window.height + bitsPerSample:8 + samplesPerPixel:4 + hasAlpha:YES + isPlanar:NO + colorSpaceName:NSDeviceRGBColorSpace + bitmapFormat:NSAlphaFirstBitmapFormat + bytesPerRow:0 + bitsPerPixel:0] autorelease]; + ASSERT(bitmap); + + // Create a GWorld with the same underlying buffer into which the plugin can draw + ::Rect printGWorldBounds; + SetRect(&printGWorldBounds, 0, 0, window.width, window.height); + GWorldPtr printGWorld; + if (NewGWorldFromPtr(&printGWorld, + k32ARGBPixelFormat, + &printGWorldBounds, + NULL, + NULL, + 0, + (Ptr)[bitmap bitmapData], + [bitmap bytesPerRow]) != noErr) { + LOG_ERROR("Could not create GWorld for printing"); + return nil; + } + + /// Create NPWindow for the GWorld + NPWindow printNPWindow; + printNPWindow.window = &printGWorld; // Normally this is an NP_Port, but when printing it is the actual CGrafPtr + printNPWindow.x = 0; + printNPWindow.y = 0; + printNPWindow.width = window.width; + printNPWindow.height = window.height; + printNPWindow.clipRect.top = 0; + printNPWindow.clipRect.left = 0; + printNPWindow.clipRect.right = window.width; + printNPWindow.clipRect.bottom = window.height; + printNPWindow.type = NPWindowTypeDrawable; // Offscreen graphics port as opposed to a proper window + + // Create embed-mode NPPrint + NPPrint npPrint; + npPrint.mode = NP_EMBED; + npPrint.print.embedPrint.window = printNPWindow; + npPrint.print.embedPrint.platformPrint = printGWorld; + + // Tell the plugin to print into the GWorld + [self willCallPlugInFunction]; + { + KJS::JSLock::DropAllLocks dropAllLocks; + NPP_Print(plugin, &npPrint); + } + [self didCallPlugInFunction]; + + // Don't need the GWorld anymore + DisposeGWorld(printGWorld); + + return bitmap; +#endif +} + +- (BOOL)_createAGLContextIfNeeded +{ + ASSERT(drawingModel == NPDrawingModelOpenGL); + + // Do nothing (but indicate success) if the AGL context already exists + if (aglContext) + return YES; + + switch (window.type) { + case NPWindowTypeWindow: + return [self _createWindowedAGLContext]; + + case NPWindowTypeDrawable: + return [self _createWindowlessAGLContext]; + + default: + ASSERT_NOT_REACHED(); + return NO; + } +} + +- (BOOL)_createWindowedAGLContext +{ + ASSERT(drawingModel == NPDrawingModelOpenGL); + ASSERT(!aglContext); + ASSERT(!aglWindow); + ASSERT([self window]); + + GLint pixelFormatAttributes[] = { + AGL_RGBA, + AGL_RED_SIZE, 8, + AGL_GREEN_SIZE, 8, + AGL_BLUE_SIZE, 8, + AGL_ALPHA_SIZE, 8, + AGL_DEPTH_SIZE, 32, + AGL_WINDOW, + AGL_ACCELERATED, + 0 + }; + + // Choose AGL pixel format + AGLPixelFormat pixelFormat = aglChoosePixelFormat(NULL, 0, pixelFormatAttributes); + if (!pixelFormat) { + LOG_ERROR("Could not find suitable AGL pixel format: %s", aglErrorString(aglGetError())); + return NO; + } + + // Create AGL context + aglContext = aglCreateContext(pixelFormat, NULL); + aglDestroyPixelFormat(pixelFormat); + if (!aglContext) { + LOG_ERROR("Could not create AGL context: %s", aglErrorString(aglGetError())); + return NO; + } + + // Create AGL window + aglWindow = [[NSWindow alloc] initWithContentRect:NSZeroRect styleMask:NSBorderlessWindowMask backing:NSBackingStoreBuffered defer:NO]; + if (!aglWindow) { + LOG_ERROR("Could not create window for AGL drawable."); + return NO; + } + + // AGL window should allow clicks to go through -- mouse events are tracked by WebCore + [aglWindow setIgnoresMouseEvents:YES]; + + // Make sure the window is not opaque -- windowed plug-ins cannot layer with other page elements + [aglWindow setOpaque:YES]; + + // Position and order in the AGL window + [self _reshapeAGLWindow]; + + // Attach the AGL context to its window + GLboolean success; +#ifdef AGL_VERSION_3_0 + success = aglSetWindowRef(aglContext, (WindowRef)[aglWindow windowRef]); +#else + success = aglSetDrawable(aglContext, (AGLDrawable)GetWindowPort((WindowRef)[aglWindow windowRef])); +#endif + if (!success) { + LOG_ERROR("Could not set AGL drawable: %s", aglErrorString(aglGetError())); + aglDestroyContext(aglContext); + aglContext = NULL; + return NO; + } + + return YES; +} + +- (BOOL)_createWindowlessAGLContext +{ + ASSERT(drawingModel == NPDrawingModelOpenGL); + ASSERT(!aglContext); + ASSERT(!aglWindow); + + GLint pixelFormatAttributes[] = { + AGL_RGBA, + AGL_RED_SIZE, 8, + AGL_GREEN_SIZE, 8, + AGL_BLUE_SIZE, 8, + AGL_ALPHA_SIZE, 8, + AGL_DEPTH_SIZE, 32, + AGL_OFFSCREEN, + 0 + }; + + // Choose AGL pixel format + AGLPixelFormat pixelFormat = aglChoosePixelFormat(NULL, 0, pixelFormatAttributes); + if (!pixelFormat) { + LOG_ERROR("Could not find suitable AGL pixel format: %s", aglErrorString(aglGetError())); + return NO; + } + + // Create AGL context + aglContext = aglCreateContext(pixelFormat, NULL); + aglDestroyPixelFormat(pixelFormat); + if (!aglContext) { + LOG_ERROR("Could not create AGL context: %s", aglErrorString(aglGetError())); + return NO; + } + + // Create offscreen buffer for AGL context + NSSize boundsSize = [self bounds].size; + GLvoid *offscreenBuffer = (GLvoid *)malloc(static_cast(boundsSize.width * boundsSize.height * 4)); + if (!offscreenBuffer) { + LOG_ERROR("Could not allocate offscreen buffer for AGL context"); + aglDestroyContext(aglContext); + aglContext = NULL; + return NO; + } + + // Attach AGL context to offscreen buffer + CGLContextObj cglContext = [self _cglContext]; + CGLError error = CGLSetOffScreen(cglContext, static_cast(boundsSize.width), static_cast(boundsSize.height), static_cast(boundsSize.width * 4), offscreenBuffer); + if (error) { + LOG_ERROR("Could not set offscreen buffer for AGL context: %d", error); + aglDestroyContext(aglContext); + aglContext = NULL; + return NO; + } + + return YES; +} + +- (CGLContextObj)_cglContext +{ + ASSERT(drawingModel == NPDrawingModelOpenGL); + + CGLContextObj cglContext = NULL; + if (!aglGetCGLContext(aglContext, (void **)&cglContext) || !cglContext) + LOG_ERROR("Could not get CGL context for AGL context: %s", aglErrorString(aglGetError())); + + return cglContext; +} + +- (BOOL)_getAGLOffscreenBuffer:(GLvoid **)outBuffer width:(GLsizei *)outWidth height:(GLsizei *)outHeight +{ + ASSERT(drawingModel == NPDrawingModelOpenGL); + + if (outBuffer) + *outBuffer = NULL; + if (outWidth) + *outWidth = 0; + if (outHeight) + *outHeight = 0; + + // Only windowless plug-ins have offscreen buffers + if (window.type != NPWindowTypeDrawable) + return NO; + + CGLContextObj cglContext = [self _cglContext]; + if (!cglContext) + return NO; + + GLsizei width, height; + GLint rowBytes; + void *offscreenBuffer = NULL; + CGLError error = CGLGetOffScreen(cglContext, &width, &height, &rowBytes, &offscreenBuffer); + if (error || !offscreenBuffer) { + LOG_ERROR("Could not get offscreen buffer for AGL context: %d", error); + return NO; + } + + if (outBuffer) + *outBuffer = offscreenBuffer; + if (outWidth) + *outWidth = width; + if (outHeight) + *outHeight = height; + + return YES; +} + +- (void)_destroyAGLContext +{ + ASSERT(drawingModel == NPDrawingModelOpenGL); + + if (!aglContext) + return; + + if (aglContext) { + // If this is a windowless plug-in, free its offscreen buffer + GLvoid *offscreenBuffer; + if ([self _getAGLOffscreenBuffer:&offscreenBuffer width:NULL height:NULL]) + free(offscreenBuffer); + + // Detach context from the AGL window +#ifdef AGL_VERSION_3_0 + aglSetWindowRef(aglContext, NULL); +#else + aglSetDrawable(aglContext, NULL); +#endif + + // Destroy the context + aglDestroyContext(aglContext); + aglContext = NULL; + } + + // Destroy the AGL window + if (aglWindow) { + [self _hideAGLWindow]; + aglWindow = nil; + } +} + +- (void)_reshapeAGLWindow +{ + ASSERT(drawingModel == NPDrawingModelOpenGL); + + if (!aglContext) + return; + + switch (window.type) { + case NPWindowTypeWindow: + { + if (!aglWindow) + break; + + // The AGL window is being reshaped because the plugin view has moved. Since the view has moved, it will soon redraw. + // We want the AGL window to update at the same time as its underlying view. So, we disable screen updates until the + // plugin view's window flushes. + NSWindow *browserWindow = [self window]; + ASSERT(browserWindow); + [browserWindow disableScreenUpdatesUntilFlush]; + + // Add the AGL window as a child of the main window if necessary + if ([aglWindow parentWindow] != browserWindow) + [browserWindow addChildWindow:aglWindow ordered:NSWindowAbove]; + + // Update the AGL window frame + NSRect aglWindowFrame = [self convertRect:[self visibleRect] toView:nil]; + aglWindowFrame.origin = [browserWindow convertBaseToScreen:aglWindowFrame.origin]; + [aglWindow setFrame:aglWindowFrame display:NO]; + + // Update the AGL context + aglUpdateContext(aglContext); + } + break; + + case NPWindowTypeDrawable: + { + // Get offscreen buffer; we can skip this step if we don't have one yet + GLvoid *offscreenBuffer; + GLsizei width, height; + if (![self _getAGLOffscreenBuffer:&offscreenBuffer width:&width height:&height] || !offscreenBuffer) + break; + + // Don't resize the offscreen buffer if it's already the same size as the view bounds + NSSize boundsSize = [self bounds].size; + if (boundsSize.width == width && boundsSize.height == height) + break; + + // Resize the offscreen buffer + offscreenBuffer = realloc(offscreenBuffer, static_cast(boundsSize.width * boundsSize.height * 4)); + if (!offscreenBuffer) { + LOG_ERROR("Could not allocate offscreen buffer for AGL context"); + break; + } + + // Update the offscreen + CGLContextObj cglContext = [self _cglContext]; + CGLError error = CGLSetOffScreen(cglContext, static_cast(boundsSize.width), static_cast(boundsSize.height), static_cast(boundsSize.width * 4), offscreenBuffer); + if (error) { + LOG_ERROR("Could not set offscreen buffer for AGL context: %d", error); + break; + } + + // Update the AGL context + aglUpdateContext(aglContext); + } + break; + + default: + ASSERT_NOT_REACHED(); + break; + } +} + +- (void)_hideAGLWindow +{ + ASSERT(drawingModel == NPDrawingModelOpenGL); + + if (!aglWindow) + return; + + // aglWindow should only be set for a windowed OpenGL plug-in + ASSERT(window.type == NPWindowTypeWindow); + + NSWindow *parentWindow = [aglWindow parentWindow]; + if (parentWindow) { + // Disable screen updates so that this AGL window orders out atomically with other plugins' AGL windows + [parentWindow disableScreenUpdatesUntilFlush]; + ASSERT(parentWindow == [self window]); + [parentWindow removeChildWindow:aglWindow]; + } + [aglWindow orderOut:nil]; +} + +- (NSImage *)_aglOffscreenImageForDrawingInRect:(NSRect)drawingInRect +{ + ASSERT(drawingModel == NPDrawingModelOpenGL); + + CGLContextObj cglContext = [self _cglContext]; + if (!cglContext) + return nil; + + // Get the offscreen buffer + GLvoid *offscreenBuffer; + GLsizei width, height; + if (![self _getAGLOffscreenBuffer:&offscreenBuffer width:&width height:&height]) + return nil; + + unsigned char *plane = (unsigned char *)offscreenBuffer; + +#if defined(__i386__) || defined(__x86_64__) + // Make rect inside the offscreen buffer because we're about to directly modify the bits inside drawingInRect + NSRect rect = NSIntegralRect(NSIntersectionRect(drawingInRect, NSMakeRect(0, 0, width, height))); + + // The offscreen buffer, being an OpenGL framebuffer, is in BGRA format on x86. We need to swap the blue and red channels before + // wrapping the buffer in an NSBitmapImageRep, which only supports RGBA and ARGB. + // On PowerPC, the OpenGL framebuffer is in ARGB format. Since that is a format that NSBitmapImageRep supports, all that is + // needed on PowerPC is to pass the NSAlphaFirstBitmapFormat flag when creating the NSBitmapImageRep. On x86, we need to swap the + // framebuffer color components such that they are in ARGB order, as they are on PowerPC. + // If only a small region of the plug-in is being redrawn, then it would be a waste to convert the entire image from BGRA to ARGB. + // Since we know what region of the image will ultimately be drawn to screen (drawingInRect), we restrict the channel swapping to + // just that region within the offscreen buffer. + if (!WebConvertBGRAToARGB(plane, width * 4, (int)rect.origin.x, (int)rect.origin.y, (int)rect.size.width, (int)rect.size.height)) + return nil; +#endif /* defined(__i386__) || defined(__x86_64__) */ + + NSBitmapImageRep *aglBitmap = [[NSBitmapImageRep alloc] + initWithBitmapDataPlanes:&plane + pixelsWide:width + pixelsHigh:height + bitsPerSample:8 + samplesPerPixel:4 + hasAlpha:YES + isPlanar:NO + colorSpaceName:NSDeviceRGBColorSpace + bitmapFormat:NSAlphaFirstBitmapFormat + bytesPerRow:width * 4 + bitsPerPixel:32]; + if (!aglBitmap) { + LOG_ERROR("Could not create bitmap for AGL offscreen buffer"); + return nil; + } + + // Wrap the bitmap in an NSImage. This allocation isn't very expensive -- the actual image data is already in the bitmap rep + NSImage *aglImage = [[[NSImage alloc] initWithSize:[aglBitmap size]] autorelease]; + [aglImage addRepresentation:aglBitmap]; + [aglBitmap release]; + + return aglImage; +} + +- (void)_redeliverStream +{ + if ([self dataSource] && [self isStarted]) { + // Deliver what has not been passed to the plug-in up to this point. + if (_dataLengthReceived > 0) { + NSData *data = [[[self dataSource] data] subdataWithRange:NSMakeRange(0, _dataLengthReceived)]; + _dataLengthReceived = 0; + [self pluginView:self receivedData:data]; + if (![[self dataSource] isLoading]) { + if (_error) + [self pluginView:self receivedError:_error]; + else + [self pluginViewFinishedLoading:self]; + } + } + } +} + +@end + +@implementation NSData (PluginExtras) + +- (BOOL)_web_startsWithBlankLine +{ + return [self length] > 0 && ((const char *)[self bytes])[0] == '\n'; +} + + +- (NSInteger)_web_locationAfterFirstBlankLine +{ + const char *bytes = (const char *)[self bytes]; + unsigned length = [self length]; + + unsigned i; + for (i = 0; i < length - 4; i++) { + + // Support for Acrobat. It sends "\n\n". + if (bytes[i] == '\n' && bytes[i+1] == '\n') { + return i+2; + } + + // Returns the position after 2 CRLF's or 1 CRLF if it is the first line. + if (bytes[i] == '\r' && bytes[i+1] == '\n') { + i += 2; + if (i == 2) { + return i; + } else if (bytes[i] == '\n') { + // Support for Director. It sends "\r\n\n" (3880387). + return i+1; + } else if (bytes[i] == '\r' && bytes[i+1] == '\n') { + // Support for Flash. It sends "\r\n\r\n" (3758113). + return i+2; + } + } + } + return NSNotFound; +} + +@end +#endif