summaryrefslogtreecommitdiffstats
path: root/WebKit/mac/Plugins/WebBaseNetscapePluginView.mm
diff options
context:
space:
mode:
Diffstat (limited to 'WebKit/mac/Plugins/WebBaseNetscapePluginView.mm')
-rw-r--r--WebKit/mac/Plugins/WebBaseNetscapePluginView.mm3209
1 files changed, 3209 insertions, 0 deletions
diff --git a/WebKit/mac/Plugins/WebBaseNetscapePluginView.mm b/WebKit/mac/Plugins/WebBaseNetscapePluginView.mm
new file mode 100644
index 0000000..00a7b7d
--- /dev/null
+++ b/WebKit/mac/Plugins/WebBaseNetscapePluginView.mm
@@ -0,0 +1,3209 @@
+/*
+ * 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;
+
+ bool wasDeferring = page->defersLoading();
+ if (!wasDeferring)
+ page->setDefersLoading(true);
+
+ // Can only send updateEvt to CoreGraphics and OpenGL plugins when actually drawing
+ ASSERT((drawingModel != NPDrawingModelCoreGraphics && drawingModel != NPDrawingModelOpenGL) || event->what != updateEvt || [NSView focusView] == self);
+
+ BOOL updating = event->what == updateEvt;
+ PortState portState;
+ if ((drawingModel != NPDrawingModelCoreGraphics && drawingModel != NPDrawingModelOpenGL) || event->what == updateEvt) {
+ // In CoreGraphics or OpenGL mode, the port state only needs to be saved/set when redrawing the plug-in view. The plug-in is not
+ // allowed to draw at any other time.
+ portState = [self saveAndSetNewPortStateForUpdate:updating];
+
+ // We may have changed the window, so inform the plug-in.
+ [self setWindowIfNecessary];
+ } else
+ portState = NULL;
+
+#if !defined(NDEBUG) && !defined(NP_NO_QUICKDRAW)
+ // Draw green to help debug.
+ // If we see any green we know something's wrong.
+ // Note that PaintRect() only works for QuickDraw plugins; otherwise the current QD port is undefined.
+ if (drawingModel == NPDrawingModelQuickDraw && !isTransparent && event->what == updateEvt) {
+ ForeColor(greenColor);
+ const ::Rect bigRect = { -10000, -10000, 10000, 10000 };
+ PaintRect(&bigRect);
+ ForeColor(blackColor);
+ }
+#endif
+
+ // Temporarily retain self in case the plug-in view is released while sending an event.
+ [[self retain] autorelease];
+
+ BOOL acceptedEvent;
+ [self willCallPlugInFunction];
+ {
+ KJS::JSLock::DropAllLocks dropAllLocks;
+ acceptedEvent = NPP_HandleEvent(plugin, event);
+ }
+ [self didCallPlugInFunction];
+
+ currentEventIsUserGesture = NO;
+
+ if (portState) {
+ if ([self currentWindow])
+ [self restorePortState:portState];
+ free(portState);
+ }
+
+ if (!wasDeferring)
+ page->setDefersLoading(false);
+
+ return acceptedEvent;
+}
+
+- (void)sendActivateEvent:(BOOL)activate
+{
+ EventRecord event;
+
+ [self getCarbonEvent:&event];
+ event.what = activateEvt;
+ WindowRef windowRef = (WindowRef)[[self window] windowRef];
+ event.message = (unsigned long)windowRef;
+ if (activate) {
+ event.modifiers |= activeFlag;
+ }
+
+ BOOL acceptedEvent;
+ acceptedEvent = [self sendEvent:&event];
+
+ LOG(PluginEvents, "NPP_HandleEvent(activateEvent): %d isActive: %d", acceptedEvent, activate);
+}
+
+- (BOOL)sendUpdateEvent
+{
+ EventRecord event;
+
+ [self getCarbonEvent:&event];
+ event.what = updateEvt;
+ WindowRef windowRef = (WindowRef)[[self window] windowRef];
+ event.message = (unsigned long)windowRef;
+
+ BOOL acceptedEvent = [self sendEvent:&event];
+
+ LOG(PluginEvents, "NPP_HandleEvent(updateEvt): %d", acceptedEvent);
+
+ return acceptedEvent;
+}
+
+-(void)sendNullEvent
+{
+ EventRecord event;
+
+ [self getCarbonEvent:&event];
+
+ // Plug-in should not react to cursor position when not active or when a menu is down.
+ MenuTrackingData trackingData;
+ OSStatus error = GetMenuTrackingData(NULL, &trackingData);
+
+ // Plug-in should not react to cursor position when the actual window is not key.
+ if (![[self window] isKeyWindow] || (error == noErr && trackingData.menu)) {
+ // FIXME: Does passing a v and h of -1 really prevent it from reacting to the cursor position?
+ event.where.v = -1;
+ event.where.h = -1;
+ }
+
+ [self sendEvent:&event];
+}
+
+- (void)stopNullEvents
+{
+ [nullEventTimer invalidate];
+ [nullEventTimer release];
+ nullEventTimer = nil;
+}
+
+- (void)restartNullEvents
+{
+ ASSERT([self window]);
+
+ if (nullEventTimer)
+ [self stopNullEvents];
+
+ if (!isStarted || [[self window] isMiniaturized])
+ return;
+
+ NSTimeInterval interval;
+
+ // If the plugin is completely obscured (scrolled out of view, for example), then we will
+ // send null events at a reduced rate.
+ interval = !isCompletelyObscured ? NullEventIntervalActive : NullEventIntervalNotActive;
+ nullEventTimer = [[NSTimer scheduledTimerWithTimeInterval:interval
+ target:self
+ selector:@selector(sendNullEvent)
+ userInfo:nil
+ repeats:YES] retain];
+}
+
+- (BOOL)acceptsFirstResponder
+{
+ return YES;
+}
+
+- (void)installKeyEventHandler
+{
+ static const EventTypeSpec sTSMEvents[] =
+ {
+ { kEventClassTextInput, kEventTextInputUnicodeForKeyEvent }
+ };
+
+ if (!keyEventHandler) {
+ InstallEventHandler(GetWindowEventTarget((WindowRef)[[self window] windowRef]),
+ NewEventHandlerUPP(TSMEventHandler),
+ GetEventTypeCount(sTSMEvents),
+ sTSMEvents,
+ self,
+ &keyEventHandler);
+ }
+}
+
+- (void)removeKeyEventHandler
+{
+ if (keyEventHandler) {
+ RemoveEventHandler(keyEventHandler);
+ keyEventHandler = NULL;
+ }
+}
+
+- (void)setHasFocus:(BOOL)flag
+{
+ if (hasFocus != flag) {
+ hasFocus = flag;
+ EventRecord event;
+ [self getCarbonEvent:&event];
+ BOOL acceptedEvent;
+ if (hasFocus) {
+ event.what = getFocusEvent;
+ acceptedEvent = [self sendEvent:&event];
+ LOG(PluginEvents, "NPP_HandleEvent(getFocusEvent): %d", acceptedEvent);
+ [self installKeyEventHandler];
+ } else {
+ event.what = loseFocusEvent;
+ acceptedEvent = [self sendEvent:&event];
+ LOG(PluginEvents, "NPP_HandleEvent(loseFocusEvent): %d", acceptedEvent);
+ [self removeKeyEventHandler];
+ }
+ }
+}
+
+- (BOOL)becomeFirstResponder
+{
+ [self setHasFocus:YES];
+ return YES;
+}
+
+- (BOOL)resignFirstResponder
+{
+ [self setHasFocus:NO];
+ return YES;
+}
+
+// AppKit doesn't call mouseDown or mouseUp on right-click. Simulate control-click
+// mouseDown and mouseUp so plug-ins get the right-click event as they do in Carbon (3125743).
+- (void)rightMouseDown:(NSEvent *)theEvent
+{
+ [self mouseDown:theEvent];
+}
+
+- (void)rightMouseUp:(NSEvent *)theEvent
+{
+ [self mouseUp:theEvent];
+}
+
+- (void)mouseDown:(NSEvent *)theEvent
+{
+ EventRecord event;
+
+ [self getCarbonEvent:&event withEvent:theEvent];
+ event.what = mouseDown;
+
+ BOOL acceptedEvent;
+ acceptedEvent = [self sendEvent:&event];
+
+ LOG(PluginEvents, "NPP_HandleEvent(mouseDown): %d pt.v=%d, pt.h=%d", acceptedEvent, event.where.v, event.where.h);
+}
+
+- (void)mouseUp:(NSEvent *)theEvent
+{
+ EventRecord event;
+
+ [self getCarbonEvent:&event withEvent:theEvent];
+ event.what = mouseUp;
+
+ BOOL acceptedEvent;
+ acceptedEvent = [self sendEvent:&event];
+
+ LOG(PluginEvents, "NPP_HandleEvent(mouseUp): %d pt.v=%d, pt.h=%d", acceptedEvent, event.where.v, event.where.h);
+}
+
+- (void)mouseEntered:(NSEvent *)theEvent
+{
+ EventRecord event;
+
+ [self getCarbonEvent:&event withEvent:theEvent];
+ event.what = adjustCursorEvent;
+
+ BOOL acceptedEvent;
+ acceptedEvent = [self sendEvent:&event];
+
+ LOG(PluginEvents, "NPP_HandleEvent(mouseEntered): %d", acceptedEvent);
+}
+
+- (void)mouseExited:(NSEvent *)theEvent
+{
+ EventRecord event;
+
+ [self getCarbonEvent:&event withEvent:theEvent];
+ event.what = adjustCursorEvent;
+
+ BOOL acceptedEvent;
+ acceptedEvent = [self sendEvent:&event];
+
+ // Set cursor back to arrow cursor. Because NSCursor doesn't know about changes that the plugin made, we could get confused about what we think the
+ // current cursor is otherwise. Therefore we have no choice but to unconditionally reset the cursor when the mouse exits the plugin.
+ [[NSCursor arrowCursor] set];
+
+ LOG(PluginEvents, "NPP_HandleEvent(mouseExited): %d", acceptedEvent);
+}
+
+- (void)mouseDragged:(NSEvent *)theEvent
+{
+ // Do nothing so that other responders don't respond to the drag that initiated in this view.
+}
+
+- (UInt32)keyMessageForEvent:(NSEvent *)event
+{
+ NSData *data = [[event characters] dataUsingEncoding:CFStringConvertEncodingToNSStringEncoding(CFStringGetSystemEncoding())];
+ if (!data) {
+ return 0;
+ }
+ UInt8 characterCode;
+ [data getBytes:&characterCode length:1];
+ UInt16 keyCode = [event keyCode];
+ return keyCode << 8 | characterCode;
+}
+
+- (void)keyUp:(NSEvent *)theEvent
+{
+ WKSendKeyEventToTSM(theEvent);
+
+ // TSM won't send keyUp events so we have to send them ourselves.
+ // Only send keyUp events after we receive the TSM callback because this is what plug-in expect from OS 9.
+ if (!suspendKeyUpEvents) {
+ EventRecord event;
+
+ [self getCarbonEvent:&event withEvent:theEvent];
+ event.what = keyUp;
+
+ if (event.message == 0) {
+ event.message = [self keyMessageForEvent:theEvent];
+ }
+
+ [self sendEvent:&event];
+ }
+}
+
+- (void)keyDown:(NSEvent *)theEvent
+{
+ suspendKeyUpEvents = YES;
+ WKSendKeyEventToTSM(theEvent);
+}
+
+static OSStatus TSMEventHandler(EventHandlerCallRef inHandlerRef, EventRef inEvent, void *pluginView)
+{
+ EventRef rawKeyEventRef;
+ OSStatus status = GetEventParameter(inEvent, kEventParamTextInputSendKeyboardEvent, typeEventRef, NULL, sizeof(EventRef), NULL, &rawKeyEventRef);
+ if (status != noErr) {
+ LOG_ERROR("GetEventParameter failed with error: %d", status);
+ return noErr;
+ }
+
+ // Two-pass read to allocate/extract Mac charCodes
+ ByteCount numBytes;
+ status = GetEventParameter(rawKeyEventRef, kEventParamKeyMacCharCodes, typeChar, NULL, 0, &numBytes, NULL);
+ if (status != noErr) {
+ LOG_ERROR("GetEventParameter failed with error: %d", status);
+ return noErr;
+ }
+ char *buffer = (char *)malloc(numBytes);
+ status = GetEventParameter(rawKeyEventRef, kEventParamKeyMacCharCodes, typeChar, NULL, numBytes, NULL, buffer);
+ if (status != noErr) {
+ LOG_ERROR("GetEventParameter failed with error: %d", status);
+ free(buffer);
+ return noErr;
+ }
+
+ EventRef cloneEvent = CopyEvent(rawKeyEventRef);
+ unsigned i;
+ for (i = 0; i < numBytes; i++) {
+ status = SetEventParameter(cloneEvent, kEventParamKeyMacCharCodes, typeChar, 1 /* one char code */, &buffer[i]);
+ if (status != noErr) {
+ LOG_ERROR("SetEventParameter failed with error: %d", status);
+ free(buffer);
+ return noErr;
+ }
+
+ EventRecord eventRec;
+ if (ConvertEventRefToEventRecord(cloneEvent, &eventRec)) {
+ BOOL acceptedEvent;
+ acceptedEvent = [(WebBaseNetscapePluginView *)pluginView sendEvent:&eventRec];
+
+ LOG(PluginEvents, "NPP_HandleEvent(keyDown): %d charCode:%c keyCode:%lu",
+ acceptedEvent, (char) (eventRec.message & charCodeMask), (eventRec.message & keyCodeMask));
+
+ // We originally thought that if the plug-in didn't accept this event,
+ // we should pass it along so that keyboard scrolling, for example, will work.
+ // In practice, this is not a good idea, because plug-ins tend to eat the event but return false.
+ // MacIE handles each key event twice because of this, but we will emulate the other browsers instead.
+ }
+ }
+ ReleaseEvent(cloneEvent);
+
+ free(buffer);
+
+ return noErr;
+}
+
+// Fake up command-modified events so cut, copy, paste and select all menus work.
+- (void)sendModifierEventWithKeyCode:(int)keyCode character:(char)character
+{
+ EventRecord event;
+ [self getCarbonEvent:&event];
+ event.what = keyDown;
+ event.modifiers |= cmdKey;
+ event.message = keyCode << 8 | character;
+ [self sendEvent:&event];
+}
+
+- (void)cut:(id)sender
+{
+ [self sendModifierEventWithKeyCode:7 character:'x'];
+}
+
+- (void)copy:(id)sender
+{
+ [self sendModifierEventWithKeyCode:8 character:'c'];
+}
+
+- (void)paste:(id)sender
+{
+ [self sendModifierEventWithKeyCode:9 character:'v'];
+}
+
+- (void)selectAll:(id)sender
+{
+ [self sendModifierEventWithKeyCode:0 character:'a'];
+}
+
+#pragma mark WEB_NETSCAPE_PLUGIN
+
+- (BOOL)isNewWindowEqualToOldWindow
+{
+ if (window.x != lastSetWindow.x)
+ return NO;
+ if (window.y != lastSetWindow.y)
+ return NO;
+ if (window.width != lastSetWindow.width)
+ return NO;
+ if (window.height != lastSetWindow.height)
+ return NO;
+ if (window.clipRect.top != lastSetWindow.clipRect.top)
+ return NO;
+ if (window.clipRect.left != lastSetWindow.clipRect.left)
+ return NO;
+ if (window.clipRect.bottom != lastSetWindow.clipRect.bottom)
+ return NO;
+ if (window.clipRect.right != lastSetWindow.clipRect.right)
+ return NO;
+ if (window.type != lastSetWindow.type)
+ return NO;
+
+ switch (drawingModel) {
+#ifndef NP_NO_QUICKDRAW
+ case NPDrawingModelQuickDraw:
+ if (nPort.qdPort.portx != lastSetPort.qdPort.portx)
+ return NO;
+ if (nPort.qdPort.porty != lastSetPort.qdPort.porty)
+ return NO;
+ if (nPort.qdPort.port != lastSetPort.qdPort.port)
+ return NO;
+ break;
+#endif /* NP_NO_QUICKDRAW */
+
+ case NPDrawingModelCoreGraphics:
+ if (nPort.cgPort.window != lastSetPort.cgPort.window)
+ return NO;
+ if (nPort.cgPort.context != lastSetPort.cgPort.context)
+ return NO;
+ break;
+
+ case NPDrawingModelOpenGL:
+ if (nPort.aglPort.window != lastSetPort.aglPort.window)
+ return NO;
+ if (nPort.aglPort.context != lastSetPort.aglPort.context)
+ return NO;
+ break;
+
+ default:
+ ASSERT_NOT_REACHED();
+ break;
+ }
+
+ return YES;
+}
+
+- (void)updateAndSetWindow
+{
+ // 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;
+ }
+ }
+
+ 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,
+ 0
+ };
+
+ // Choose AGL pixel format
+ AGLPixelFormat pixelFormat = aglChoosePixelFormat(NULL, 0, pixelFormatAttributes);
+ if (!pixelFormat) {
+ LOG_ERROR("Could not find suitable AGL pixel format: %s", aglErrorString(aglGetError()));
+ return NO;
+ }
+
+ // Create AGL context
+ aglContext = aglCreateContext(pixelFormat, NULL);
+ aglDestroyPixelFormat(pixelFormat);
+ if (!aglContext) {
+ LOG_ERROR("Could not create AGL context: %s", aglErrorString(aglGetError()));
+ return NO;
+ }
+
+ // Create AGL window
+ aglWindow = [[NSWindow alloc] initWithContentRect:NSZeroRect styleMask:NSBorderlessWindowMask backing:NSBackingStoreBuffered defer:NO];
+ if (!aglWindow) {
+ LOG_ERROR("Could not create window for AGL drawable.");
+ return NO;
+ }
+
+ // AGL window should allow clicks to go through -- mouse events are tracked by WebCore
+ [aglWindow setIgnoresMouseEvents:YES];
+
+ // Make sure the window is not opaque -- windowed plug-ins cannot layer with other page elements
+ [aglWindow setOpaque:YES];
+
+ // Position and order in the AGL window
+ [self _reshapeAGLWindow];
+
+ // Attach the AGL context to its window
+ GLboolean success;
+#ifdef AGL_VERSION_3_0
+ success = aglSetWindowRef(aglContext, (WindowRef)[aglWindow windowRef]);
+#else
+ success = aglSetDrawable(aglContext, (AGLDrawable)GetWindowPort((WindowRef)[aglWindow windowRef]));
+#endif
+ if (!success) {
+ LOG_ERROR("Could not set AGL drawable: %s", aglErrorString(aglGetError()));
+ aglDestroyContext(aglContext);
+ aglContext = NULL;
+ return NO;
+ }
+
+ return YES;
+}
+
+- (BOOL)_createWindowlessAGLContext
+{
+ ASSERT(drawingModel == NPDrawingModelOpenGL);
+ ASSERT(!aglContext);
+ ASSERT(!aglWindow);
+
+ GLint pixelFormatAttributes[] = {
+ AGL_RGBA,
+ AGL_RED_SIZE, 8,
+ AGL_GREEN_SIZE, 8,
+ AGL_BLUE_SIZE, 8,
+ AGL_ALPHA_SIZE, 8,
+ AGL_DEPTH_SIZE, 32,
+ AGL_OFFSCREEN,
+ 0
+ };
+
+ // Choose AGL pixel format
+ AGLPixelFormat pixelFormat = aglChoosePixelFormat(NULL, 0, pixelFormatAttributes);
+ if (!pixelFormat) {
+ LOG_ERROR("Could not find suitable AGL pixel format: %s", aglErrorString(aglGetError()));
+ return NO;
+ }
+
+ // Create AGL context
+ aglContext = aglCreateContext(pixelFormat, NULL);
+ aglDestroyPixelFormat(pixelFormat);
+ if (!aglContext) {
+ LOG_ERROR("Could not create AGL context: %s", aglErrorString(aglGetError()));
+ return NO;
+ }
+
+ // Create offscreen buffer for AGL context
+ NSSize boundsSize = [self bounds].size;
+ GLvoid *offscreenBuffer = (GLvoid *)malloc(static_cast<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