| 1 | [[PageOutline]] |
| 2 | |
| 3 | = Notes on Netscape plugins on Mac = |
| 4 | |
| 5 | Safari uses the NPAPI implementation in WebKit/mac/Plugins, not the stuff in WebCore/plugins. |
| 6 | |
| 7 | 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. |
| 8 | |
| 9 | There are likely many mistakes and wrong assumptions in these notes, but they may be useful for someone else, hence the dump. |
| 10 | |
| 11 | == Stuff that Safari does == |
| 12 | |
| 13 | When Safari encounters errors during loading of a plugin, such as NPP_New |
| 14 | failing, or a bad drawing or event model combination, it cleans up by |
| 15 | calling _destroyPlugin and _pluginPackage.close(). The former is equivalent |
| 16 | to us calling stop() (for calling NP_Destroy), and then destructing the |
| 17 | plugin view (which will clean up script opjects and unload the plugin package). |
| 18 | We never unload/close the plugin package, except for in the destructor, and |
| 19 | we don't do cleanupScriptObjectsForPlugin(). |
| 20 | |
| 21 | Safari wraps each call to NPP-functions in a willCallPlugInFunction() and |
| 22 | didCallPlugInFunction(), which basically ensures that any stop() when there |
| 23 | are plugin-stack-frames will result in shouldStopSoon being set, and the |
| 24 | real stop() being called after the last unwind from a NPP-function. |
| 25 | |
| 26 | == Initialization and shutdown == |
| 27 | |
| 28 | PluginView is a Widget subclass, which contains all the other plugin |
| 29 | members, such as the PluginPackage, and the NPP instance. The view is |
| 30 | created in the factory function PluginView::create(), which uses the |
| 31 | PluginDatabase to fetch the PluginPacakge, which is then passed on when |
| 32 | constructing the PluginView. |
| 33 | |
| 34 | PluginView has the following methods: |
| 35 | |
| 36 | - PluginView() constructor: called from PluginView::create(), from FrameLoaderClient, from FrameLoader::loadPlugin() |
| 37 | - Does not call any further init methods (init or start) |
| 38 | - ~PluginView() destructor: called from RenderWidget::clearWidget(), from HTMLPluginElement::detach() |
| 39 | - Calls stop() |
| 40 | - init(): called from PluginView::setParent(), from ScrollView::addChild(), from RenderWidget::addWidget(), from FrameLoader::loadPlugin() |
| 41 | - Calls load() on the PluginPackage |
| 42 | - Calls start() |
| 43 | - start(): called from init() |
| 44 | - Calls NP_New |
| 45 | - stop(): called from the PluginView destructor |
| 46 | - Calls NP_Destroy |
| 47 | |
| 48 | PluginView has the following state members: |
| 49 | |
| 50 | - m_haveInitialized (boolean) |
| 51 | - Used to guard against consecutive calls to init() |
| 52 | - So if setParent() is called due to a reparent, init() will not be called |
| 53 | - This means anything done in init should be done only once |
| 54 | - m_isStarted (boolean) |
| 55 | - Set to true after a successfull call to NP_New |
| 56 | - Guards against consecutive calls to start() |
| 57 | - Guards against calling stop() when not started |
| 58 | - Set to false in stop(), before actually calling NP_Destroy |
| 59 | - The Safari implementation has the same concept (as _isStarted) |
| 60 | - m_status (PluginStatus[CanNotFindPlugin|CanNotLoadPlugin|LoadedSuccessfully] |
| 61 | - Does not seem to be used by the Safari implementation |
| 62 | |
| 63 | === Steps in initialization === |
| 64 | |
| 65 | 1. PluginView constructor sets up data members |
| 66 | 2. PluginView::setParent() is called, which calls init() |
| 67 | 3. Each platform has its own init(), which does the following |
| 68 | 1. Check that we haven't initialized before |
| 69 | 2. Check that we have a plugin, and in that case load the plugin package |
| 70 | 3. Call start() and check that it succeeded. If not, set status to PluginStatusCanNotLoadPlugin (m_isStarted is not set at this point) |
| 71 | 4. Do platform-spesific stuff |
| 72 | - Win registeres the plugin window class, sets up offscreen painting hooks, some encoding stuff, etc |
| 73 | - Qt (really X11), checks if the plugin is windowed (NPPVpluginNeedsXEmbed), and fails if its windowless by setting PluginStatusCanNotLoadPlugin |
| 74 | - Mac determines the drawing and event models, and fails if not compatiable by setting the status and calling stop() |
| 75 | - It also starts the null timer, and calls show() for some reason |
| 76 | - GTK also checks NPPVpluginNeedsXEmbed, and calls show() |
| 77 | 5. Finally, status is set to PluginStatusLoadedSuccessfully |
| 78 | |
| 79 | |
| 80 | === Steps in shutdown === |
| 81 | |
| 82 | Stuff we are doing in PluginViewMac: |
| 83 | |
| 84 | - Stopping and disconnecting all PluginStreams |
| 85 | - Setting m_started to false |
| 86 | - Unregister the plugin from the PluginMainThreadScheduler |
| 87 | - Call NPP_Destroy on the plugin |
| 88 | - Clear m_instance's pdata |
| 89 | - Delete all plugin-requests |
| 90 | - Free parameter names and values |
| 91 | - Clean up script objects for the plugin |
| 92 | - Unload the plugin package (unless there's a quirk) |
| 93 | - Set the NPWindow to 0 |
| 94 | |
| 95 | Stuff that Safari does in WebNetscapePluginView |
| 96 | |
| 97 | - Free the plugin instance and set to 0 |
| 98 | - Cancel pending frame-loads (or somethign like that) |
| 99 | - Set the last-NPWindow.type to 0 to reschedule a NPP_SetWindow |
| 100 | |
| 101 | Stuff that Safari does in WebBaseNetscapePluginView |
| 102 | |
| 103 | - Stop timers |
| 104 | |
| 105 | Stuff Safari does as part of unloading the plugin package: |
| 106 | |
| 107 | - Call NPP_Shutdown on the plugin (we do this too in PluginPackage) |
| 108 | |
| 109 | |
| 110 | == Window handling and drawing == |
| 111 | |
| 112 | |
| 113 | The "window state" is stored in multiple locations: |
| 114 | |
| 115 | - NPWindow window, which is a normal NPWindow |
| 116 | This is the same as our m_npWindow |
| 117 | |
| 118 | - PluginPort nPort, which for GC has one member, a NP_CGContext cgPort |
| 119 | The cgPort is the same as our m_npCgContext |
| 120 | |
| 121 | - PortState, which is a NP_CGContext on CG, but without the window |
| 122 | |
| 123 | PortState saveAndSetNewPortStateForUpdate(bool forUpdate) |
| 124 | { |
| 125 | 1. Compute bounds and visible rect in top level window coordinates (top-left-relative) |
| 126 | 2. Get the WindowRef of the current window |
| 127 | 3. Set NP_Window's x,y,width,height and type based on bounds |
| 128 | 4. Inspect the bounds and top-level-window state to determine clipping |
| 129 | 1. If completely obscured, set NPWindow's height and width to specified size |
| 130 | and use the NPWindow.clipRect to set the size to 0 (bottom = top, etc) |
| 131 | 2. Otherwise set the NPWindow.clipRect based on the visible rect |
| 132 | 5. Construct and build new "PortState", which for CG is just a NP_CGContext, |
| 133 | so the PortState is basically the NPWindow's window attribute |
| 134 | 1. Check that we can draw (probably means that we're in a paint event, INVESTIGATE) |
| 135 | 2. Get the CGContextRef for the current context and window |
| 136 | 3. If using the Cocoa event model, update the NP_Windows's window struct |
| 137 | 1. Setting the windowRef and context from above in the nPort.cgPort (our m_npCgContext) |
| 138 | 4. Save current graphics context's state using CGContextSaveGState() |
| 139 | 5. Clip to the dirty region if drawing to a window (do we need this?) |
| 140 | 6. Return the new PortState |
| 141 | |
| 142 | Note: The forUpdate argument has no effect and is only used for QuickDraw |
| 143 | } |
| 144 | |
| 145 | PortState saveAndSetNewPortState() |
| 146 | { |
| 147 | 1. return saveAndSetNewPortStateForUpdate(false) |
| 148 | |
| 149 | This method is identical to saveAndSetNewPortStateForUpdate() for CoreGraphics |
| 150 | } |
| 151 | |
| 152 | void restorePortState(PortState portState) |
| 153 | { |
| 154 | 1. Get the CGContextRef out of the portState |
| 155 | 2. Verify that the nPort.cgPort context is not set, _or_ if it's |
| 156 | set, that it's the same as the portState's context |
| 157 | 3. Restore the current graphics context's state using CGContextRestoreGState() |
| 158 | } |
| 159 | |
| 160 | void updateAndSetWindow() |
| 161 | { |
| 162 | 1. Ensure that the plugin is started (and not stopped) |
| 163 | 2. Ensure that we can paint |
| 164 | 3. Do some NSView locking (not relevant for us?) |
| 165 | 4. Get the portState though saveAndSetNewPortState() |
| 166 | This effectivly updates the NSWindow and npPort |
| 167 | 5. If we have a valid port state: |
| 168 | 1. Call NPP_SetWindow though setWindowIfNecessary() |
| 169 | 2. Restore the portState though restorePortState() |
| 170 | 3. Free the port state |
| 171 | 6. Else, call setWindowIfNecessary() directly |
| 172 | 7. Unlock stuff we locked earlier |
| 173 | } |
| 174 | |
| 175 | void setWindowIfNecessary() |
| 176 | { |
| 177 | 1. Ensure that the plugin is started |
| 178 | 2. Check if the NPWindow or other structs have changed, by a |
| 179 | call to isNewWindowEqualToOldWindow() |
| 180 | 3. If so, call NPP_SetWindow and save structs to lastSet-structs |
| 181 | |
| 182 | This is basically a wrapper for NPP_SetWindow |
| 183 | } |
| 184 | |
| 185 | bool isNewWindowEqualToOldWindow() |
| 186 | { |
| 187 | 1. Compare every member of the NPWindow |
| 188 | 2. Compare the npPort.cgPort members |
| 189 | } |
| 190 | |
| 191 | bool sendEvent(void* event, bool eventIsDrawRect) |
| 192 | { |
| 193 | 1. Ensure that the plugin is started |
| 194 | 2. Check that we're not in NPP_SetWindow (for SVG viewer install) |
| 195 | 3. Check that we have a page and a frame |
| 196 | 4. Verify that if the event is updateEvt we're inside a paint event |
| 197 | 5. If the event is an updateEvt (draw), prepare for drawing: |
| 198 | 1. Call saveAndSetNewPortStateForUpdate() |
| 199 | 2. Call setWindowIfNecessary() |
| 200 | 6. If we have a portState (we're drawing and the previous 5.1 call returned one) |
| 201 | 1. Call restorePortState() |
| 202 | |
| 203 | This is basically a wrapper for NPP_HandleEvent |
| 204 | } |
| 205 | |
| 206 | void sendDrawRectEvent(NSRect rect) |
| 207 | { |
| 208 | 1. Get the current graphics context |
| 209 | 2. Call drawRect on the event handler with the rect and context |
| 210 | } |
| 211 | |
| 212 | void drawRect(NSRect rect) |
| 213 | { |
| 214 | 1. Ensure we're not using NPDrawingModelCoreAnimation |
| 215 | 2. Ensure the plugin is started |
| 216 | 3. If we are drawing to screen, call sendDrawRectEvent() |
| 217 | 4. Otherwise, do bitmap drawing |
| 218 | } |
| 219 | |
| 220 | void invalidateRect(NPRect rect) |
| 221 | { |
| 222 | 1. Call invalidatePluginContentRect() with rect |
| 223 | } |
| 224 | |
| 225 | void invalidateRegion(NPRegion region) |
| 226 | { |
| 227 | 1. Call invalidatePluginContentRect() with region (converted to rect) |
| 228 | } |
| 229 | |
| 230 | void forceRedraw() |
| 231 | { |
| 232 | 1. Call invalidatePluginContentRect() with plugin bounds |
| 233 | 2. Call displayIfNeeded() on the plugin's NSView |
| 234 | } |
| 235 | |
| 236 | void invalidatePluginContentRect(NSRect rect) |
| 237 | { |
| 238 | 1. Get the RenderBoxModelObject (renderer) for the plugin element |
| 239 | 2. Compute the contentRect for the rect |
| 240 | 3. Move contentRect based on render-object top-left + padding |
| 241 | 4. Call repaintRectangle(contentRect) on the renderer |
| 242 | } |
| 243 | |
| 244 | |
| 245 | == The NPWindow struct == |
| 246 | |
| 247 | |
| 248 | Has: |
| 249 | |
| 250 | - Geometry (x, y, width, height) |
| 251 | - Clip (a NPRect) |
| 252 | - A platform spesific window handle (window) |
| 253 | - For CoreGraphics this is a NP_CGContext, which has: |
| 254 | - A CoreGraphics context reference (CGContextRef) |
| 255 | - A window handle depending on the event model |
| 256 | - For Carbon it's a WindowRef |
| 257 | - For Cocoa it's a NPNSWindow |
| 258 | |
| 259 | So: |
| 260 | |
| 261 | NPWindow (window) { |
| 262 | - x |
| 263 | - y |
| 264 | - width |
| 265 | - height |
| 266 | - clip |
| 267 | - window { |
| 268 | QuickDraw: NP_Port { |
| 269 | # Not relevant since we don't want to support this |
| 270 | # (deprecated in 10.5 and not available in 64-bit) |
| 271 | } |
| 272 | CoreGraphics: NP_CGContext { |
| 273 | - context (CGContextRef) |
| 274 | - window { |
| 275 | Carbon: WindowRef |
| 276 | Cocoa: NPNSWindow |
| 277 | } |
| 278 | OpenGL: NP_GLContext { |
| 279 | # mirrors the CoreGraphics NP_CGContext |
| 280 | # except for the CGLContextObj context |
| 281 | } |
| 282 | CoreAnimation: ??? |
| 283 | } |
| 284 | } |
| 285 | |
| 286 | The plugin is notified of changes to this struct by calling NPP_SetWindow |
| 287 | |
| 288 | |
| 289 | == Drawing models == |
| 290 | |
| 291 | |
| 292 | Stored in the NPNVpluginDrawingModel variable. |
| 293 | |
| 294 | Plugin can query host by NPN_GetValue(NPNVpluginDrawingModel) |
| 295 | |
| 296 | === QuickDraw === |
| 297 | |
| 298 | - Feature can be disabled by defining NP_NO_QUICKDRAW |
| 299 | - Plugin can query for spesific support using NPN_GetValue(NPNVsupportsQuickDrawBool) |
| 300 | - NPDrawingModel value is NPDrawingModelQuickDraw |
| 301 | - Depricated in 10.5 and not available in 64-bit |
| 302 | |
| 303 | === CoreGraphics === |
| 304 | |
| 305 | - Plugin can query for spesific support using NPN_GetValue(NPNVsupportsCoreGraphicsBool) |
| 306 | - NPDrawingModel value is NPDrawingModelCoreGraphics |
| 307 | - Preferred drawing model |
| 308 | |
| 309 | === OpenGL === |
| 310 | |
| 311 | - Plugin can query for spesific support using NPN_GetValue(NPNVsupportsOpenGLBool) |
| 312 | - NPDrawingModel value is NPDrawingModelOpenGL |
| 313 | |
| 314 | === CoreAnimation === |
| 315 | |
| 316 | - Plugin can query for spesific support using NPN_GetValue(NPNVsupportsCoreAnimationBool) |
| 317 | - NPDrawingModel value is NPDrawingModelCoreAnimation |
| 318 | - ??? |
| 319 | |
| 320 | |
| 321 | == Event models == |
| 322 | |
| 323 | |
| 324 | Current event model stored in the NPNVpluginEventModel variable. |
| 325 | |
| 326 | === Carbon === |
| 327 | |
| 328 | - Plugin can query for spesific support using NPN_GetValue(NPNVsupportsCarbonBool) |
| 329 | - NPNVpluginEventModel value is NPEventModelCarbon |
| 330 | - The NPEvent type sent to NPP_HandleEvent is an EventRecord |
| 331 | |
| 332 | === Cocoa === |
| 333 | |
| 334 | - Plugin can query for spesific support using NPN_GetValue(NPNVsupportsCocoaBool) |
| 335 | - NPNVpluginEventModel is NPEventModelCocoa |
| 336 | - The NPEvent type sent to NPP_HandleEvent is a NPCocoaEvent |
| 337 | - Does not send null-events |
| 338 | - The window field of NPWindow is null. Instead the CGContextRef to use when |
| 339 | drawing is a member of the draw event struct. |
| 340 | |
| 341 | == Event handling == |
| 342 | |
| 343 | |
| 344 | As part of the plugin view's createPlugin() the event handler is created after |
| 345 | determining the plugin's drawing and event model. The factory function in |
| 346 | WebNetscapePluginEventHandler uses the event model to determine the correct |
| 347 | event handler subclass. |
| 348 | |
| 349 | The WebNetscapePluginEventHandler takes care of normal key and mouse events, |
| 350 | as well as drawRect(), flagsChanged(), focus, and timers. |
| 351 | |
| 352 | All event-functions in the event handler uses sendEvent(EventRecord* event) |
| 353 | to do its work, which in turn calls sendEvent() on the PluginView. |
| 354 | |
| 355 | So the call chain is something like: |
| 356 | |
| 357 | View::mouseDown() to Handler::mouseDown() to Handler::sendEvent() to View::sendEvent() |
| 358 | which finally ends up in NPP_HandleEvent. |
| 359 | |
| 360 | The event passed to View::sendEvent() is a void*, and is passed along to NPP_HandleEvent |
| 361 | directly, so it's up to the event handler to pass the correct type of event -- that is, |
| 362 | either an EventRecord or a NPCocoaEvent |
| 363 | |
| 364 | |