blob: 91f3bda280ccfbab01584bd4a61734190e93c21c [file] [log] [blame]
/*
* 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 "WebUIDelegatePrivate.h"
#import <Carbon/Carbon.h>
#import <JavaScriptCore/Assertions.h>
#import <JavaScriptCore/JSLock.h>
#import <JavaScriptCore/npruntime_impl.h>
#import <WebCore/Document.h>
#import <WebCore/Element.h>
#import <WebCore/Frame.h>
#import <WebCore/FrameLoader.h>
#import <WebCore/FrameTree.h>
#import <WebCore/Page.h>
#import <WebCore/SoftLinking.h>
#import <WebCore/WebCoreObjCExtras.h>
#import <WebKit/DOMPrivate.h>
#import <WebKit/WebUIDelegate.h>
#import <objc/objc-runtime.h>
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<short>(carbonEvent->where.h * HIGetScaleFactor());
carbonEvent->where.v = static_cast<short>(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<short>(carbonEvent->where.h * HIGetScaleFactor());
carbonEvent->where.v = static_cast<short>(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 <rdar://problem/4201099> 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<short>(contentRect.origin.x), /* Flip Y */ static_cast<short>(windowHeight - NSMaxY(contentRect)));
PortSize(static_cast<short>(contentRect.size.width), static_cast<short>(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<uint16>(cgr.origin.y);
npr.left = static_cast<uint16>(cgr.origin.x);
npr.bottom = static_cast<uint16>(CGRectGetMaxY(cgr));
npr.right = static_cast<uint16>(CGRectGetMaxX(cgr));
}
#endif
static inline void getNPRect(const NSRect& nr, NPRect& npr)
{
npr.top = static_cast<uint16>(nr.origin.y);
npr.left = static_cast<uint16>(nr.origin.x);
npr.bottom = static_cast<uint16>(NSMaxY(nr));
npr.right = static_cast<uint16>(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<uint32>(NSWidth(boundsInWindow));
window.height = static_cast<uint32>(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<char*>(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<int32>(-boundsInWindow.origin.x + origin.x);
nPort.qdPort.porty = static_cast<int32>(-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(CGRectOffset(CGContextGetClipBoundingBox(currentContext), -origin.x, origin.y), 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<short>(NSMinX(dirtyRect)), static_cast<short>(NSMinY(dirtyRect)), static_cast<short>(NSMaxX(dirtyRect)), static_cast<short>(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: {
ASSERT([NSView focusView] == self);
CGContextRef context = static_cast<CGContextRef>([[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<CGRect, 16> convertedDirtyRects;
convertedDirtyRects.resize(count);
for (int i = 0; i < count; ++i)
reinterpret_cast<NSRect&>(convertedDirtyRects[i]) = [self convertRect:dirtyRects[i] fromView:opaqueAncestor];
CGContextClipToRects(context, convertedDirtyRects.data(), count);
break;
}
case NPDrawingModelOpenGL: {
ASSERT([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<GLint>(NSMinX(boundsInWindow) - NSMinX(visibleRectInWindow)),
static_cast<GLint>(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;
// 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);
}
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
{
// A plug-in can only update if it's (1) already been started (2) isn't stopped
// and (3) is able to draw on-screen. To meet condition (3) the plug-in must not
// be hidden and be attached to a window. QuickDraw plug-ins are an important
// excpetion to rule (3) because they manually must be told when to stop writing
// bits to the window backing store, thus to do so requires a new call to
// NPP_SetWindow() with an empty NPWindow struct.
if (!isStarted)
return;
if (drawingModel != NPDrawingModelQuickDraw && ![self canDraw])
return;
BOOL didLockFocus = [NSView focusView] != self && [self lockFocusIfCanDraw];
PortState portState = [self saveAndSetNewPortState];
if (portState) {
[self setWindowIfNecessary];
[self restorePortState:portState];
free(portState);
}
if (didLockFocus)
[self unlockFocus];
}
- (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 <rdar://problem/4480737>.
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: <rdar://problem/3822871> 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 finishedLoading];
}
@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 finishedLoading];
[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 = kit([[self webFrame] _frameLoader]->findFrameForNavigation(frameName));
if (frame == nil) {
WebView *currentWebView = [self webView];
NSDictionary *features = [[NSDictionary alloc] init];
WebView *newWebView = [[currentWebView _UIDelegateForwarder] webView:currentWebView
createWebViewWithRequest:nil
windowFeatures:features];
[features release];
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;
}
} else if (!FrameLoader::canLoad(URL, core([self webFrame])->document()))
return NPERR_GENERIC_ERROR;
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: <rdar://problem/5093862> | 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<WebBaseNetscapePluginStream *>(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: <http://www.mozilla.org/projects/plugins/npruntime.html#browseraccess>
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: <http://www.mozilla.org/projects/plugins/npruntime.html#browseraccess>
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 NPPVpluginDrawingModel:
{
// 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,
#ifndef BUILDING_ON_TIGER
AGL_ALLOW_OFFLINE_RENDERERS,
#endif
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,
#ifndef BUILDING_ON_TIGER
AGL_ALLOW_OFFLINE_RENDERERS,
#endif
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<size_t>(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<long>(boundsSize.width), static_cast<long>(boundsSize.height), static_cast<long>(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<size_t>(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<long>(boundsSize.width), static_cast<long>(boundsSize.height), static_cast<long>(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