I have an addon that places an ActionButton on the toolbar. When the ActionButton is clicked the code below is run.
The code opens a new tab and provides some html and js, this acts as the addon's UI.
The url of the new tab is:
resource://jid1-qljswfs6someid-at-jetpack/addon-firefox/data/html/view.html
If I copy/paste that url into another new tab manually, the html displays but the js logic is not loaded. Is there a way to do this without clicking the ActionButton? So I could maybe bookmark the addon instead of having the ActionButton take up space.
Code:
Tabs.open({
url: require("sdk/self").data.url('html/view.html'),
onReady: function onReady(tab) {
worker = tab.attach({
contentScriptFile: [
require("sdk/self").data.url.get('lib/lib1.js'),
require("sdk/self").data.url.get('js/lib1.js')
],
onMessage: function(message) {
console.log('stuff done');
}
});
}
});
In order to run it whenever the site from data.url('html/view.html') is loaded you would have to use page-mod instead of manually attaching to the document to the tab.
Your include pattern would be something like data.url('html/view.html') + "*", so it also attaches to the page if there is a hash or a query to the document.
In a Firefox add-on I need to track which tab messages are associated with. The content script will send data to the main.js. Later, when the user clicks the extension's button in the toolbar it will look for data associated with the active tab.
In Chrome extensions, when a message was received, I could ask which tab the message came from and track messages by the tab id. In Firefox, tabs have id's too, but there doesn't seem to be an easy way to access them from content scripts.
The answer depends on which way you are creating the content scripts. Below is an example main.js file for adding content scripts with PageMod.
var buttons = require('sdk/ui/button/action'),
pageMod = require('sdk/page-mod'),
data = require('sdk/self').data;
// Map of messages keyed by tab id
var messages = {};
pageMod.PageMod({
include: 'http://www.example.com',
contentScriptFile: [
data.url('my-script.js')
],
onAttach: function(worker){
// Get the tab id from the worker
var tabId = worker.tab.id;
// Save the message
worker.port.on('message', function(message){
messages[tabId] = message;
});
// Delete the messages when the tab is closed
// to prevent a memory leak
worker.on('detach', function(){
delete messages[tabId];
});
}
});
var button = buttons.ActionButton({
id: 'my-extension',
label: 'Example',
icon: {
'16': './icon-16.png',
'32': './icon-32.png',
'64': './icon-64.png'
},
onClick: function(state){
// Retrieve the message associated with the
// currently active tab, if there is one
var message = messages[tabs.activeTab.id];
// Do something with the message
}
});
I also recommend reading Content Scripts - Interacting with Page Scripts and Content Worker for a better understanding about what's going on and how to adapt it to your situation.
In our PhoneGap iOS application, we are using the InAppBrowser plugin to display some content, and we need to open a page in Safari from within the InAppBrowser.
How can we have links from within the InAppBrowser open in Safari?
From the phonegap documentation:
Opens a URL in a new InAppBrowser instance, the current browser instance, or the system browser.
var ref = window.open(url, target, options);
ref: Reference to the InAppBrowser window. (InAppBrowser)
url: The URL to load (String). Call encodeURI() on this if the URL contains Unicode characters.
target: The target in which to load the URL, an optional parameter that defaults to _self. (String)
_self: Opens in the Cordova WebView if the URL is in the white list, otherwise it opens in the InAppBrowser.
_blank: Opens in the InAppBrowser.
_system: Opens in the system's web browser.
So to answer your question, use:
window.open(your_url, '_system', opts);
Note that the domain will need to be white-listed.
Update 4/25/2014:
I think I kind of misunderstood the question (thanks to commenter #peteorpeter) -- you want to have some way to click a link in the InAppBrowser and have that open in the system browser (e.g. Mobile Safari on iOS). This is possible, but it will require some forethought and cooperation between the app developer and the person responsible for the links on the page.
When you create an IAB instance, you get a reference to it back:
var ref = window.open('http://foo.com', '_blank', {...});
You can register a few event listeners on that reference:
ref.addEventListener('loadStart', function(event){ ... });
This particular event is fired every time the URL of the IAB changes (e.g. a link is clicked, the server returns a 302, etc...), and you can inspect the new URL.
To break out into the system browser, you need some sort of flag defined in the URL. You could do any number of things, but for this example let's assume there's a systemBrowser flag in the url:
.....html?foo=1&systemBrowser=true
You'll look for that flag in your event handler, and when found, kick out to the system browser:
ref.addEventListener('loadStart', function(event){
if (event.url.indexOf('systemBrowser') > 0){
window.open(event.url, '_system', null);
}
});
Note that this is not the best method for detecting the flag in the url (could lead to false positives, possibly) and I'm pretty sure that PhoneGap whitelist rules will still apply.
Unfortunately target=_system does not work from within the InAppBrowser. (This would work if the link originated in the parent app, though.)
You could add an event listener to the IAB and sniff for a particular url pattern, as you mention in your comments, if that fit your use case.
iab.addEventListener('loadstart', function(event) {
if (event.url.indexOf("openinSafari") != -1) {
window.open(event.url, '_system');
}
}
The 'event' here is not a real browser event - it is a construct of the IAB plugin - and doesn't support event.preventDefault(), so the IAB will also load the url (in addition to Safari). You might try to handle that event within the IAB, with something like:
iab.addEventListener('loadstop', function(event) {
iab.executeScript('functionThatPreventsOpenInSafariLinksFromGoingAnywhere');
}
...which I have not tested.
This message is for clarification:
If you open an another with window.open by catching a link on loadstart, it will kill yor eventhandlers that assigned to first IAB.
For example,
iab = window.open('http://example.com', '_blank', 'location=no,hardwareback=yes,toolbar=no');
iab.addEventListener('loadstop', function(event) {console.log('stop: ' + event.url);});
iab.addEventListener('loaderror', function(event) { console.log('loaderror: ' + event.message); });
iab.addEventListener('loadstart', function(event) {
if (event.url.indexOf("twitter") != -1){
var ref2 = window.open(event.url, '_system', null);
}
});
When the second window.open executed, it will kill all the event listeners that you binded before. Also loadstop event will not be fired after that window.open executed.
I'm finding another way to avoid but nothing found yet..
window.open() doesn't work for me from within an InAppBrowser, whether or not I add a script reference to cordova.js to get support for window.open(...'_system'), so I came up with the following solution which tunnels the "external" URL back to the IAB host through the hashtag so it can be opened there.
Inside the InAppBrowser instance (I'm using AngularJS, but you can replace angular.element with jQuery or $ if you're using jQuery):
angular.element(document).find('a').on('click', function(e) {
var targetUrl = angular.element(this).attr('href');
if(targetUrl.indexOf('http') === 0) {
e.preventDefault();
window.open('#' + targetUrl);
}
});
Note that that's the native window.open above, not cordova.js's window.open. Also, the handler code assumes that all URLs that start with http should be externally loaded. You can change the filter as you like to allow some URLs to be loaded in the IAB and others in Safari.
Then, in the code from the parent that created the InAppBrowser:
inAppBrowser.addEventListener('loadstart', function(e) {
if(e.url.indexOf('#') > 0) {
var tunneledUrl = e.url.substring(e.url.indexOf('#') + 1);
window.open(tunneledUrl, '_system', null);
}
});
With this solution the IAB remains on the original page and doesn't trigger a back-navigation arrow to appear, and the loadstart handler is able to open the requested URL in Safari.
Ok, this is odd:
First I open page1.html. From page1.html I go to page2.html by link and then back to page1.html via another link. These links are just regular links with relative path and not rel="back" kind of link.
Problem is: jQuery Mobile will cache page1.html (though it doesn't cache page2.html)
If I add rel="external" to the link of page2.html then the page1 is refresh, but together, all resources is also reloaded (which not what I want).
I only want the html of page1.html to be reloaded. I added data-cache=false and data-dom-cache=false to page1.html annotation but it doesn't help.
How can I have jQuery Mobile not caching page1.html with the given scenario?
I am using a workarround that manualy removes the page based on the data-dom-cache attribute. You need to add an event handler for pagehide events and check for the domCache property of the page data
$(document).on('pagehide', function(event, ui){
var page = $(event.target);
var pageData = page.data(); // get all the data attributes (remove the data prefix and format to camel case)
if(pageData.domCache == false){
console.log("Removing Page (id: " + page.attr('id') + ", url: " + pageData.url + ")"); //Log to console for debugging
page.remove(); // remove the page
}
});
I am trying to create an add-on through Mozilla Add-On Builder. What I need to know is how to get the URL of a left clicked link in the active tab through the add-on and open it in a new tab.
I know this process involved adding an eventlistener through a page-mod and then using the tabs module, however I can't seem to get the syntax correct.
Edit: (This is what I have so far)
var Widget = require("widget").Widget;
var tabs = require('tabs');
var pageMod = require("page-mod");
exports.main = function() {
pageMod.PageMod({
include: '*',
contentScriptWhen: 'ready',
contentScript: "window.addEventListener('click', function(event) { self.port.emit( 'click',event.target.toString() )},false)",
onAttach: function(worker) {
worker.port.on("click", function(urlClicked) {
tabs.open(urlClicked);
});
}
});
};
The code you have there is mostly correct and works for me. There are two issues with your content script code however:
It needs to call event.preventDefault() to prevent the browser from following the link. Otherwise the linked page will be loaded both in the current tab and the new tab opened by your extension.
It doesn't check whether event.target is actually a link. It could be a child node of the link or it might not be a link at all.
Altogether, your content script should look like this:
window.addEventListener("click", function(event)
{
var link = event.target;
while (link && link.localName != "a")
link = link.parentNode;
if (link)
{
self.port.emit("click", link.href);
event.preventDefault();
}
}, false);
For a non-trivial content script like this, you shouldn't use contentScript parameter but rather put it into its own file in the data/ directory. You can then use contentScriptFile parameter when constructing the panel:
contentScriptFile: require("self").data.url("contentScript.js"),