/* * 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. */ #if ENABLE(NETSCAPE_PLUGIN_API) #import "WebNetscapePluginView.h" #import "WebDataSourceInternal.h" #import "WebDefaultUIDelegate.h" #import "WebFrameInternal.h" #import "WebFrameView.h" #import "WebKitErrorsPrivate.h" #import "WebKitLogging.h" #import "WebNetscapeContainerCheckPrivate.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 "WebBaseNetscapePluginStream.h" #import "WebPluginContainerCheck.h" #import "WebNetscapeContainerCheckContextInfo.h" #import "WebNetscapePluginEventHandler.h" #import "WebNullPluginView.h" #import "WebPreferences.h" #import "WebPluginRequest.h" #import "WebViewInternal.h" #import "WebUIDelegatePrivate.h" #import #import #import #import #import #import #import #import #import #import #import #import #import #import #import #import #import #import #import #import #import #import #import #define LoginWindowDidSwitchFromUserNotification @"WebLoginWindowDidSwitchFromUserNotification" #define LoginWindowDidSwitchToUserNotification @"WebLoginWindowDidSwitchToUserNotification" #define WKNVSupportsCompositingCoreAnimationPluginsBool 74656 /* TRUE if the browser supports hardware compositing of Core Animation plug-ins */ static const int WKNVSilverlightFullScreenPerformanceIssueFixed = 7288546; /* TRUE if Siverlight addressed its underlying bug in */ using namespace WebCore; using namespace WebKit; using namespace std; static inline bool isDrawingModelQuickDraw(NPDrawingModel drawingModel) { #ifndef NP_NO_QUICKDRAW return drawingModel == NPDrawingModelQuickDraw; #else return false; #endif }; @interface WebNetscapePluginView (Internal) - (NPError)_createPlugin; - (void)_destroyPlugin; - (NSBitmapImageRep *)_printedPluginBitmap; - (void)_redeliverStream; - (BOOL)_shouldCancelSrcStream; @end static WebNetscapePluginView *currentPluginView = nil; typedef struct OpaquePortState* PortState; static const double ThrottledTimerInterval = 0.25; class PluginTimer : public TimerBase { public: typedef void (*TimerFunc)(NPP npp, uint32 timerID); PluginTimer(NPP npp, uint32 timerID, uint32 interval, NPBool repeat, TimerFunc timerFunc) : m_npp(npp) , m_timerID(timerID) , m_interval(interval) , m_repeat(repeat) , m_timerFunc(timerFunc) { } void start(bool throttle) { ASSERT(!isActive()); double timeInterval = m_interval / 1000.0; if (throttle) timeInterval = max(timeInterval, ThrottledTimerInterval); if (m_repeat) startRepeating(timeInterval); else startOneShot(timeInterval); } private: virtual void fired() { m_timerFunc(m_npp, m_timerID); if (!m_repeat) delete this; } NPP m_npp; uint32 m_timerID; uint32 m_interval; NPBool m_repeat; TimerFunc m_timerFunc; }; #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; @class NSTextInputContext; @interface NSResponder (AppKitDetails) - (NSTextInputContext *)inputContext; @end @interface WebNetscapePluginView (ForwardDeclarations) - (void)setWindowIfNecessary; - (NPError)loadRequest:(NSMutableURLRequest *)request inTarget:(const char *)cTarget withNotifyData:(void *)notifyData sendNotification:(BOOL)sendNotification; @end @implementation WebNetscapePluginView + (void)initialize { JSC::initializeThreading(); #ifndef BUILDING_ON_TIGER WebCoreObjCFinalizeOnMainThread(self); #endif WKSendUserChangeNotifications(); } #pragma mark EVENTS - (BOOL)superviewsHaveSuperviews { NSView *contentView = [[self window] contentView]; NSView *view; for (view = self; view != nil; view = [view superview]) { if (view == contentView) { return YES; } } return NO; } // The WindowRef created by -[NSWindow windowRef] has a QuickDraw GrafPort that covers // the entire window frame (or structure region to use the Carbon term) rather then just the window content. // We can remove this when is fixed. - (void)fixWindowPort { #ifndef NP_NO_QUICKDRAW ASSERT(isDrawingModelQuickDraw(drawingModel)); NSWindow *currentWindow = [self currentWindow]; if ([currentWindow isKindOfClass:objc_getClass("NSCarbonWindow")]) return; float windowHeight = [currentWindow frame].size.height; NSView *contentView = [currentWindow contentView]; NSRect contentRect = [contentView convertRect:[contentView frame] toView:nil]; // convert to window-relative coordinates CGrafPtr oldPort; GetPort(&oldPort); SetPort(GetWindowPort((WindowRef)[currentWindow windowRef])); MovePortTo(static_cast(contentRect.origin.x), /* Flip Y */ static_cast(windowHeight - NSMaxY(contentRect))); PortSize(static_cast(contentRect.size.width), static_cast(contentRect.size.height)); SetPort(oldPort); #endif } #ifndef NP_NO_QUICKDRAW static UInt32 getQDPixelFormatForBitmapContext(CGContextRef context) { UInt32 byteOrder = CGBitmapContextGetBitmapInfo(context) & kCGBitmapByteOrderMask; if (byteOrder == kCGBitmapByteOrderDefault) switch (CGBitmapContextGetBitsPerPixel(context)) { case 16: byteOrder = kCGBitmapByteOrder16Host; break; case 32: byteOrder = kCGBitmapByteOrder32Host; break; } switch (byteOrder) { case kCGBitmapByteOrder16Little: return k16LE555PixelFormat; case kCGBitmapByteOrder32Little: return k32BGRAPixelFormat; case kCGBitmapByteOrder16Big: return k16BE555PixelFormat; case kCGBitmapByteOrder32Big: return k32ARGBPixelFormat; } ASSERT_NOT_REACHED(); return 0; } static inline void getNPRect(const CGRect& cgr, NPRect& npr) { npr.top = static_cast(cgr.origin.y); npr.left = static_cast(cgr.origin.x); npr.bottom = static_cast(CGRectGetMaxY(cgr)); npr.right = static_cast(CGRectGetMaxX(cgr)); } #endif static inline void getNPRect(const NSRect& nr, NPRect& npr) { npr.top = static_cast(nr.origin.y); npr.left = static_cast(nr.origin.x); npr.bottom = static_cast(NSMaxY(nr)); npr.right = static_cast(NSMaxX(nr)); } - (PortState)saveAndSetNewPortStateForUpdate:(BOOL)forUpdate { ASSERT([self currentWindow] != nil); // 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 WindowRef windowRef = (WindowRef)[[self currentWindow] windowRef]; ASSERT(windowRef); // Look at the Carbon port to convert top-left-based window coordinates into top-left-based content coordinates. if (isDrawingModelQuickDraw(drawingModel)) { // 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. [self fixWindowPort]; ::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.type = NPWindowTypeWindow; window.x = (int32)boundsInWindow.origin.x; window.y = (int32)boundsInWindow.origin.y; window.width = static_cast(NSWidth(boundsInWindow)); window.height = static_cast(NSHeight(boundsInWindow)); // "Clip-out" the plug-in when: // 1) it's not really in a window or off-screen or has no height or width. // 2) window.x is a "big negative number" which is how WebCore expresses off-screen widgets. // 3) the window is miniaturized or the app is hidden // 4) we're inside of viewWillMoveToWindow: with a nil window. In this case, superviews may already have nil // superviews and nil windows and results from convertRect:toView: are incorrect. NSWindow *realWindow = [self window]; if (window.width <= 0 || window.height <= 0 || window.x < -100000 || realWindow == nil || [realWindow isMiniaturized] || [NSApp isHidden] || ![self superviewsHaveSuperviews] || [self isHiddenOrHasHiddenAncestor]) { // The following code tries to give plug-ins the same size they will eventually have. // The specifiedWidth and specifiedHeight variables are used to predict the size that // WebCore will eventually resize us to. // The QuickTime plug-in has problems if you give it a width or height of 0. // Since other plug-ins also might have the same sort of trouble, we make sure // to always give plug-ins a size other than 0,0. if (window.width <= 0) window.width = specifiedWidth > 0 ? specifiedWidth : 100; if (window.height <= 0) window.height = specifiedHeight > 0 ? specifiedHeight : 100; window.clipRect.bottom = window.clipRect.top; window.clipRect.left = window.clipRect.right; } else { getNPRect(visibleRectInWindow, window.clipRect); } // Save the port state, set up the port for entry into the plugin PortState portState; switch (drawingModel) { #ifndef NP_NO_QUICKDRAW case NPDrawingModelQuickDraw: { // Set up NS_Port. ::Rect portBounds; CGrafPtr port = GetWindowPort(windowRef); GetPortBounds(port, &portBounds); nPort.qdPort.port = port; nPort.qdPort.portx = (int32)-boundsInWindow.origin.x; nPort.qdPort.porty = (int32)-boundsInWindow.origin.y; window.window = &nPort; PortState_QD *qdPortState = (PortState_QD*)malloc(sizeof(PortState_QD)); portState = (PortState)qdPortState; GetGWorld(&qdPortState->oldPort, &qdPortState->oldDevice); qdPortState->oldOrigin.h = portBounds.left; qdPortState->oldOrigin.v = portBounds.top; qdPortState->oldClipRegion = NewRgn(); GetPortClipRegion(port, qdPortState->oldClipRegion); qdPortState->oldVisibleRegion = NewRgn(); GetPortVisibleRegion(port, qdPortState->oldVisibleRegion); RgnHandle clipRegion = NewRgn(); qdPortState->clipRegion = clipRegion; CGContextRef currentContext = (CGContextRef)[[NSGraphicsContext currentContext] graphicsPort]; if (currentContext && WKCGContextIsBitmapContext(currentContext)) { // We use WKCGContextIsBitmapContext here, because if we just called CGBitmapContextGetData // on any context, we'd log to the console every time. But even if WKCGContextIsBitmapContext // returns true, it still might not be a context we need to create a GWorld for; for example // transparency layers will return true, but return 0 for CGBitmapContextGetData. void* offscreenData = CGBitmapContextGetData(currentContext); if (offscreenData) { // If the current context is an offscreen bitmap, then create a GWorld for it. ::Rect offscreenBounds; offscreenBounds.top = 0; offscreenBounds.left = 0; offscreenBounds.right = CGBitmapContextGetWidth(currentContext); offscreenBounds.bottom = CGBitmapContextGetHeight(currentContext); GWorldPtr newOffscreenGWorld; QDErr err = NewGWorldFromPtr(&newOffscreenGWorld, getQDPixelFormatForBitmapContext(currentContext), &offscreenBounds, 0, 0, 0, static_cast(offscreenData), CGBitmapContextGetBytesPerRow(currentContext)); ASSERT(newOffscreenGWorld); ASSERT(!err); if (!err) { if (offscreenGWorld) DisposeGWorld(offscreenGWorld); offscreenGWorld = newOffscreenGWorld; SetGWorld(offscreenGWorld, NULL); port = offscreenGWorld; nPort.qdPort.port = port; boundsInWindow = [self bounds]; // Generate a QD origin based on the current affine transform for currentContext. CGAffineTransform offscreenMatrix = CGContextGetCTM(currentContext); CGPoint origin = {0,0}; CGPoint axisFlip = {1,1}; origin = CGPointApplyAffineTransform(origin, offscreenMatrix); axisFlip = CGPointApplyAffineTransform(axisFlip, offscreenMatrix); // Quartz bitmaps have origins at the bottom left, but the axes may be inverted, so handle that. origin.x = offscreenBounds.left - origin.x * (axisFlip.x - origin.x); origin.y = offscreenBounds.bottom + origin.y * (axisFlip.y - origin.y); nPort.qdPort.portx = static_cast(-boundsInWindow.origin.x + origin.x); nPort.qdPort.porty = static_cast(-boundsInWindow.origin.y - origin.y); window.x = 0; window.y = 0; window.window = &nPort; // Use the clip bounds from the context instead of the bounds we created // from the window above. getNPRect(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 the dirty region if drawing to a window. When drawing to another bitmap context, do not clip. if ([NSGraphicsContext currentContext] == [[self currentWindow] graphicsContext]) { // Clip to dirty region so plug-in does not draw over already-drawn regions of the window that are // not going to be redrawn this update. This forces plug-ins to play nice with z-index ordering. if (forUpdate) { RgnHandle viewClipRegion = NewRgn(); // Get list of dirty rects from the opaque ancestor -- WebKit does some tricks with invalidation and // display to enable z-ordering for NSViews; a side-effect of this is that only the WebHTMLView // knows about the true set of dirty rects. NSView *opaqueAncestor = [self opaqueAncestor]; const NSRect *dirtyRects; NSInteger dirtyRectCount, dirtyRectIndex; [opaqueAncestor getRectsBeingDrawn:&dirtyRects count:&dirtyRectCount]; for (dirtyRectIndex = 0; dirtyRectIndex < dirtyRectCount; dirtyRectIndex++) { NSRect dirtyRect = [self convertRect:dirtyRects[dirtyRectIndex] fromView:opaqueAncestor]; if (!NSEqualSizes(dirtyRect.size, NSZeroSize)) { // Create a region for this dirty rect RgnHandle dirtyRectRegion = NewRgn(); SetRectRgn(dirtyRectRegion, static_cast(NSMinX(dirtyRect)), static_cast(NSMinY(dirtyRect)), static_cast(NSMaxX(dirtyRect)), static_cast(NSMaxY(dirtyRect))); // Union this dirty rect with the rest of the dirty rects UnionRgn(viewClipRegion, dirtyRectRegion, viewClipRegion); DisposeRgn(dirtyRectRegion); } } // Intersect the dirty region with the clip region, so that we only draw over dirty parts SectRgn(clipRegion, viewClipRegion, clipRegion); DisposeRgn(viewClipRegion); } } // Switch to the port and set it up. SetPort(port); PenNormal(); ForeColor(blackColor); BackColor(whiteColor); SetOrigin(nPort.qdPort.portx, nPort.qdPort.porty); SetPortClipRegion(nPort.qdPort.port, clipRegion); if (forUpdate) { // AppKit may have tried to help us by doing a BeginUpdate. // But the invalid region at that level didn't include AppKit's notion of what was not valid. // We reset the port's visible region to counteract what BeginUpdate did. SetPortVisibleRegion(nPort.qdPort.port, clipRegion); InvalWindowRgn(windowRef, clipRegion); } qdPortState->forUpdate = forUpdate; break; } #endif /* NP_NO_QUICKDRAW */ case NPDrawingModelCoreGraphics: { if (![self canDraw]) { portState = NULL; break; } ASSERT([NSView focusView] == self); CGContextRef context = static_cast([[NSGraphicsContext currentContext] graphicsPort]); PortState_CG *cgPortState = (PortState_CG *)malloc(sizeof(PortState_CG)); portState = (PortState)cgPortState; cgPortState->context = context; #ifndef NP_NO_CARBON if (eventModel != NPEventModelCocoa) { // Update the plugin's window/context nPort.cgPort.window = windowRef; nPort.cgPort.context = context; window.window = &nPort.cgPort; } #endif /* NP_NO_CARBON */ // Save current graphics context's state; will be restored by -restorePortState: CGContextSaveGState(context); // Clip to the dirty region if drawing to a window. When drawing to another bitmap context, do not clip. if ([NSGraphicsContext currentContext] == [[self currentWindow] graphicsContext]) { // Get list of dirty rects from the opaque ancestor -- WebKit does some tricks with invalidation and // display to enable z-ordering for NSViews; a side-effect of this is that only the WebHTMLView // knows about the true set of dirty rects. NSView *opaqueAncestor = [self opaqueAncestor]; const NSRect *dirtyRects; NSInteger count; [opaqueAncestor getRectsBeingDrawn:&dirtyRects count:&count]; Vector convertedDirtyRects; convertedDirtyRects.resize(count); for (int i = 0; i < count; ++i) reinterpret_cast(convertedDirtyRects[i]) = [self convertRect:dirtyRects[i] fromView:opaqueAncestor]; CGContextClipToRects(context, convertedDirtyRects.data(), count); } break; } case NPDrawingModelCoreAnimation: // Just set the port state to a dummy value. portState = (PortState)1; 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); CGContextRef context = ((PortState_CG *)portState)->context; ASSERT(!nPort.cgPort.context || (context == nPort.cgPort.context)); CGContextRestoreGState(context); break; } case NPDrawingModelCoreAnimation: ASSERT(portState == (PortState)1); break; default: ASSERT_NOT_REACHED(); break; } } - (BOOL)sendEvent:(void*)event isDrawRect:(BOOL)eventIsDrawRect { if (![self window]) return NO; ASSERT(event); if (!_isStarted) return NO; ASSERT([_pluginPackage.get() pluginFuncs]->event); // Make sure we don't call NPP_HandleEvent while we're inside NPP_SetWindow. // We probably don't want more general reentrancy protection; we are really // protecting only against this one case, which actually comes up when // you first install the SVG viewer plug-in. if (inSetWindow) return NO; Frame* frame = core([self webFrame]); if (!frame) return NO; Page* page = frame->page(); if (!page) return NO; // Can only send drawRect (updateEvt) to CoreGraphics plugins when actually drawing ASSERT((drawingModel != NPDrawingModelCoreGraphics) || !eventIsDrawRect || [NSView focusView] == self); PortState portState = NULL; if (isDrawingModelQuickDraw(drawingModel) || (drawingModel != NPDrawingModelCoreAnimation && eventIsDrawRect)) { // In CoreGraphics 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:eventIsDrawRect]; // We may have changed the window, so inform the plug-in. [self setWindowIfNecessary]; } #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 (isDrawingModelQuickDraw(drawingModel) && eventIsDrawRect) { 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]; { JSC::JSLock::DropAllLocks dropAllLocks(JSC::SilenceAssertionsOnly); acceptedEvent = [_pluginPackage.get() pluginFuncs]->event(plugin, event); } [self didCallPlugInFunction]; if (portState) { if ([self currentWindow]) [self restorePortState:portState]; if (portState != (PortState)1) free(portState); } return acceptedEvent; } - (void)windowFocusChanged:(BOOL)hasFocus { _eventHandler->windowFocusChanged(hasFocus); } - (void)sendDrawRectEvent:(NSRect)rect { ASSERT(_eventHandler); CGContextRef context = static_cast([[NSGraphicsContext currentContext] graphicsPort]); _eventHandler->drawRect(context, rect); } - (void)stopTimers { [super stopTimers]; if (_eventHandler) _eventHandler->stopTimers(); if (!timers) return; HashMap::const_iterator end = timers->end(); for (HashMap::const_iterator it = timers->begin(); it != end; ++it) { PluginTimer* timer = it->second; timer->stop(); } } - (void)startTimers { [super startTimers]; // If the plugin is completely obscured (scrolled out of view, for example), then we will // send null events at a reduced rate. _eventHandler->startTimers(_isCompletelyObscured); if (!timers) return; HashMap::const_iterator end = timers->end(); for (HashMap::const_iterator it = timers->begin(); it != end; ++it) { PluginTimer* timer = it->second; ASSERT(!timer->isActive()); timer->start(_isCompletelyObscured); } } - (void)focusChanged { // We need to null check the event handler here because // the plug-in view can resign focus after it's been stopped // and the event handler has been deleted. if (_eventHandler) _eventHandler->focusChanged(_hasFocus); } - (void)mouseDown:(NSEvent *)theEvent { if (!_isStarted) return; _eventHandler->mouseDown(theEvent); } - (void)mouseUp:(NSEvent *)theEvent { if (!_isStarted) return; _eventHandler->mouseUp(theEvent); } - (void)mouseEntered:(NSEvent *)theEvent { if (!_isStarted) return; _eventHandler->mouseEntered(theEvent); } - (void)mouseExited:(NSEvent *)theEvent { if (!_isStarted) return; _eventHandler->mouseExited(theEvent); // 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]; } // We can't name this method mouseMoved because we don't want to override // the NSView mouseMoved implementation. - (void)handleMouseMoved:(NSEvent *)theEvent { if (!_isStarted) return; _eventHandler->mouseMoved(theEvent); } - (void)mouseDragged:(NSEvent *)theEvent { if (!_isStarted) return; _eventHandler->mouseDragged(theEvent); } - (void)scrollWheel:(NSEvent *)theEvent { if (!_isStarted) { [super scrollWheel:theEvent]; return; } if (!_eventHandler->scrollWheel(theEvent)) [super scrollWheel:theEvent]; } - (void)keyUp:(NSEvent *)theEvent { if (!_isStarted) return; _eventHandler->keyUp(theEvent); } - (void)keyDown:(NSEvent *)theEvent { if (!_isStarted) return; _eventHandler->keyDown(theEvent); } - (void)flagsChanged:(NSEvent *)theEvent { if (!_isStarted) return; _eventHandler->flagsChanged(theEvent); } - (void)sendModifierEventWithKeyCode:(int)keyCode character:(char)character { if (!_isStarted) return; _eventHandler->syntheticKeyDownWithCommandModifier(keyCode, character); } - (void)privateBrowsingModeDidChange { if (!_isStarted) return; NPBool value = _isPrivateBrowsingEnabled; [self willCallPlugInFunction]; { JSC::JSLock::DropAllLocks dropAllLocks(JSC::SilenceAssertionsOnly); if ([_pluginPackage.get() pluginFuncs]->setvalue) [_pluginPackage.get() pluginFuncs]->setvalue(plugin, NPNVprivateModeBool, &value); } [self didCallPlugInFunction]; } #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 NPDrawingModelCoreAnimation: if (window.window != lastSetWindow.window) return NO; break; default: ASSERT_NOT_REACHED(); break; } return YES; } -(void)tellQuickTimeToChill { #ifndef NP_NO_QUICKDRAW ASSERT(isDrawingModelQuickDraw(drawingModel)); // 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)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. There are two exceptions to this rule: // // Exception 1: QuickDraw plug-ins must be manually 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. // // Exception 2: CoreGraphics plug-ins expect to have their drawable area updated // when they are moved to a background tab, via a NPP_SetWindow call. This is // accomplished by allowing -saveAndSetNewPortStateForUpdate to "clip-out" the window's // clipRect. Flash is curently an exception to this. See 6453738. // if (!_isStarted) return; #ifdef NP_NO_QUICKDRAW if (![self canDraw]) return; #else if (drawingModel == NPDrawingModelQuickDraw) [self tellQuickTimeToChill]; else if (drawingModel == NPDrawingModelCoreGraphics && ![self canDraw] && _isFlash) { // The Flash plug-in does not expect an NPP_SetWindow call from WebKit in this case. // See Exception 2 above. return; } #endif // NP_NO_QUICKDRAW BOOL didLockFocus = [NSView focusView] != self && [self lockFocusIfCanDraw]; PortState portState = [self saveAndSetNewPortState]; if (portState) { [self setWindowIfNecessary]; [self restorePortState:portState]; if (portState != (PortState)1) free(portState); } else if (drawingModel == NPDrawingModelCoreGraphics) [self setWindowIfNecessary]; 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; [self willCallPlugInFunction]; { JSC::JSLock::DropAllLocks dropAllLocks(JSC::SilenceAssertionsOnly); npErr = [_pluginPackage.get() pluginFuncs]->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 window.clipRect size:%dx%d", npErr, nPort.cgPort.window, nPort.cgPort.context, (int)window.x, (int)window.y, (int)window.width, (int)window.height, window.clipRect.right - window.clipRect.left, window.clipRect.bottom - window.clipRect.top); break; case NPDrawingModelCoreAnimation: LOG(Plugins, "NPP_SetWindow (CoreAnimation): %d, window=%p window.x:%d window.y:%d window.width:%d window.height:%d", npErr, window.window, nPort.cgPort.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)setCurrentPluginView:(WebNetscapePluginView *)view { currentPluginView = view; } + (WebNetscapePluginView *)currentPluginView { return currentPluginView; } - (BOOL)createPlugin { // Open the plug-in package so it remains loaded while our plugin uses it [_pluginPackage.get() open]; // Initialize drawingModel to an invalid value so that we can detect when the plugin does not specify a drawingModel drawingModel = (NPDrawingModel)-1; // Initialize eventModel to an invalid value so that we can detect when the plugin does not specify an event model. eventModel = (NPEventModel)-1; NPError npErr = [self _createPlugin]; if (npErr != NPERR_NO_ERROR) { LOG_ERROR("NPP_New failed with error: %d", npErr); [self _destroyPlugin]; [_pluginPackage.get() 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. Instead, default to CoreGraphics. drawingModel = NPDrawingModelCoreGraphics; #endif } if (eventModel == (NPEventModel)-1) { // If the plug-in did not specify a drawing model we default to Carbon when it is available. #ifndef NP_NO_CARBON eventModel = NPEventModelCarbon; #else eventModel = NPEventModelCocoa; #endif // NP_NO_CARBON } #ifndef NP_NO_CARBON if (eventModel == NPEventModelCocoa && isDrawingModelQuickDraw(drawingModel)) { LOG(Plugins, "Plugin can't use use Cocoa event model with QuickDraw drawing model: %@", _pluginPackage.get()); [self _destroyPlugin]; [_pluginPackage.get() close]; return NO; } #endif // NP_NO_CARBON #ifndef BUILDING_ON_TIGER if (drawingModel == NPDrawingModelCoreAnimation) { void *value = 0; if ([_pluginPackage.get() pluginFuncs]->getvalue(plugin, NPPVpluginCoreAnimationLayer, &value) == NPERR_NO_ERROR && value) { // The plug-in gives us a retained layer. _pluginLayer.adoptNS((CALayer *)value); BOOL accleratedCompositingEnabled = false; #if USE(ACCELERATED_COMPOSITING) accleratedCompositingEnabled = [[[self webView] preferences] acceleratedCompositingEnabled]; #endif if (accleratedCompositingEnabled) [self element]->setNeedsStyleRecalc(SyntheticStyleChange); else [self setWantsLayer:YES]; LOG(Plugins, "%@ is using Core Animation drawing model with layer %@", _pluginPackage.get(), _pluginLayer.get()); } ASSERT(_pluginLayer); } #endif // Create the event handler _eventHandler.set(WebNetscapePluginEventHandler::create(self)); return YES; } #ifndef BUILDING_ON_TIGER // FIXME: This method is an ideal candidate to move up to the base class - (CALayer *)pluginLayer { return _pluginLayer.get(); } - (void)setLayer:(CALayer *)newLayer { [super setLayer:newLayer]; if (newLayer && _pluginLayer) { _pluginLayer.get().frame = [newLayer frame]; _pluginLayer.get().autoresizingMask = kCALayerWidthSizable | kCALayerHeightSizable; [newLayer addSublayer:_pluginLayer.get()]; } } #endif - (void)loadStream { if ([self _shouldCancelSrcStream]) return; 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 && ![_sourceURL.get() _web_isEmpty]) { NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:_sourceURL.get()]; [request _web_setHTTPReferrer:core([self webFrame])->loader()->outgoingReferrer()]; [self loadRequest:request inTarget:nil withNotifyData:nil sendNotification:NO]; } } - (BOOL)shouldStop { // If we're already calling a plug-in function, do not call NPP_Destroy(). The plug-in function we are calling // may assume that its instance->pdata, or other memory freed by NPP_Destroy(), is valid and unchanged until said // plugin-function returns. // See . if (pluginFunctionCallDepth > 0) { shouldStopSoon = YES; return NO; } return YES; } - (void)destroyPlugin { // To stop active streams it's necessary to invoke stop() on a copy // of streams. This is because calling WebNetscapePluginStream::stop() also has the side effect // of removing a stream from this hash set. Vector > streamsCopy; copyToVector(streams, streamsCopy); for (size_t i = 0; i < streamsCopy.size(); i++) streamsCopy[i]->stop(); [[_pendingFrameLoads.get() 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; #ifndef BUILDING_ON_TIGER _pluginLayer = 0; #endif [self _destroyPlugin]; [_pluginPackage.get() close]; _eventHandler.clear(); } - (NPEventModel)eventModel { return eventModel; } - (NPP)plugin { return plugin; } - (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.get() 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.get() _web_URLCString]); argsCount++; } else { cAttributes = (char **)malloc([keys count] * sizeof(char *)); cValues = (char **)malloc([values count] * sizeof(char *)); } BOOL isWMP = [[[_pluginPackage.get() 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++; } } - (uint32)checkIfAllowedToLoadURL:(const char*)urlCString frame:(const char*)frameNameCString callbackFunc:(void (*)(NPP npp, uint32 checkID, NPBool allowed, void* context))callbackFunc context:(void*)context { if (!_containerChecksInProgress) _containerChecksInProgress = [[NSMutableDictionary alloc] init]; NSString *frameName = frameNameCString ? [NSString stringWithCString:frameNameCString encoding:NSISOLatin1StringEncoding] : nil; ++_currentContainerCheckRequestID; WebNetscapeContainerCheckContextInfo *contextInfo = [[WebNetscapeContainerCheckContextInfo alloc] initWithCheckRequestID:_currentContainerCheckRequestID callbackFunc:callbackFunc context:context]; WebPluginContainerCheck *check = [WebPluginContainerCheck checkWithRequest:[self requestWithURLCString:urlCString] target:frameName resultObject:self selector:@selector(_containerCheckResult:contextInfo:) controller:self contextInfo:contextInfo]; [contextInfo release]; [_containerChecksInProgress setObject:check forKey:[NSNumber numberWithInt:_currentContainerCheckRequestID]]; [check start]; return _currentContainerCheckRequestID; } - (void)_containerCheckResult:(PolicyAction)policy contextInfo:(id)contextInfo { ASSERT([contextInfo isKindOfClass:[WebNetscapeContainerCheckContextInfo class]]); void (*pluginCallback)(NPP npp, uint32, NPBool, void*) = [contextInfo callback]; if (!pluginCallback) { ASSERT_NOT_REACHED(); return; } pluginCallback([self plugin], [contextInfo checkRequestID], (policy == PolicyUse), [contextInfo context]); } - (void)cancelCheckIfAllowedToLoadURL:(uint32)checkID { WebPluginContainerCheck *check = (WebPluginContainerCheck *)[_containerChecksInProgress objectForKey:[NSNumber numberWithInt:checkID]]; if (!check) return; [check cancel]; [_containerChecksInProgress removeObjectForKey:[NSNumber numberWithInt:checkID]]; } // WebPluginContainerCheck automatically calls this method after invoking our _containerCheckResult: selector. // It works this way because calling -[WebPluginContainerCheck cancel] allows it to do it's teardown process. - (void)_webPluginContainerCancelCheckIfAllowedToLoadRequest:(id)webPluginContainerCheck { ASSERT([webPluginContainerCheck isKindOfClass:[WebPluginContainerCheck class]]); WebPluginContainerCheck *check = (WebPluginContainerCheck *)webPluginContainerCheck; ASSERT([[check contextInfo] isKindOfClass:[WebNetscapeContainerCheckContextInfo class]]); [self cancelCheckIfAllowedToLoadURL:[[check contextInfo] checkRequestID]]; } #ifdef BUILDING_ON_TIGER // The Tiger compiler requires these two methods be present. Otherwise it doesn't think WebNetscapePluginView // conforms to the WebPluginContainerCheckController protocol. - (WebView *)webView { return [super webView]; } - (WebFrame *)webFrame { return [super webFrame]; } #endif #pragma mark NSVIEW - (id)initWithFrame:(NSRect)frame pluginPackage:(WebNetscapePluginPackage *)pluginPackage URL:(NSURL *)URL baseURL:(NSURL *)baseURL MIMEType:(NSString *)MIME attributeKeys:(NSArray *)keys attributeValues:(NSArray *)values loadManually:(BOOL)loadManually element:(PassRefPtr)element { self = [super initWithFrame:frame pluginPackage:pluginPackage URL:URL baseURL:baseURL MIMEType:MIME attributeKeys:keys attributeValues:values loadManually:loadManually element:element]; if (!self) return nil; _pendingFrameLoads.adoptNS([[NSMutableDictionary alloc] init]); // load the plug-in if it is not already loaded if (![pluginPackage load]) { [self release]; return nil; } return self; } - (id)initWithFrame:(NSRect)frame { ASSERT_NOT_REACHED(); return nil; } - (void)fini { #ifndef NP_NO_QUICKDRAW if (offscreenGWorld) DisposeGWorld(offscreenGWorld); #endif for (unsigned i = 0; i < argsCount; i++) { free(cAttributes[i]); free(cValues[i]); } free(cAttributes); free(cValues); ASSERT(!_eventHandler); if (timers) { deleteAllValues(*timers); delete timers; } [_containerChecksInProgress release]; } - (void)disconnectStream:(WebNetscapePluginStream*)stream { streams.remove(stream); } - (void)dealloc { ASSERT(!_isStarted); ASSERT(!plugin); [self fini]; [super dealloc]; } - (void)finalize { ASSERT_MAIN_THREAD(); ASSERT(!_isStarted); [self fini]; [super finalize]; } - (void)drawRect:(NSRect)rect { if (drawingModel == NPDrawingModelCoreAnimation) return; if (!_isStarted) return; if ([NSGraphicsContext currentContextDrawingToScreen]) [self sendDrawRectEvent:rect]; 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); } } } - (NPObject *)createPluginScriptableObject { if (![_pluginPackage.get() pluginFuncs]->getvalue || !_isStarted) return NULL; NPObject *value = NULL; NPError error; [self willCallPlugInFunction]; { JSC::JSLock::DropAllLocks dropAllLocks(JSC::SilenceAssertionsOnly); error = [_pluginPackage.get() pluginFuncs]->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::create(core([self webFrame])->loader()); } - (void)pluginView:(NSView *)pluginView receivedData:(NSData *)data { ASSERT(_loadManually); ASSERT(_manualStream); _dataLengthReceived += [data length]; if (!_isStarted) return; if (!_manualStream->plugin()) { // Check if the load should be cancelled if ([self _shouldCancelSrcStream]) { NSURLResponse *response = [[self dataSource] response]; NSError *error = [[NSError alloc] _initWithPluginErrorCode:WebKitErrorPlugInWillHandleLoad contentURL:[response URL] pluginPageURL:nil pluginName:nil // FIXME: Get this from somewhere MIMEType:[response MIMEType]]; [[self dataSource] _documentLoader]->cancelMainResourceLoad(error); [error release]; return; } _manualStream->setRequestURL([[[self dataSource] request] URL]); _manualStream->setPlugin([self plugin]); ASSERT(_manualStream->plugin()); _manualStream->startStreamWithResponse([[self dataSource] response]); } if (_manualStream->plugin()) _manualStream->didReceiveData(0, static_cast([data bytes]), [data length]); } - (void)pluginView:(NSView *)pluginView receivedError:(NSError *)error { ASSERT(_loadManually); _error = error; if (!_isStarted) { return; } _manualStream->destroyStreamWithError(error); } - (void)pluginViewFinishedLoading:(NSView *)pluginView { ASSERT(_loadManually); ASSERT(_manualStream); if (_isStarted) _manualStream->didFinishLoading(0); } - (NSTextInputContext *)inputContext { return nil; } @end @implementation WebNetscapePluginView (WebNPPCallbacks) - (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] _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]; { JSC::JSLock::DropAllLocks dropAllLocks(JSC::SilenceAssertionsOnly); [_pluginPackage.get() pluginFuncs]->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]; RefPtr stream = WebNetscapePluginStream::create([NSURLRequest requestWithURL:URL], plugin, [JSPluginRequest sendNotification], [JSPluginRequest notifyData]); RetainPtr response(AdoptNS, [[NSURLResponse alloc] initWithURL:URL MIMEType:@"text/plain" expectedContentLength:[JSData length] textEncodingName:nil]); stream->startStreamWithResponse(response.get()); stream->didReceiveData(0, static_cast([JSData bytes]), [JSData length]); stream->didFinishLoading(0); } } - (void)webFrame:(WebFrame *)webFrame didFinishLoadWithReason:(NPReason)reason { ASSERT(_isStarted); WebPluginRequest *pluginRequest = [_pendingFrameLoads.get() objectForKey:webFrame]; ASSERT(pluginRequest != nil); ASSERT([pluginRequest sendNotification]); [self willCallPlugInFunction]; { JSC::JSLock::DropAllLocks dropAllLocks(JSC::SilenceAssertionsOnly); [_pluginPackage.get() pluginFuncs]->urlnotify(plugin, [[[pluginRequest request] URL] _web_URLCString], reason, [pluginRequest notifyData]); } [self didCallPlugInFunction]; [_pendingFrameLoads.get() removeObjectForKey:webFrame]; [webFrame _setInternalLoadDelegate:nil]; } - (void)webFrame:(WebFrame *)webFrame didFinishLoadWithError:(NSError *)error { NPReason reason = NPRES_DONE; if (error != nil) reason = WebNetscapePluginStream::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(core([self webFrame])->loader()->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]; { JSC::JSLock::DropAllLocks dropAllLocks(JSC::SilenceAssertionsOnly); [_pluginPackage.get() pluginFuncs]->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. WebNetscapePluginView *view = [frame _internalLoadDelegate]; if (view != nil) { ASSERT([view isKindOfClass:[WebNetscapePluginView class]]); [view webFrame:frame didFinishLoadWithReason:NPRES_USER_BREAK]; } [_pendingFrameLoads.get() _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; // Don't allow requests to be loaded when the document loader is stopping all loaders. if ([[self dataSource] _documentLoader]->isStopping()) return NPERR_GENERIC_ERROR; NSString *target = nil; if (cTarget) { // Find the frame given the target string. target = [NSString stringWithCString:cTarget encoding:NSISOLatin1StringEncoding]; } 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] != core([self webFrame])->loader()->activeDocumentLoader() && (!cTarget || [frame findFrameNamed:target] != frame)) { return NPERR_GENERIC_ERROR; } NSString *JSString = [URL _webkit_scriptIfJavaScriptURL]; if (JSString != nil) { if (![[[self webView] preferences] isJavaScriptEnabled]) { // Return NPERR_GENERIC_ERROR if JS is disabled. This is what Mozilla does. return NPERR_GENERIC_ERROR; } else if (cTarget == NULL && _mode == NP_FULL) { // Don't allow a JavaScript request from a standalone plug-in that is self-targetted // because this can cause the user to be redirected to a blank page (3424039). return NPERR_INVALID_PARAM; } } else { if (!SecurityOrigin::canLoad(URL, String(), core([self webFrame])->document())) return NPERR_GENERIC_ERROR; } if (cTarget || JSString) { // Make when targetting a frame or evaluating a JS string, perform the request after a delay because we don't // want to potentially kill the plug-in inside of its URL request. if (JSString && target && [frame findFrameNamed:target] != frame) { // For security reasons, only allow JS requests to be made on the frame that contains the plug-in. return NPERR_INVALID_PARAM; } bool currentEventIsUserGesture = false; if (_eventHandler) currentEventIsUserGesture = _eventHandler->currentEventIsUserGesture(); WebPluginRequest *pluginRequest = [[WebPluginRequest alloc] initWithRequest:request frameName:target notifyData:notifyData sendNotification:sendNotification didStartFromUserGesture:currentEventIsUserGesture]; [self performSelector:@selector(loadPluginRequest:) withObject:pluginRequest afterDelay:0]; [pluginRequest release]; } else { RefPtr stream = WebNetscapePluginStream::create(request, plugin, sendNotification, notifyData); streams.add(stream.get()); stream->start(); } 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([contentLength intValue], dataLength); [header removeObjectForKey:@"Content-Length"]; if ([header count] > 0) { [request setAllHTTPHeaderFields:header]; } // Everything after the blank line is the actual content of the POST. postData = [postData subdataWithRange:NSMakeRange(location, dataLength)]; } } if ([postData length] == 0) { return NPERR_INVALID_PARAM; } } // Plug-ins expect to receive uncached data when doing a POST (3347134). [request setCachePolicy:NSURLRequestReloadIgnoringCacheData]; [request setHTTPBody:postData]; return [self loadRequest:request inTarget:target withNotifyData:notifyData sendNotification:sendNotification]; } - (NPError)postURLNotify:(const char *)URLCString target:(const char *)target len:(UInt32)len buf:(const char *)buf file:(NPBool)file notifyData:(void *)notifyData { LOG(Plugins, "NPN_PostURLNotify: %s", URLCString); return [self _postURL:URLCString target:target len:len buf:buf file:file notifyData:notifyData sendNotification:YES allowHeaders:YES]; } -(NPError)postURL:(const char *)URLCString target:(const char *)target len:(UInt32)len buf:(const char *)buf file:(NPBool)file { LOG(Plugins, "NPN_PostURL: %s", URLCString); // As documented, only allow headers to be specified via NPP_PostURL when using a file. return [self _postURL:URLCString target:target len:len buf:buf file:file notifyData:NULL sendNotification:NO allowHeaders:file]; } -(NPError)newStream:(NPMIMEType)type target:(const char *)target stream:(NPStream**)stream { LOG(Plugins, "NPN_NewStream"); return NPERR_GENERIC_ERROR; } -(NPError)write:(NPStream*)stream len:(SInt32)len buffer:(void *)buffer { LOG(Plugins, "NPN_Write"); return NPERR_GENERIC_ERROR; } -(NPError)destroyStream:(NPStream*)stream reason:(NPReason)reason { LOG(Plugins, "NPN_DestroyStream"); // This function does a sanity check to ensure that the NPStream provided actually // belongs to the plug-in that provided it, which fixes a crash in the DivX // plug-in: | http://bugs.webkit.org/show_bug.cgi?id=13203 if (!stream || WebNetscapePluginStream::ownerForStream(stream) != plugin) { LOG(Plugins, "Invalid NPStream passed to NPN_DestroyStream: %p", stream); return NPERR_INVALID_INSTANCE_ERROR; } WebNetscapePluginStream* browserStream = static_cast(stream->ndata); browserStream->cancelLoadAndDestroyStreamWithError(browserStream->errorForReason(reason)); return NPERR_NO_ERROR; } - (const char *)userAgent { NSString *userAgent = [[self webView] userAgentForURL:_baseURL.get()]; if (_isSilverlight) { // Silverlight has a workaround for a leak in Safari 2. This workaround is // applied when the user agent does not contain "Version/3" so we append it // at the end of the user agent. userAgent = [userAgent stringByAppendingString:@" Version/3.2.1"]; } return [userAgent 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 invalidatePluginContentRect:NSMakeRect(invalidRect->left, invalidRect->top, (float)invalidRect->right - invalidRect->left, (float)invalidRect->bottom - invalidRect->top)]; } - (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: { CGRect cgRect = CGPathGetBoundingBox((NPCGRegion)invalidRegion); invalidRect = *(NSRect*)&cgRect; break; } default: ASSERT_NOT_REACHED(); break; } [self invalidatePluginContentRect:invalidRect]; } -(void)forceRedraw { LOG(Plugins, "forceRedraw"); [self invalidatePluginContentRect:[self bounds]]; [[self window] displayIfNeeded]; } - (NPError)getVariable:(NPNVariable)variable value:(void *)value { switch (variable) { case NPNVWindowNPObject: { Frame* frame = core([self webFrame]); NPObject* windowScriptObject = frame ? frame->script()->windowScriptNPObject() : 0; // Return value is expected to be retained, as described here: if (windowScriptObject) _NPN_RetainObject(windowScriptObject); void **v = (void **)value; *v = windowScriptObject; return NPERR_NO_ERROR; } case NPNVPluginElementNPObject: { if (!_element) return NPERR_GENERIC_ERROR; NPObject *plugInScriptObject = _element->getNPObject(); // Return value is expected to be retained, as described here: if (plugInScriptObject) _NPN_RetainObject(plugInScriptObject); void **v = (void **)value; *v = plugInScriptObject; return NPERR_NO_ERROR; } case NPNVpluginDrawingModel: { *(NPDrawingModel *)value = drawingModel; return NPERR_NO_ERROR; } #ifndef NP_NO_QUICKDRAW case NPNVsupportsQuickDrawBool: { *(NPBool *)value = TRUE; return NPERR_NO_ERROR; } #endif /* NP_NO_QUICKDRAW */ case NPNVsupportsCoreGraphicsBool: { *(NPBool *)value = TRUE; return NPERR_NO_ERROR; } case NPNVsupportsOpenGLBool: { *(NPBool *)value = FALSE; return NPERR_NO_ERROR; } case NPNVsupportsCoreAnimationBool: { #ifdef BUILDING_ON_TIGER *(NPBool *)value = FALSE; #else *(NPBool *)value = TRUE; #endif return NPERR_NO_ERROR; } #ifndef NP_NO_CARBON case NPNVsupportsCarbonBool: { *(NPBool *)value = TRUE; return NPERR_NO_ERROR; } #endif /* NP_NO_CARBON */ case NPNVsupportsCocoaBool: { *(NPBool *)value = TRUE; return NPERR_NO_ERROR; } case NPNVprivateModeBool: { *(NPBool *)value = _isPrivateBrowsingEnabled; return NPERR_NO_ERROR; } case WKNVBrowserContainerCheckFuncs: { *(WKNBrowserContainerCheckFuncs **)value = browserContainerCheckFuncs(); return NPERR_NO_ERROR; } #if USE(ACCELERATED_COMPOSITING) case WKNVSupportsCompositingCoreAnimationPluginsBool: { *(NPBool *)value = [[[self webView] preferences] acceleratedCompositingEnabled]; return NPERR_NO_ERROR; } #endif default: break; } return NPERR_GENERIC_ERROR; } - (NPError)setVariable:(NPPVariable)variable value:(void *)value { switch (variable) { 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: #ifndef BUILDING_ON_TIGER case NPDrawingModelCoreAnimation: #endif drawingModel = newDrawingModel; return NPERR_NO_ERROR; // Unsupported (or unknown) drawing models: default: LOG(Plugins, "Plugin %@ uses unsupported drawing model: %d", _eventHandler.get(), drawingModel); return NPERR_GENERIC_ERROR; } } case NPPVpluginEventModel: { // Can only set event model inside NPP_New() if (self != [[self class] currentPluginView]) return NPERR_GENERIC_ERROR; // Check for valid, supported event model NPEventModel newEventModel = (NPEventModel)(uintptr_t)value; switch (newEventModel) { // Supported event models: #ifndef NP_NO_CARBON case NPEventModelCarbon: #endif case NPEventModelCocoa: eventModel = newEventModel; return NPERR_NO_ERROR; // Unsupported (or unknown) event models: default: LOG(Plugins, "Plugin %@ uses unsupported event model: %d", _eventHandler.get(), eventModel); return NPERR_GENERIC_ERROR; } } default: return NPERR_GENERIC_ERROR; } } - (uint32)scheduleTimerWithInterval:(uint32)interval repeat:(NPBool)repeat timerFunc:(void (*)(NPP npp, uint32 timerID))timerFunc { if (!timerFunc) return 0; if (!timers) timers = new HashMap; uint32 timerID; do { timerID = ++currentTimerID; } while (timers->contains(timerID) || timerID == 0); PluginTimer* timer = new PluginTimer(plugin, timerID, interval, repeat, timerFunc); timers->set(timerID, timer); if (_shouldFireTimers) timer->start(_isCompletelyObscured); return timerID; } - (void)unscheduleTimer:(uint32)timerID { if (!timers) return; if (PluginTimer* timer = timers->take(timerID)) delete timer; } - (NPError)popUpContextMenu:(NPMenu *)menu { NSEvent *currentEvent = [NSApp currentEvent]; // NPN_PopUpContextMenu must be called from within the plug-in's NPP_HandleEvent. if (!currentEvent) return NPERR_GENERIC_ERROR; [NSMenu popUpContextMenu:(NSMenu *)menu withEvent:currentEvent forView:self]; return NPERR_NO_ERROR; } - (NPError)getVariable:(NPNURLVariable)variable forURL:(const char*)url value:(char**)value length:(uint32*)length { switch (variable) { case NPNURLVCookie: { if (!value) break; NSURL *URL = [self URLWithCString:url]; if (!URL) break; if (Frame* frame = core([self webFrame])) { String cookieString = cookies(frame->document(), URL); CString cookieStringUTF8 = cookieString.utf8(); if (cookieStringUTF8.isNull()) return NPERR_GENERIC_ERROR; *value = static_cast(NPN_MemAlloc(cookieStringUTF8.length())); memcpy(*value, cookieStringUTF8.data(), cookieStringUTF8.length()); if (length) *length = cookieStringUTF8.length(); return NPERR_NO_ERROR; } break; } case NPNURLVProxy: { #if !defined(BUILDING_ON_TIGER) && !defined(BUILDING_ON_LEOPARD) if (!value) break; NSURL *URL = [self URLWithCString:url]; if (!URL) break; CString proxiesUTF8 = proxiesForURL(URL); *value = static_cast(NPN_MemAlloc(proxiesUTF8.length())); memcpy(*value, proxiesUTF8.data(), proxiesUTF8.length()); if (length) *length = proxiesUTF8.length(); return NPERR_NO_ERROR; #else break; #endif } } return NPERR_GENERIC_ERROR; } - (NPError)setVariable:(NPNURLVariable)variable forURL:(const char*)url value:(const char*)value length:(uint32)length { switch (variable) { case NPNURLVCookie: { NSURL *URL = [self URLWithCString:url]; if (!URL) break; String cookieString = String::fromUTF8(value, length); if (!cookieString) break; if (Frame* frame = core([self webFrame])) { setCookies(frame->document(), URL, cookieString); return NPERR_NO_ERROR; } break; } case NPNURLVProxy: // Can't set the proxy for a URL. break; } return NPERR_GENERIC_ERROR; } - (NPError)getAuthenticationInfoWithProtocol:(const char*)protocolStr host:(const char*)hostStr port:(int32)port scheme:(const char*)schemeStr realm:(const char*)realmStr username:(char**)usernameStr usernameLength:(uint32*)usernameLength password:(char**)passwordStr passwordLength:(uint32*)passwordLength { if (!protocolStr || !hostStr || !schemeStr || !realmStr || !usernameStr || !usernameLength || !passwordStr || !passwordLength) return NPERR_GENERIC_ERROR; CString username; CString password; if (!getAuthenticationInfo(protocolStr, hostStr, port, schemeStr, realmStr, username, password)) return NPERR_GENERIC_ERROR; *usernameLength = username.length(); *usernameStr = static_cast(NPN_MemAlloc(username.length())); memcpy(*usernameStr, username.data(), username.length()); *passwordLength = password.length(); *passwordStr = static_cast(NPN_MemAlloc(password.length())); memcpy(*passwordStr, password.data(), password.length()); return NPERR_NO_ERROR; } - (char*)resolveURL:(const char*)url forTarget:(const char*)target { WebCore::CString location = [self resolvedURLStringForURL:url target:target]; if (location.isNull()) return 0; // We use strdup here because the caller needs to free it with NPN_MemFree (which calls free). return strdup(location.data()); } @end @implementation WebNetscapePluginView (Internal) - (BOOL)_shouldCancelSrcStream { ASSERT(_isStarted); // Check if we should cancel the load NPBool cancelSrcStream = 0; if ([_pluginPackage.get() pluginFuncs]->getvalue && [_pluginPackage.get() pluginFuncs]->getvalue(plugin, NPPVpluginCancelSrcStream, &cancelSrcStream) == NPERR_NO_ERROR && cancelSrcStream) return YES; return NO; } // Work around Silverlight full screen performance issue by maintaining an accelerated GL pixel format. // We can safely remove it at some point in the future when both: // 1) Microsoft releases a genuine fix for 7288546. // 2) Enough Silverlight users update to the new Silverlight. // For now, we'll distinguish older broken versions of Silverlight by asking the plug-in if it resolved its full screen badness. - (void)_workaroundSilverlightFullScreenBug:(BOOL)initializedPlugin { #if !defined(BUILDING_ON_TIGER) && !defined(BUILDING_ON_LEOPARD) ASSERT(_isSilverlight); NPBool isFullScreenPerformanceIssueFixed = 0; NPPluginFuncs *pluginFuncs = [_pluginPackage.get() pluginFuncs]; if (pluginFuncs->getvalue && pluginFuncs->getvalue(plugin, static_cast(WKNVSilverlightFullScreenPerformanceIssueFixed), &isFullScreenPerformanceIssueFixed) == NPERR_NO_ERROR && isFullScreenPerformanceIssueFixed) return; static CGLPixelFormatObj pixelFormatObject = 0; static unsigned refCount = 0; if (initializedPlugin) { refCount++; if (refCount == 1) { const CGLPixelFormatAttribute attributes[] = { kCGLPFAAccelerated, static_cast(0) }; GLint npix; CGLChoosePixelFormat(attributes, &pixelFormatObject, &npix); } } else { ASSERT(pixelFormatObject); refCount--; if (!refCount) CGLReleasePixelFormat(pixelFormatObject); } #endif } - (NPError)_createPlugin { plugin = (NPP)calloc(1, sizeof(NPP_t)); plugin->ndata = self; ASSERT([_pluginPackage.get() pluginFuncs]->newp); // NPN_New(), which creates the plug-in instance, should never be called while calling a plug-in function for that instance. ASSERT(pluginFunctionCallDepth == 0); PluginMainThreadScheduler::scheduler().registerPlugin(plugin); _isFlash = [[[_pluginPackage.get() bundle] bundleIdentifier] isEqualToString:@"com.macromedia.Flash Player.plugin"]; _isSilverlight = [[[_pluginPackage.get() bundle] bundleIdentifier] isEqualToString:@"com.microsoft.SilverlightPlugin"]; [[self class] setCurrentPluginView:self]; NPError npErr = [_pluginPackage.get() pluginFuncs]->newp((char *)[_MIMEType.get() cString], plugin, _mode, argsCount, cAttributes, cValues, NULL); [[self class] setCurrentPluginView:nil]; if (_isSilverlight) [self _workaroundSilverlightFullScreenBug:YES]; LOG(Plugins, "NPP_New: %d", npErr); return npErr; } - (void)_destroyPlugin { PluginMainThreadScheduler::scheduler().unregisterPlugin(plugin); if (_isSilverlight) [self _workaroundSilverlightFullScreenBug:NO]; NPError npErr; npErr = ![_pluginPackage.get() pluginFuncs]->destroy(plugin, NULL); LOG(Plugins, "NPP_Destroy: %d", npErr); if (Frame* frame = core([self webFrame])) frame->script()->cleanupScriptObjectsForPlugin(self); free(plugin); plugin = NULL; } - (NSBitmapImageRep *)_printedPluginBitmap { #ifdef NP_NO_QUICKDRAW return nil; #else // Cannot print plugins that do not implement NPP_Print if (![_pluginPackage.get() pluginFuncs]->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]; { JSC::JSLock::DropAllLocks dropAllLocks(JSC::SilenceAssertionsOnly); [_pluginPackage.get() pluginFuncs]->print(plugin, &npPrint); } [self didCallPlugInFunction]; // Don't need the GWorld anymore DisposeGWorld(printGWorld); return bitmap; #endif } - (void)_redeliverStream { if ([self dataSource] && _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.get()]; else [self pluginViewFinishedLoading:self]; } } } } @end #endif