(Modified from original spec proposed by Oliver Yeoh. Modifier: Danielle Pham)

NPAPI Changes to Handle Keyboard Interaction with Plugin

1. Variables

2. Events

Plugin side changes

1. Handshake Tab Navigation Support Between Browser and Plugin

1.1. The plugin should return true when the browser queries its NPPVpluginSupportsFocusBool value.

1.2. The plugin should query the browser's NPNVSupportsFocusBool to make sure tab focus traversal handling is available on the browser side.

2. Handle Tab Focus Events Into Plugin

In handling of tab focus events, the browser will not send any NPEventType_GetFocusEvent (existing event type defined in NPAPI) to plugin.

The browser sends the GetFocusFirstEvent to the plugin when tabbing forward and GetFocusLastEvent when tabbing backward into the plugin. This can be done via NPP_HandleEvent(). The plugin should implement NPP_HandleEvent() to handle these events according to the plugin's internal focus system (which is likely platform dependent.)

The underlying keyboard event (tab or modifier+tab) will be dropped, because:

- That tab-key event took place outside of the plugin.

- Its meaning is conveyed in GetFocusFirstEvent or GetFocusLastEvent

- Sending that tab-key event easily confuses the plugin into advancing its internal focus.

Once received the GetFocusFirstEvent or the GetFocusLastEvent, plugin should take over input focus. That is, the plugin should listen to the event loop and forward any unconsumed key-strokes to the browser until it reaches the end of its tab order or until the user mouse-click to steal focus away from the plugin.

XEmbed plugins do not need to handle these events. XEmbed plugins will receive XEMBED messages which should be automatically handled by the plugin's toolkit.

3. Handle Tabbing Out of the Plugin

When user tab navigates out of the plugin, e.g, when the plugin reaches the end of its tab order, the plugin should send either the GetFocusFirstEvent to the browser when tabbing forward and GetFocusLastEvent when tabbing backward out of the plugin. This can be done via NPN_HandleEvent(). The browser should implement NPN_HandleEvent() to handle these event according to the browsers internal focus system.

The underlying keyboard event (tab or modifier+tab) will be dropped, because:

- That tab-key event took place within the plugin, not the browser.

- Its meaning is now conveyed in GetFocusFirstEvent or GetFocusLastEvent

- Sending that tab-key event easily confuses the browser into advancing its internal focus.

Once received the GetFocusFirstEvent or the GetFocusLastEvent, the browser should take over input focus. That is, the browser should listen to the event loop.

XEmbed plugins need not to do this as it's toolkit should be sending the required XEmbed messages when tabbing out of the plugin. However, XEmbed plugins should still forward other unconsumed keystrokes to the browser.

4. Handle Mouse Click Events

No change to how mouse click events are handled.

5. Forward Unused Keystrokes to the Browser

5.1. Windows

For windowed plugins, the plugin forwards unconsumed WM_KEYDOWN messages to the subclassed window procedure.
NPBool nsPluginInstance::init(NPWindow* aWindow)
{
  ....
  
  // Save the subclassed WNDPROC. We need it to forward unused keystrokes
  // back to the browser.
  mOldWndProc = SubclassWindow(mhWnd, (WNDPROC)PluginWinProc);
}

LRESULT CALLBACK PluginWinProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
  switch(msg) {
    case WM_KEYDOWN:
      if (!IsKeyUseful(wParam) {
        // We don't need this key. Forward to browser.
        return mOldWndProc(hWnd, msg, wParam, lParam);
      }
      
      // We need this key. Consume it.
      ...
      return 0;
  }
}
    
For windowless plugins, the plugin should simply return false when processing the WM_KEYDOWN in the NPP_HandleEvent() function.

5.2. Linux/Unix

For XEmbed plugins, unused keystrokes should be forwarded to the socket window.
NPError nsPluginInstance::SetWindow(NPWindow* aWindow)
{
  GtkPlug *plug = gtk_plug_new((XID)aWindow->window);
  
  g_signal_connect_after(plug, "key_press_event", G_CALLBACK(plug_key_press_event_cb), NULL);
  ...
}

gboolean plug_key_press_event_cb (GtkWidget *widget, GdkEventKey *event, gpointer user_data)
{
  // forward unconsumed keystrokes to embedder
  XEvent xevent;
  GtkPlug *plug = GTK_PLUG(widget);
  GdkScreen *screen = gdk_drawable_get_screen (plug->socket_window);

  xevent.xkey.type = KeyPress;
  xevent.xkey.window = GDK_WINDOW_XWINDOW(plug->socket_window);
  xevent.xkey.root = GDK_WINDOW_XWINDOW(gdk_screen_get_root_window(screen));
  xevent.xkey.subwindow = None;
  xevent.xkey.time = event->time;
  xevent.xkey.x = 0;
  xevent.xkey.y = 0;
  xevent.xkey.x_root = 0;
  xevent.xkey.y_root = 0;
  xevent.xkey.state = event->state;
  xevent.xkey.keycode = event->hardware_keycode;
  xevent.xkey.same_screen = True;

  XSendEvent (GDK_WINDOW_XDISPLAY (plug->socket_window),
        GDK_WINDOW_XWINDOW (plug->socket_window),
        False,
        NoEventMask,
        &xevent);

  gdk_display_sync(gdk_screen_get_display (screen));
  
  return TRUE; 
}
    
For X11 plugins, the unused keystrokes should be forwarded to the plugin's parent window.
void xt_event_handler(Widget xtwidget, nsPluginInstance *plugin, XEvent *xevent)
{
  switch (xevent->type) {
    case KeyPress:
      if (!IsUsefulKey(xevent->xkey)) {
        /* we don't need this key. forward it to the embedder. */
        Window parentWindow = 0, rootWindow = 0, *childWindows;
        unsigned int childCount;
        
        XQueryTree(mDisplay, mWindow, &rootWindow, &parentWindow, &childWindows, &childCount);
        XFree(childWindows);
        
        XEvent event;

        event.xkey.type = xevent->type;
        event.xkey.window = parentWindow;
        event.xkey.root = rootWindow;
        event.xkey.subwindow = None;
        event.xkey.time = xevent->xkey.time;
        event.xkey.x = 0;
        event.xkey.y = 0;
        event.xkey.x_root = 0;
        event.xkey.y_root = 0;
        event.xkey.state = xevent->xkey.state;
        event.xkey.keycode = xevent->xkey.keycode;
        event.xkey.same_screen = True;

        XSendEvent(mDisplay, parentWindow, False, NoEventMask, &event);
        return;
      }
      ...
      break;
  }
}
    

5.3. Mac

The plugin should simply return false when processing the keyDown event in the NPP_HandleEvent() function.