| | 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 | |