views:

945

answers:

4

I'm in the process of building my first Safari extension--a very simple one--but I've run into a couple of problems. The extension boils down to a single, injected script that attempts to bypass the native feed handler and redirect to an http:// URI. My issues so far are twofold:

  1. The "whitelist" isn't working the way I'd expect. Since all feeds are shown under the "feed://" protocol, I've tried to capture that in the whitelist as "feed://*/*" (with nothing in the blacklist), but I end up in a request loop that I can't understand. If I set blacklist values of "http://*/*" and "https://*/*", everything works as expected.
  2. I can't figure out how to access my settings from my injected script. The script creates a beforeload event handler, but can't access my settings using the safari.extension.settings path indicated in the documentation.

I haven't found anything in Apple's documentation to indicate that settings shouldn't be available from my script. Since extensions are such a new feature, even Google returns limited relevant results and most of those are from the official documentation.

What am I missing?

Thanks.

UPDATE So I'm hoping that the documentation is incomplete because it's borderline abysmal, but I've learned a bit about settings. It turns out that, from injection scripts, the SafariExtensionSettings object isn't available. Injection scripts only have access to the SafariContentExtension object (which isn't useful at all), but it's aliased in exactly the same manner (safari.extension.settings)--bad idea, IMO. As stated in the injection script documentation:

Important: When you use safari.extension from within an injected script, you are not addressing the SafariExtension class. You are addressing the SafariContentExtension class.

You have to use the messaging system to talk to a global HTML file which has access to the settings. It's kind of loopy, but I have a message being sent to a global.html file that retrieves the settings and will send a message back to my injection script as soon as I figure out how to go about doing that.

Since I'm doing all of my work before the document loads, all of the methods I've found to send message back rely on a page object that I don't have.

+3  A: 

EDIT: like you said in your initial post update, the injected script doesn't have the same kind of access that a global HTML page would have. This is my working solution, imagine you want to know the value of setting "foo" in the injected script:

Injected script code:

function getMessage(msgEvent) {

    if (msgEvent.name == "settingValueIs")
        alert("Value for asked setting is: " + msgEvent.message);

}

safari.self.tab.dispatchMessage("getSettingValue", "foo"); // ask for value
safari.self.addEventListener("message", getMessage, false); // wait for reply

Global HTML code:

function respondToMessage(messageEvent) {

    if (messageEvent.name == "getSettingValue") {

           // getItem("foo");
        var value = safari.extension.settings.getItem(messageEvent.message);
        // return value of foo to injected script
           safari.application.activeBrowserWindow.activeTab.page.dispatchMessage("settingValueIs", value);

    } 

}

safari.application.addEventListener("message",respondToMessage,false);

Hope this helps !


Initial post: I'm having the same 2nd problem as you, I can't access my settings (or secureSettings) from an injected script. In my case the script is loaded after page load, but even that way I can't use safari.extension.settings.

The only way it works is with a toolbar/button, the HTML behind that element can getItem and setItem as expected.

My conclusion is that, for some reason, injected scripts can't access settings (actually, they don't even seem to have access to the safari element). Bug or intended feature, that's left to figure out.

Mars
Take a look at the updates I've added above. I now know _why_ you can't access settings and have even indicated how to get to them (even though it's not working for me for other reasons).
Rob Wilkerson
I just found a solution for my problem, it's posted above for you or anyone else with the same issue. Maybe you can adapt it for your case !
Mars
Thanks for the update. In my case, all of the work is being done `beforeload`, so the page object is undefined which appears to mean that the `dispatchMessage()` method isn't available. I'm still looking for a workaround to that.
Rob Wilkerson
+1  A: 

It took me several days, but I think I found a workable solution using the canLoad() messaging method. My injection script retrieves settings by calling the global HTML page like this:

settings = safari.self.tab.canLoad( event );

My global HTML file, in turn, returns those settings as:

settings = {
  'setting1': safari.extension.settings.getItem( 'setting1' )
}

msgEvent.message = settings;

It's still a bit more "hacky" than I'd like. I can't seem to simply return the settings object itself, so I have to compile a new object by retrieving each setting manually. Not ideal, but it does seem to be effective.

Rob Wilkerson
+1  A: 

Like everyone else at this point, I'm still climbing the learning curve, but here's how I've handled this problem:

I have a simple extension with no chrome and one injected end script (script.js). For the purpose of loading settings I've added a simple global page (proxy.html). When script.js is injected, it sends a getSettings message to proxy.html. proxy.html responds with a setSettings message, and script.js continues initialization.

The most helpful page I've found in the docs on this topic is Messages and Proxies.

proxy.html:

<!doctype html>
<html lang="en">
<head>
  <script type="text/javascript">
    safari.application.addEventListener( "message", function( e ) {
      if( e.name === "getSettings" ) {
        e.target.page.dispatchMessage( "setSettings", {
          sort_keys: safari.extension.settings.getItem( "sort_keys" )
        } );
      }
    }, false );
  </script>
</head>
<body></body>
</html>

script.js:

( function() {
  var settings, init = function() {
    // do extension stuff
  };

  // listen for an incoming setSettings message
  safari.self.addEventListener( "message", function( e ) {
    if( e.name === "setSettings" ) {
      settings = e.message;
      init();
    }
  }, false );

  // ask proxy.html for settings
  safari.self.tab.dispatchMessage( "getSettings" );
}() )
Rick Fletcher
A: 

Can someone give a full example of the canload method? Or any method that works? What I want to do is very simple: I need a setting passed from global.html to injected.js.

I've tried simply putting a dispatchMessage command in my global.html and an event listener and message handler in my injected.js, but it just doesn't work.

Any help would be greatly appreciated.

Andrew
I'd recommend that you ask this as a separate question rather than piggybacking on an existing one. In that question, provide some of your code that isn't working and maybe we can help you figure it out.
Rob Wilkerson