I have just started making an add-on with Firefox. This add-on is written in order to open a local folder outside FF. The folder is already opened by the browser. And in the context menu you will see an option to open the folder outside the browser (I use Win7).
This is the code that I used:
var contextMenu = require("context-menu");
var menuItem = contextMenu.Item({
label: "Open Local File",
context: contextMenu.URLContext("file:///*"),
contentScript: 'self.on("click", function() {'+
'openDir(document.URL);'+
'});',
});
function openDir(val)
{
if (val == "")
{
alert("Directory not defined");
return;
}
if(navigator.userAgent.indexOf("Firefox") == -1)
{
alert("Currently active folder links supported only for Mozilla Firefox web browser");
return;
}
netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
var localFile =
Components.classes["#mozilla.org/file/local;1"]
.createInstance(Components.interfaces.nsILocalFile);
var env =
Components.classes["#mozilla.org/process/environment;1"]
.createInstance(Components.interfaces.nsIEnvironment);
var systemRoot = env.get("SystemRoot");
if (systemRoot == "")
{
alert("Unable to retrieve SystemRoot environment variable");
}
localFile.initWithPath(systemRoot + "\\explorer.exe");
var process =
Components.classes["#mozilla.org/process/util;1"]
.createInstance(Components.interfaces.nsIProcess);
process.init(localFile);
process.run(false, Array(val), 1);
}
Now the problem is that when I save the add-on under http://builder.addons.mozilla.org/... it cannot be compiled. Instead a red box shows up with the message "XPI not built". This is the log:
GET https://builder.addons.mozilla.org/xpi/test/.../ 404 NOT FOUND 236ms
What should I do?
The modified code:
var contextMenu = require("context-menu");
var menuItem = contextMenu.Item({
label: "Open Local File",
contentScript: 'self.on("context", function(node)'+
'{'+
' return node.ownerDocument.URL.indexOf("file:///") == 0;'+
'});'+
'self.on("click", function(node)' +
'{' +
' self.postMessage(node.ownerDocument.URL);' +
'});',
onMessage: function(url)
{
openDir(url);
}
}) ;
function openDir(val)
{
var {Cc, Ci} = require("chrome");
var ioService = Cc["#mozilla.org/network/io-service;1"]
.getService(Ci.nsIIOService);
var uri = ioService.newURI(val, null, null);
if (uri instanceof Ci.nsIFileURL && uri.file.isDirectory())
{
uri.file.QueryInterface(Ci.nsILocalFile).launch();
}
}
The Add-on Builder web application is there to package up your code and create an extension - Firefox merely installs the extension once it is done. You have an issue with the Add-on Builder, not one with Firefox. I can only recommend you to file a bug report.
Your code has numerous issues however:
It seems that you want to show your context menu item on pages using the file:/// URL scheme, not on links pointing to files. There is no predefined context for this, you will have to use the content script (see Specifying Contexts > In Content Scripts. Something like:
self.on("context", function(node)
{
return node.ownerDocument.URL.indexOf("file:///") == 0;
});
Function openDir() isn't defined in the content script, it is defined in your extension. This means that you have to send a message back to your extension with the URL (see last example in Handling Menu Item Clicks). Something like this:
contentScript: 'self.on("context", ...);' +
'self.on("click", function(node, data)' +
'{' +
' self.postMessage(node.ownerDocument.URL);' +
'});',
onMessage: function(url)
{
openDir(url);
}
Checking whether your code is running in Firefox is pointless - currently, the Add-on SDK only supports Firefox.
You should not use the deprecated PrivilegeManager.enablePrivilege method - your code is already running with highest privileges. You will need to use chrome authority however, extensions built with the Add-on SDK by default cannot access low-level functionality.
You shouldn't run Windows Explorer directly, use nsILocalFile.launch(), for directories it will run Windows Explorer (or whatever action is defined in the operating system to open directories). Altogether the code in openDir() should look like this:
var {Cc, Ci} = require("chrome");
var ioService = Cc["#mozilla.org/network/io-service;1"]
.getService(Ci.nsIIOService);
var uri = ioService.newURI(val, null, null);
if (uri instanceof Ci.nsIFileURL && uri.file.isDirectory())
uri.file.QueryInterface(Ci.nsILocalFile).launch();
Documentation: nsIIOService, nsIFileURL.
Related
I have an extension, functional on Chrome, that monitors the active Tab for URL changes.
Specifically, I need to detect when the URL changes, but there is no new page load or navigation. Some sites do this (e.g. when you click to view another video on YouTube).
On Chrome, I accomplished this with:
chrome.tabs.onUpdated.addListener(function(tabId, changeInfo, tab) {
if (changeInfo && changeInfo.status == "complete") {
//do stuff here
}
});
How do I detect such changes in a Firefox add-on?
I've been told to use: Listening to events on all tabs, but I couldn't put it together. One of the problems was that gBrowser was not defined in the extension.
What am I doing wrong?
Is there a simpler way?
Use ProgressListener to be notified about location changes.
To install a listener, convert SDK tab to its raw (old) representation using viewFor.
Backward conversion is possible with modelFor and getTabForContentWindow.
const tabs = require("sdk/tabs");
const {viewFor} = require('sdk/view/core');
const {modelFor} = require('sdk/model/core');
const {getBrowserForTab, getTabForContentWindow} = require("sdk/tabs/utils");
const {Ci, Cu} = require("chrome");
Cu.import("resource://gre/modules/XPCOMUtils.jsm", this);
var progressListener = {
QueryInterface: XPCOMUtils.generateQI([Ci.nsIWebProgressListener, Ci.nsISupportsWeakReference]),
onLocationChange: function(aProgress, aRequest, aURI) {
var highLevel= modelFor(getTabForContentWindow(aProgress.DOMWindow));
console.log("onLocationChange ", highLevel.url);
}
};
tabs.on('open', function(newTab) {
var lowLevel = viewFor(newTab);
var browser = getBrowserForTab(lowLevel);
browser.addProgressListener(progressListener);
});
Don't forget to remove listeners on extension unload. Tab listeners are removed automagically, but ProgressListeners won't be.
Inspired by
Converting to chrome windows
If you're using the add-on SDK, you're looking at the wrong docs. Here are the tab docs.
As stated there, you create a listener like so:
var tabs = require("sdk/tabs");
// Listen for tab openings.
tabs.on('open', function onOpen(tab) {
myOpenTabs.push(tab);
});
// Listen for tab content loads.
tabs.on('ready', function(tab) {
console.log('tab is loaded', tab.title, tab.url);
});
All the docs you look at should be a subset of developer.mozilla.org/en-US/Add-ons/SDK.
I find that the activate and pageshow events, between the two of them, cover all changes in URL that I can conjure up between switching tabs, opening pages in a new tab, closing tabs, refreshing pages, and typing in new URL's.
var updateURL = function (tab) {
var oldURL = url;
var url = tab.url;
console.log(url);
};
tabs.on("activate", updateURL);
tabs.on("pageshow", updateURL);
I'm porting a Chrome extension to a Firefox extension and due to the nature of the website that it runs on, I need to monitor the pushState.
Chrome Extensions has a handy way to handle this: chrome.webNavigation.onHistoryStateUpdated. The way that I use it in the Chrome extension is as follows:
chrome.webNavigation.onHistoryStateUpdated.addListener(function(details) {
var tabUrl = details.url;
if (isTabUrlValid(tabUrl)) {
$.get(tabUrl, function(data) {
var videoUrl = $(data).find('meta[itemprop=contentURL]').prop('content');
videoUrl = validateUrl(videoUrl);
videoUrl5k = make5kUrl(videoUrl);
});
}
});
I need to do the same thing for the Firefox Extension, but I haven't found any good answers. I've tried doing the answer mentioned here: How to get notified about changes of the history via history.pushState?
(function(history) {
var pushState = history.pushState;
history.pushState = function(state) {
if (typeof history.onpushstate == "function") {
history.onpushstate({state: state});
}
var tabUrl = tabs.activeTab.url;
console.log("UPDATED TAB URL: " + tabUrl);
if (isTabUrlValid(tabUrl)) {
$.get(tabUrl, function(data) {
var videoUrl = $(data).find('meta[itemprop=contentURL]').prop('content');
videoUrl = validateUrl(videoUrl);
videoUrl5k = make5kUrl(videoUrl);
});
}
return pushState.apply(history, arguments);
};
})(window.history);
The problem is that when I do cfx run it complains that history/window is undefined and therefore never gets detected. I think this is due to it being within the SDK, but I don't know of a good workaround.
Any thoughts?
Edit: I looked at #willma's answer below and I don't think that would work for me. The issue is that the URL is updated via pushState and the DOM is not... Is there any good way replicate what I do in the chrome extension?
Edit: Here's the pageMod portion
pageMod.PageMod({
attachTo: 'top', // Don't attach to iFrames --> http://goo.gl/b6b1Iv
include: [URLs],
contentScriptFile: [data.url("jquery-2.1.1.min.js"),
data.url("csScript.js")],
onAttach: function(worker) {
worker.port.on('url', function(url) {
var videoUrl = validateUrl(url);
videoUrl5k = make5kUrl(videoUrl);
console.log("--5K URL--: " + videoUrl5k);
});
}
});
That history code needs to get injected into a tab using a content script. Right now your logic says when the history event occurs, check to see if the tab URL is valid.
In Firefox, the logic will be the other way around: when a tab is opened, check if its URL is valid, and if so, then attach a script to it that will monitor for the history event. To do so you'll need to use a Page Mod.
Edit: All the code
One key concept you're missing is the difference between a content script and a main/library script. The library scripts are stored in lib and have access to all the SDK modules, but don't have access to the DOM, window object… The content scripts are stored in data, are injected into a page using the PageMod or tabs modules, can access the dom and window objects, but have no access to any SDK modules. Content scripts are essentially like the page scripts you'd attach your standard HTML page (with <script></script>) with the caveats that they can't share variables other page scripts but they can communicate with the main scripts.
The only reason I bring this up is because your initial problem was trying to access the window object from a main script and the problem in your fiddle is that you're trying to access the tabs module inside a content script. It's worth reading the topmost link in this answer if this is still confusing.
main.js
const { PageMod } = require('sdk/page-mod');
var sendXHR = function(url) {
// Do something with the new URL
// See Request Module docs (below) for sending XHRs from main script.
}
const pageMod = PageMod({
attachTo: 'top',
include: '*',
onAttach: function(worker) {
worker.port.on('newURL', sendXHR);
}
});
content.js
var sendNewUrlToMain = function() {
self.port.emit('newURL', location.href);
}
var pushState = window.history.pushState;
window.history.pushState = function(state) {
if (typeof history.onpushstate == "function") {
history.onpushstate({state: state});
}
sendNewUrlToMain();
return pushState.apply(history, arguments);
}
window.addEventListener('hashchange', sendNewUrlToMain);
Here are the request module docs, for making XHRs.
NB: if you don't want to use the request module (the only reason being that you already have standard XHR code for your chrome extension and don't want to take the time to learn/rewrite that code), you can send a standard XHR from the content script, but in doing so, you risk allowing the user to close the tab and thus destroy the script before your XHR callbacks are executed.
I have installed a FF extension called FireSSH which is basically a terminal in Firefox. When it runs the URL in the address bar is chrome://firessh/content/firessh.xul - I would like to make a link on a webpage that will open FireSSH but all atempts thus far have failed, for example I have tried:
<script>
function myFunction()
{
window.open("chrome://firessh/content/firessh.xul");
}
</script>
firessh>
and also:
firessh
and simply:
firessh
Any help appreciated.
B.
listen to all pages and when you find pages of interest, from your addon do,
var link = gBrowser.contentDocument.querySelector('#myLink');
link.addEventListener('click',openXulWindow,false);
//this openXulWindow opens a regular browser window
function openXulWindow() {
var sa = Cc["#mozilla.org/supports-array;1"].createInstance(Ci.nsISupportsArray);
var wuri = Cc["#mozilla.org/supports-string;1"].createInstance(Ci.nsISupportsString);
wuri.data = 'http://www.bing.com/';
let aCharset = 'UTF-8';
let charset = Cc["#mozilla.org/supports-string;1"].createInstance(Ci.nsISupportsString);
charset.data = "charset=" + aCharset;
var aAllowThirdPartyFixup = false;
var allowThirdPartyFixupSupports = Cc["#mozilla.org/supports-PRBool;1"].createInstance(Ci.nsISupportsPRBool);
allowThirdPartyFixupSupports.data = aAllowThirdPartyFixup;
sa.AppendElement(wuri);
sa.AppendElement(charset);
sa.AppendElement(allowThirdPartyFixupSupports);
let features = "chrome,dialog=no,all";
if (PrivateBrowsingUtils.permanentPrivateBrowsing) {
features += ",private";
} else {
features += ",non-private";
}
Services.ww.openWindow(null, 'chrome://browser/content/browser.xul', null, features, sa);
}
Web content is not allowed to access, link and/or script chrome: content due to security concerns. chrome: runs at a higher security level, and allowing web content to unrestrictedly call into chrome: would essentially be a privilege escalation vulnerability.
I am trying to create an addon which after a user logs in to my site I will try and read the session id of that domain and use it for further interactions with my addon. I use the online addon builder and I have this code which I want to read the cookies:
var data = require("self").data;
var {Cc, Ci} = require("chrome");
var cookieMgr = Cc["#mozilla.org/cookiemanager;1"].getService(Ci.nsICookieManager);
var cm = require("context-menu");
cm.Item({
label: "My Menu Item",
contentScript: 'self.on("click", function (node, data) {' +
'for (var e = cookieMgr.enumerator; e.hasMoreElements();) { ' +
'var cookie = e.getNext().QueryInterface(Ci.nsICookie); ' +
' console.log(cookie.host + ";" + cookie.name + "=" + cookie.value + "\\n");'+
'}});'
});
but it throws an error every time I click on the 'My Menu Item' button, saying cookieMgr, the variable is not defined.
The content script context is entirely disconnected from the addon script context. It's not so easy to grasp this when you're using contentScript, but if you put it in another file and use it through a contentScriptFile it becomes more obvious. Content scripts are run in the context of the document, while addon scripts aren't.
Is there any way to track when a page is bookmarked or downloaded in Firefox? I
mean is there any event that is triggered on bookmarking or
downloading a page? I am using Add-on SDK for developing Add-on.
If not, then kindly suggest me some workarounds.
The browser window has <command> elements that get triggered when the user bookmarks or downloads a page. The former has ID Browser:AddBookmarkAs, the latter Browser:SavePage. The Add-on SDK itself doesn't give you access to them, so you need to use the chrome package to access XPCOM directly. Something like this:
// Add listener to all existing browser windows
var {Cc, Ci} = require("chrome");
var mediator = Cc["#mozilla.org/appshell/window-mediator;1"]
.getService(Ci.nsIWindowMediator);
var enumerator = mediator.getEnumerator("navigator:browser");
while (enumerator.hasMoreElements())
listenToWindow(enumerator.getNext().QueryInterface(Ci.nsIDOMWindow));
// Get notified when new browser windows open
var observers = require("observer-service");
observers.add("chrome-document-global-created", function(window)
{
if (window instanceof Ci.nsIDOMWindow && window.location.href == "chrome://browser/content/browser.xul")
listenToWindow(window);
});
function listenToWindow(window)
{
window.document
.getElementById("Browser:AddBookmarkAs")
.addEventListener("command", onBookmark, false);
window.document
.getElementById("Browser:SavePage")
.addEventListener("command", onSavePage, false);
}
This code isn't tested so there might be minor issues but the overall concept should be correct.
Edit: Actually, the same seems to be simpler if you use the internal window-utils package. Not sure whether the API provided by this package is stable however.
var windows = require("window-utils");
for (window in windows.browserWindowIterator)
listenToWindow(window);
var observers = require("observer-service");
observers.add("chrome-document-global-created", function(window)
{
if (window instanceof Ci.nsIDOMWindow && windows.isBrowser(window))
listenToWindow(window);
});
It is all in the addon sdk documentation. Although I must admit I did not see it the first time around.
https://addons.mozilla.org/en-US/developers/docs/sdk/latest/dev-guide/tutorials/event-targets.html
The following example is from the documentation.
Note that I had to add Cr to the require to make it work
as well as substitute Components.interfaces by Ci in the generateQI() call.
var {Cc, Ci, Cu, Cr} = require("chrome");
Cu.import("resource://gre/modules/XPCOMUtils.jsm", this);
var bookmarkService = Cc["#mozilla.org/browser/nav-bookmarks-service;1"]
.getService(Ci.nsINavBookmarksService);
var bookmarkObserver = {
onItemAdded: function(aItemId, aFolder, aIndex) {
console.log("added ", bookmarkService.getBookmarkURI(aItemId).spec);
},
onItemVisited: function(aItemId, aVisitID, time) {
console.log("visited ", bookmarkService.getBookmarkURI(aItemId).spec);
},
QueryInterface: XPCOMUtils.generateQI([Ci.nsINavBookmarkObserver])
};
exports.main = function() {
bookmarkService.addObserver(bookmarkObserver, false);
};
exports.onUnload = function() {
bookmarkService.removeObserver(bookmarkObserver);
}