wiki:NetscapePluginsOnMacNotes

Version 1 (modified by vestbo@webkit.org, 14 years ago) (diff)

--

Notes on Netscape plugins on Mac

Safari uses the NPAPI implementation in WebKit/mac/Plugins, not the stuff in WebCore/plugins.

This page is a brain dump of my notes when doing the WebCore NAPI implementation for Mac. The theme is basically: figure out what the WebKit implementation does, try to document/make sense of it, then implement the same behavior.

There are likely many mistakes and wrong assumptions in these notes, but they may be useful for someone else, hence the dump.

Stuff that Safari does

When Safari encounters errors during loading of a plugin, such as NPP_New failing, or a bad drawing or event model combination, it cleans up by calling _destroyPlugin and _pluginPackage.close(). The former is equivalent to us calling stop() (for calling NP_Destroy), and then destructing the plugin view (which will clean up script opjects and unload the plugin package). We never unload/close the plugin package, except for in the destructor, and we don't do cleanupScriptObjectsForPlugin().

Safari wraps each call to NPP-functions in a willCallPlugInFunction() and didCallPlugInFunction(), which basically ensures that any stop() when there are plugin-stack-frames will result in shouldStopSoon being set, and the real stop() being called after the last unwind from a NPP-function.

Initialization and shutdown

PluginView is a Widget subclass, which contains all the other plugin members, such as the PluginPackage, and the NPP instance. The view is created in the factory function PluginView::create(), which uses the PluginDatabase to fetch the PluginPacakge, which is then passed on when constructing the PluginView.

PluginView has the following methods:

  • PluginView() constructor: called from PluginView::create(), from FrameLoaderClient, from FrameLoader::loadPlugin()
    • Does not call any further init methods (init or start)
  • ~PluginView() destructor: called from RenderWidget::clearWidget(), from HTMLPluginElement::detach()
    • Calls stop()
  • init(): called from PluginView::setParent(), from ScrollView::addChild(), from RenderWidget::addWidget(), from FrameLoader::loadPlugin()
    • Calls load() on the PluginPackage
    • Calls start()
  • start(): called from init()
    • Calls NP_New
  • stop(): called from the PluginView destructor
    • Calls NP_Destroy

