Safari Extension Questions - safari

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:
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.
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?
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.

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" );
}() )

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.

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.

run into the same problem, but the answer is easier than you can imagine: include the script in your global html.
<!DOCTYPE HTML>
<script type="text/javascript" src="cleanup.js"></script>
<script>
…
</script>
then you can access the settings as described in documentation safari.extension.settings.myKey
you can also upvote #Travis, because I got the idea from his post
//EDIT:
actually I don't really know whats wrong. Calling the settings as the first command works, but not at a later time. Additionally it seems to corrupting my complete script after the 2. injection. Need verification if it's only in my (difficult?) script.
//EDIT2:
now I got it to work to get back the settings object via dispatchMessage()
in your injected.js
function gotSettings(msgEvent) {
if (msgEvent.name === "SETTINGS") {
setts = msgEvent.message;
alert(setts.mySetting1);
// run the programm
}
}
safari.self.addEventListener("message", gotSettings, false);
safari.self.tab.dispatchMessage("getSettings");
and in global.html
switch (event.name) {
case "getSettings":
// send the settings data
event.target.page.dispatchMessage("SETTINGS", safari.extension.settings);
relying on this apple documentation

Related

Global variable is undefined if assign variable in the TargetConnected method in stimulus.js

This is the location controller file that is going to access by the html code.
export default class extends Controller {
static targets = [ "visible", "map" ]
mapTargetConnected(element) {
this.name = "aaa"
}
add(event) {
console.log(this.name) // this line is logged that variable is undefined.
}
}
here is the HTML code
<%= form_with(model: #location, local: false, url: location_path(), data: {controller: 'location', action: 'ajax:beforeSend->location#add'}) do |form| %>
....
<% end %>
This is the code regarding form submit via ajax request. if i access the this.name variable inside the add method or click event its says the variable is undefined… but if i same name variable assign it in connect() method than it’s working…
but i want to assign variable at targetConnected method and use it in the add action method.Please suggest any solution or let me know if i'm doing wrong.
Most likely the add event is being triggered before the mapTargetConnected has run.
Stimulus will go through the DOM and match elements and their targets and then trigger the relevant someTargetConnected and connect lifecycle methods once the controller is set up.
However, this is not instant and there may be some nuance to how the timing works when you are working with other events.
You will need to work out when the actual map target is being added to the DOM and possibly do some logging to check that timing compared to when the ajax:beforeSend event triggers.
Sometimes, adding a setTimeout can assist as it will ensure that the code provided to it runs 'last' (there is some nuance to this, technically it is the next event cycle).
For example
add(event) {
// here the mapTargetConnected may not have run
setTimeout(() => {
// by this point, mapTargetConnected has hopefully now run
console.log(this.name);
});
}
It is hard to offer more help without a bit more specifics on what ajax:beforeSend is and when it triggers, along with what actually adds the map target to the DOM. It may be more helpful to write this question with the initially rendered HTML output (with the minimum parts to help guide the question).
In general, it is good to remember that in the browser, things do not happen instantly, while they may be fast there can be timing issues to be aware of.

using dijit.byId w dojox.mobile widgets

I'm dynamically building a series of dojox.mobile.ListItem widgets under a statically defined dojox.mobile.RoundRectList widget via this function...
function displayOpps(items) {
// Create the list container that will hold application names
var rrlOppsContainer = dijit.byId("rrlOpps");
// Add a new item to the list container for each element in the server respond
for (var i in items){
// Create and populate the list container with applications' names
var name = items[i].CustName + " - " + items[i].OppNbr;
var liOpps = new dojox.mobile.ListItem({
label: name,
moveTo: "sv3OppDetail"
});
// Add the newly created item to the list container
rrlOppsContainer.addChild(liOpps);
}
}
When I run this code during onLoad() in my html file, I get the following error using Chrome's dev tools...
Uncaught TypeError: Object # has no method 'byId'
I've read numerous articles around this topic and it appears that lots of folks have this problem, but each that I have found are in relation to some other technology (e.g., Spring MVC, etc) and I'm attempting to use it w a dojox.mobile based app. That said, I attempted to mimic some of the solutions brought up by others by including this in my html file, and it still doesn't work...
<script type="text/javascript"
data-dojo-config="isDebug: true, async: true, parseOnLoad: true"
src="dojo/dojo.js">
dojo.require("dojox.mobile.RoundRectList")
</script>
What am I doing wrong?
Thank you in advance for your time and expertise.
If you are using Dojo 1.7+, you probably just forgot to require the "dijit/registry" module. This where the byId function is defined. When you use desktop widgets, this is loaded indirectly by other base modules, but with dojox/mobile you must load it explicitly (because dojox/mobile loads only a very minimal set of modules by default, to minimize code footprint).
Depending on how you wrote your application, do this:
dojo.require("dijit.registry"); // legacy (pre-1.7) loader syntax
...
var rrlOppsContainer = dijit.byId("rrlOpps");
...
or this:
require(["dijit/registry", ...], function(registry, ...){ // 1.7+ AMD loader syntax
...
var rrlOppsContainer = registry.byId("rrlOpps");
...
});
Note also that your second code sample tries to use asynchronous loading (async: true) while it uses the legacy loader syntax. This won't work, to get async loading you must use the AMD syntax.

How to precompile processingjs sketch to speed load times?

The loading times of my processingjs webpage are getting pretty hairy. How can I precache the compilation of processing to javascript?
It would be acceptable for my application to compile on first entering the webpage (maybe keeping the result in the local store?) and then reuse the compilation on subsequent loads.
There's two ways to drive down load time as experienced by the user. The first is using precompiled sketches, which is relatively easy: github repo, or even just download the master branch using github's download button (https://github.com/processing-js/processing-js), and then look for the "./tools/processing-helper.html" file. This is a helper page that lets you run or compile sketches to the JavaScript source that Processing.js uses. You will still need to run this alongside Processing, since it ties into the API provided, but you can use the "API only" version for that. Take the code it generates, prepend "var mySketch = ", and then do this on your page:
<script src="processing.api.js"></script>
<script>
function whenImGoodAndReady() {
var mySketch = (function.....) // generated by Processing.js helper
var myCanvas = document.getElementById('mycanvas');
new Processing(myCanvas, mySketch);
}
</script>
Just make sure to call the load function when, as the name implies, you're ready to do so =)
The other is to do late-loading, if you have any sketches that are initially off-screen.
There's a "lazy loading" extension in the full download for Processing.js - you can include that on your page, and it will make sketches load only once they're in view. That way you don't bog down the entire page load.
Alternatively, you can write a background loader that does the same thing as the lazy loading extension: turn off Processing.init, and instead gather all the script/canvas elements that represent Processing sketches, then loading them on a timeout using something like
var sketchList = [];
function findSketches() {
/* find all script/canvas elements */
for(every of these elements) {
sketchList.append({
canvas: <a canvas element>,
sourceCode: <the sketch code>
});
}
// kickstart slowloading
slowLoad();
}
function slowLoad() {
if(sketchList.length>0) {
var sketchData = sketchList.splice(0,1);
try {
new Processing(sketchData.canvas, sketchData.sourceCode);
setTimeout(slowLoad, 15000); // load next sketch in 15 seconds
} catch (e) { console.log(e); }
}
}
This will keep slow-loading your sketches until it's run out.

SafariExtension messageEvent

i'm trying to make a SafariExtension but i have some issue with the messaging Api, in fact the example in the doc don't work, this: Safari Message Proxies
so What work and what don't work ?
I can call the function on the global page from a injected script.
I can't send a response to the injected script.
here what i have:
injected:
safari.self.tab.dispatchMessage("foo", "bar");
no need more, the bug is in the global html.
global:
safari.application.addEventListener("message", function ( e ) {
if (e.name != 'foo')
return false;
e.target.page.dispatchMessage("bar", 'foo'); <-- Undefined on page ...
},false);
as i mention the 4th line on the global page always fails, so i can't send back an answer
to the injected script ...
no clue on the documentation since this is almost extracted form the doc.
You need to include a listener in the target page. For example...
safari.self.addEventListener("message", function(e) {
if(e.name == 'bar') {
alert(e.message);
// 'foo' would be alerted to the user, as it is the message content.
};
}, false);
Messages are one-way transactions, so sending a message to the global page and receiving a message on the target page are two completely separate events. A listener is needed on both sides of the exchange.

How do I get data from a background page to the content script in google chrome extensions

I've been trying to send data from my background page to a content script in my chrome extension. i can't seem to get it to work. I've read a few posts online but they're not really clear and seem quite high level. I've got managed to get the oauth working using the Oauth contacts example on the Chrome samples. The authentication works, i can get the data and display it in an html page by opening a new tab.
I want to send this data to a content script.
i'm having a lot of trouble with this and would really appreciate if someone could outline the explicit steps you need to follow to send data from a bg page to a content script or even better some code. Any takers?
the code for my background page is below (i've excluded the oauth paramaeters and other )
` function onContacts(text, xhr) {
contacts = [];
var data = JSON.parse(text);
var realdata = data.contacts;
for (var i = 0, person; person = realdata.person[i]; i++) {
var contact = {
'name' : person['name'],
'emails' : person['email']
};
contacts.push(contact); //this array "contacts" is read by the
contacts.html page when opened in a new tab
}
chrome.tabs.create({ 'url' : 'contacts.html'}); sending data to new tab
//chrome.tabs.executeScript(null,{file: "contentscript.js"});
may be this may work?
};
function getContacts() {
oauth.authorize(function() {
console.log("on authorize");
setIcon();
var url = "http://mydataurl/";
oauth.sendSignedRequest(url, onContacts);
});
};
chrome.browserAction.onClicked.addListener(getContacts);`
As i'm not quite sure how to get the data into the content script i wont bother posting the multiple versions of my failed content scripts. if I could just get a sample on how to request the "contacts" array from my content script, and how to send the data from the bg page, that would be great!
You have two options getting the data into the content script:
Using Tab API:
http://code.google.com/chrome/extensions/tabs.html#method-executeScript
Using Messaging:
http://code.google.com/chrome/extensions/messaging.html
Using Tab API
I usually use this approach when my extension will just be used once in a while, for example, setting the image as my desktop wallpaper. People don't set a wallpaper every second, or every minute. They usually do it once a week or even day. So I just inject a content script to that page. It is pretty easy to do so, you can either do it by file or code as explained in the documentation:
chrome.tabs.executeScript(tab.id, {file: 'inject_this.js'}, function() {
console.log('Successfully injected script into the page');
});
Using Messaging
If you are constantly need information from your websites, it would be better to use messaging. There are two types of messaging, Long-lived and Single-requests. Your content script (that you define in the manifest) can listen for extension requests:
chrome.extension.onRequest.addListener(function(request, sender, sendResponse) {
if (request.method == 'ping')
sendResponse({ data: 'pong' });
else
sendResponse({});
});
And your background page could send a message to that content script through messaging. As shown below, it will get the currently selected tab and send a request to that page.
chrome.tabs.getSelected(null, function(tab) {
chrome.tabs.sendRequest(tab.id, {method: 'ping'}, function(response) {
console.log(response.data);
});
});
Depends on your extension which method to use. I have used both. For an extension that will be used like every second, every time, I use Messaging (Long-Lived). For an extension that will not be used every time, then you don't need the content script in every single page, you can just use the Tab API executeScript because it will just inject a content script whenever you need to.
Hope that helps! Do a search on Stackoverflow, there are many answers to content scripts and background pages.
To follow on Mohamed's point.
If you want to pass data from the background script to the content script at initialisation, you can generate another simple script that contains only JSON and execute it beforehand.
Is that what you are looking for?
Otherwise, you will need to use the message passing interface
In the background page:
// Subscribe to onVisited event, so that injectSite() is called once at every pageload.
chrome.history.onVisited.addListener(injectSite);
function injectSite(data) {
// get custom configuration for this URL in the background page.
var site_conf = getSiteConfiguration(data.url);
if (site_conf)
{
chrome.tabs.executeScript({ code: 'PARAMS = ' + JSON.stringify(site_conf) + ';' });
chrome.tabs.executeScript({ file: 'site_injection.js' });
}
}
In the content script page (site_injection.js)
// read config directly from background
console.log(PARAM.whatever);
I thought I'd update this answer for current and future readers.
According to the Chrome API, chrome.extension.onRequest is "[d]eprecated since Chrome 33. Please use runtime.onMessage."
See this tutorial from the Chrome API for code examples on the messaging API.
Also, there are similar (newer) SO posts, such as this one, which are more relevant for the time being.