Saturday, October 1, 2011

How to load an NPAPI plugin inside a Safari extension, sort of ...


I was recently developing a Safari extension that had to hold an NPAPI plugin inside, but I realized that NPAPI plugins don't work inside the global.html page that runs on the Safari background. I have not seen  anything explaining this behavior in the Safari developer documentation, but I suspect that the problem is that as the global.html page is never displayed, Safari does not even try to load this kind of objects.

The only solution I have found is to put the NPAPI object inside a popover, which is loaded when the application launches and is a common instance inside Safari. The only problem of this is that the object is loaded only when the popover is shown (shit!), although all the javascript is loaded at the beginning. So if your extension has a popover and it must be shown on a common use case, you are good to go, but if not, there is a "crappy" solution that you can try. What I have done (which is veeery tricky...) is assign the popover that holds the NPAPI plugin to a toolbar button, then show the popover programmatically from the global.html page javascript, hide it again and unassign the popover from the toolbar button. The result is that when Safari is launched you momentarily see a popover shown and hidden immediately, but you have access to your NPAPI plugin inside your Safari extension :)

As this solution is a very crappy and unpleasant solution for a developer, please if you find a cleaner one, use that one and share it here ;)

This is the code I have used:

Inside the global.html page:

function deregisterPopover () {
    for (t in safari.extension.toolbarItems) {
        if (safari.extension.toolbarItems[t].identifier === "toolbar-button") {
            safari.extension.toolbarItems[t].popover.hide();
            // Deregister the popover from the toolbar button
            safari.extension.toolbarItems[t].popover = null;
        }
    }
}

// Find the popover that holds the NPAPI plugin
for (t in safari.extension.toolbarItems) {
    if (safari.extension.toolbarItems[t].identifier === "toolbar-button") {
        safari.extension.toolbarItems[t].showPopover();
        safari.extension.toolbarItems[t].popover.hide();
        NpapiPlugin = safari.extension.toolbarItems[t].popover.contentWindow.getPlugin();
    }
}

Inside the popover that holds the NPAPI plugin:

<html>
<head>
    <script>
    function pluginLoaded () {
        safari.extension.globalPage.contentWindow.deregisterPopover();
    }
    function getPlugin() {
        return document.getElementById("npapi-plugin");
    }
    </script>
</head>
<body>
<object type="application/x-test-npapi" id="npapi-plugin">
    <param name="onload" value="pluginLoaded">
</object>
</body>
</html>

Then you will have to register the popover to the toolbar button toolbar-button from the Safari Extension Builder.

More information | StackOverflow

5 comments:

  1. I tried your code, but I couldn't work with the Plugin, it's doesn't exists!
    When I execute a plugin's method, the inpector say: "TypeError: 'undefined' is not a function (evaluating 'myBars[i].popover.contentWindow.mlGetPlugin()')"
    ¿How do you configurate the plugin? Safari doesn't recognize

    ReplyDelete
  2. Sorry, this wasn't the error, The error was: "TypeError: "undefined" is not a function"

    mPlugin = document.getElementById("myplugin");
    mPlugin.DisplayMenu();

    This plugin work in chrome!

    ReplyDelete
  3. I have the same problem.
    Have you found a cleaner solution?

    ReplyDelete
    Replies
    1. Not for now... Hoping Apple to share some light...

      Delete