PluginView has the following state members:

  • m_haveInitialized (boolean)
    • Used to guard against consecutive calls to init()
    • So if setParent() is called due to a reparent, init() will not be called
    • This means anything done in init should be done only once
  • m_isStarted (boolean)
    • Set to true after a successfull call to NP_New
    • Guards against consecutive calls to start()
    • Guards against calling stop() when not started
    • Set to false in stop(), before actually calling NP_Destroy
    • The Safari implementation has the same concept (as _isStarted)
  • m_status (PluginStatus[CanNotFindPlugin|CanNotLoadPlugin|LoadedSuccessfully]
    • Does not seem to be used by the Safari implementation

Steps in initialization

  1. PluginView constructor sets up data members
  2. PluginView::setParent() is called, which calls init()
  3. Each platform has its own init(), which does the following
    1. Check that we haven't initialized before
    2. Check that we have a plugin, and in that case load the plugin package
    3. Call start() and check that it succeeded. If not, set status to PluginStatusCanNotLoadPlugin (m_isStarted is not set at this point)
    4. Do platform-spesific stuff
      • Win registeres the plugin window class, sets up offscreen painting hooks, some encoding stuff, etc
      • Qt (really X11), checks if the plugin is windowed (NPPVpluginNeedsXEmbed), and fails if its windowless by setting PluginStatusCanNotLoadPlugin
      • Mac determines the drawing and event models, and fails if not compatiable by setting the status and calling stop()
        • It also starts the null timer, and calls show() for some reason
      • GTK also checks NPPVpluginNeedsXEmbed, and calls show()
    5. Finally, status is set to PluginStatusLoadedSuccessfully

Steps in shutdown

Stuff we are doing in PluginViewMac:

  • Stopping and disconnecting all PluginStreams
  • Setting m_started to false
  • Unregister the plugin from the PluginMainThreadScheduler
  • Call NPP_Destroy on the plugin
  • Clear m_instance's pdata
  • Delete all plugin-requests
  • Free parameter names and values
  • Clean up script objects for the plugin
  • Unload the plugin package (unless there's a quirk)
  • Set the NPWindow to 0

Stuff that Safari does in WebNetscapePluginView

  • Free the plugin instance and set to 0
  • Cancel pending frame-loads (or somethign like that)
  • Set the last-NPWindow.type to 0 to reschedule a NPP_SetWindow

Stuff that Safari does in WebBaseNetscapePluginView

  • Stop timers

Stuff Safari does as part of unloading the plugin package:

  • Call NPP_Shutdown on the plugin (we do this too in PluginPackage)

Window handling and drawing

The "window state" is stored in multiple locations:

  • NPWindow window, which is a normal NPWindow This is the same as our m_npWindow
  • PluginPort nPort, which for GC has one member, a NP_CGContext cgPort The cgPort is the same as our m_npCgContext
  • PortState, which is a NP_CGContext on CG, but without the window

PortState saveAndSetNewPortStateForUpdate(bool forUpdate) {

  1. Compute bounds and visible rect in top level window coordinates (top-left-relative)
  2. Get the WindowRef of the current window
  3. Set NP_Window's x,y,width,height and type based on bounds
  4. Inspect the bounds and top-level-window state to determine clipping
    1. If completely obscured, set NPWindow's height and width to specified size and use the NPWindow.clipRect to set the size to 0 (bottom = top, etc)
    2. Otherwise set the NPWindow.clipRect based on the visible rect
  5. Construct and build new "PortState", which for CG is just a NP_CGContext, so the PortState is basically the NPWindow's window attribute
    1. Check that we can draw (probably means that we're in a paint event, INVESTIGATE)
    2. Get the CGContextRef for the current context and window
    3. If using the Cocoa event model, update the NP_Windows's window struct
      1. Setting the windowRef and context from above in the nPort.cgPort (our m_npCgContext)
    4. Save current graphics context's state using CGContextSaveGState()
    5. Clip to the dirty region if drawing to a window (do we need this?)
  6. Return the new PortState

Note: The forUpdate argument has no effect and is only used for QuickDraw

}

PortState saveAndSetNewPortState() {

  1. return saveAndSetNewPortStateForUpdate(false)

This method is identical to saveAndSetNewPortStateForUpdate() for CoreGraphics

}

void restorePortState(PortState portState) {

  1. Get the CGContextRef out of the portState
  2. Verify that the nPort.cgPort context is not set, _or_ if it's set, that it's the same as the portState's context
  3. Restore the current graphics context's state using CGContextRestoreGState()

}

void updateAndSetWindow() {

  1. Ensure that the plugin is started (and not stopped)
  2. Ensure that we can paint
  3. Do some NSView locking (not relevant for us?)
  4. Get the portState though saveAndSetNewPortState() This effectivly updates the NSWindow and npPort
  5. If we have a valid port state:
    1. Call NPP_SetWindow though setWindowIfNecessary()
    2. Restore the portState though restorePortState()
    3. Free the port state
  6. Else, call setWindowIfNecessary() directly
  7. Unlock stuff we locked earlier

}

void setWindowIfNecessary() {

  1. Ensure that the plugin is started
  2. Check if the NPWindow or other structs have changed, by a call to isNewWindowEqualToOldWindow()
  3. If so, call NPP_SetWindow and save structs to lastSet-structs

This is basically a wrapper for NPP_SetWindow

}

bool isNewWindowEqualToOldWindow() {

  1. Compare every member of the NPWindow
  2. Compare the npPort.cgPort members

}

bool sendEvent(void* event, bool eventIsDrawRect) {

  1. Ensure that the plugin is started
  2. Check that we're not in NPP_SetWindow (for SVG viewer install)
  3. Check that we have a page and a frame
  4. Verify that if the event is updateEvt we're inside a paint event
  5. If the event is an updateEvt (draw), prepare for drawing:
    1. Call saveAndSetNewPortStateForUpdate()
    2. Call setWindowIfNecessary()
  6. If we have a portState (we're drawing and the previous 5.1 call returned one)
    1. Call restorePortState()

This is basically a wrapper for NPP_HandleEvent

}

void sendDrawRectEvent(NSRect rect) {

  1. Get the current graphics context
  2. Call drawRect on the event handler with the rect and context

}

void drawRect(NSRect rect) {

  1. Ensure we're not using NPDrawingModelCoreAnimation
  2. Ensure the plugin is started
  3. If we are drawing to screen, call sendDrawRectEvent()
  4. Otherwise, do bitmap drawing

}

void invalidateRect(NPRect rect) {

  1. Call invalidatePluginContentRect() with rect

}

void invalidateRegion(NPRegion region) {

  1. Call invalidatePluginContentRect() with region (converted to rect)

}

void forceRedraw() {

  1. Call invalidatePluginContentRect() with plugin bounds
  2. Call displayIfNeeded() on the plugin's NSView

}

void invalidatePluginContentRect(NSRect rect) {

  1. Get the RenderBoxModelObject (renderer) for the plugin element
  2. Compute the contentRect for the rect
  3. Move contentRect based on render-object top-left + padding
  4. Call repaintRectangle(contentRect) on the renderer

}

The NPWindow struct

Has:

  • Geometry (x, y, width, height)
  • Clip (a NPRect)
  • A platform spesific window handle (window)
    • For CoreGraphics this is a NP_CGContext, which has:
      • A CoreGraphics context reference (CGContextRef)
      • A window handle depending on the event model
        • For Carbon it's a WindowRef
        • For Cocoa it's a NPNSWindow

So:

NPWindow (window) {

  • x
  • y
  • width
  • height
  • clip
  • window {

QuickDraw: NP_Port {

# Not relevant since we don't want to support this # (deprecated in 10.5 and not available in 64-bit)

} CoreGraphics: NP_CGContext {

  • context (CGContextRef)
  • window {

Carbon: WindowRef Cocoa: NPNSWindow

} OpenGL: NP_GLContext {

# mirrors the CoreGraphics NP_CGContext # except for the CGLContextObj context

} CoreAnimation: ???

}

}

The plugin is notified of changes to this struct by calling NPP_SetWindow

Drawing models

Stored in the NPNVpluginDrawingModel variable.

Plugin can query host by NPN_GetValue(NPNVpluginDrawingModel)

QuickDraw

  • Feature can be disabled by defining NP_NO_QUICKDRAW
  • Plugin can query for spesific support using NPN_GetValue(NPNVsupportsQuickDrawBool)
  • NPDrawingModel value is NPDrawingModelQuickDraw
  • Depricated in 10.5 and not available in 64-bit

CoreGraphics

  • Plugin can query for spesific support using NPN_GetValue(NPNVsupportsCoreGraphicsBool)
  • NPDrawingModel value is NPDrawingModelCoreGraphics
  • Preferred drawing model

OpenGL

  • Plugin can query for spesific support using NPN_GetValue(NPNVsupportsOpenGLBool)
  • NPDrawingModel value is NPDrawingModelOpenGL

CoreAnimation

  • Plugin can query for spesific support using NPN_GetValue(NPNVsupportsCoreAnimationBool)
  • NPDrawingModel value is NPDrawingModelCoreAnimation
  • ???

Event models

Current event model stored in the NPNVpluginEventModel variable.

Carbon

  • Plugin can query for spesific support using NPN_GetValue(NPNVsupportsCarbonBool)
  • NPNVpluginEventModel value is NPEventModelCarbon
  • The NPEvent type sent to NPP_HandleEvent is an EventRecord

Cocoa

  • Plugin can query for spesific support using NPN_GetValue(NPNVsupportsCocoaBool)
  • NPNVpluginEventModel is NPEventModelCocoa
  • The NPEvent type sent to NPP_HandleEvent is a NPCocoaEvent
  • Does not send null-events
  • The window field of NPWindow is null. Instead the CGContextRef to use when drawing is a member of the draw event struct.

Event handling

As part of the plugin view's createPlugin() the event handler is created after determining the plugin's drawing and event model. The factory function in WebNetscapePluginEventHandler uses the event model to determine the correct event handler subclass.

The WebNetscapePluginEventHandler takes care of normal key and mouse events, as well as drawRect(), flagsChanged(), focus, and timers.

All event-functions in the event handler uses sendEvent(EventRecord* event) to do its work, which in turn calls sendEvent() on the PluginView.

So the call chain is something like:

View::mouseDown() to Handler::mouseDown() to Handler::sendEvent() to View::sendEvent() which finally ends up in NPP_HandleEvent.

The event passed to View::sendEvent() is a void*, and is passed along to NPP_HandleEvent directly, so it's up to the event handler to pass the correct type of event -- that is, either an EventRecord or a NPCocoaEvent