Referring to this question: Add-on Builder: ContentScript and back to Addon code?
Here is my addon code:
var widget = widgets.Widget({
id: "addon",
contentURL: data.url("icon.png"),
onClick: function() {
var workers = [];
for each (var tab in windows.activeWindow.tabs) {
var worker = tab.attach({contentScriptFile: [data.url("jquery.js"), data.url("myScript.js")]});
workers.push(worker);
}
}
});
And here is myScript.js:
var first = $(".avatar:first");
if (first.length !== 0) {
var url = first.attr("href");
self.port.emit('got-url', {url: url});
}
Now that I have multiple workers where do I put
worker.port.on('got-url', function(data) {
worker.tab.url = data.url;
});
Since in the other question I only had one worker but now I have an array of workers.
The code would be:
// main.js:
var data = require("self").data;
var windows = require("windows").browserWindows;
var widget = require("widget").Widget({
id: "addon",
label: "Some label",
contentURL: data.url("favicon.png"),
onClick: function() {
//var workers = [];
for each (var tab in windows.activeWindow.tabs) {
var worker = tab.attach({
contentScriptFile: [data.url("jquery.js"),
data.url("inject.js")]
});
worker.port.on('got-url', function(data) {
console.log(data.url);
// worker.tab.url = data.url;
});
worker.port.emit('init', true);
console.log("got here");
//workers.push(worker);
}
}
});
// inject.js
$(function() {
self.port.on('init', function() {
console.log('in init');
var first = $(".avatar:first");
if (first.length !== 0) {
var url = first.attr("href");
console.log('injected!');
self.port.emit('got-url', {url: url});
}
});
});
Edit: sorry, should have actually run the code, we had a timing issue there where the content script was injected before the worker listener was set up, so the listener was not yet created when the 'got-url' event was emitted. I work around this by deferring any action in the content script until the 'init' event is emitted into the content script.
Here's a working example on builder:
https://builder.addons.mozilla.org/addon/1045470/latest/
The remaining issue with this example is that there is no way to tell if a tab has been injected by our add-on, so we will 'leak' or use more memory every time the widget is clicked. A better approach might be to inject the content script using a page-mod when it is loaded, and only emit the 'init' event in the widget's onclick handler.
Related
My web page has
var bc = new BroadcastChannel('Consumer');
bc.onmessage = function(event) {
alert("a");
}
bc.postMessage("hello");
It broadcasts a message, and the page is also required to receive the same message.
However it doesn't work. Did I miss anything?
You can create two instances of BroadcastChannel on your page. One can act as a broadcaster for messages, the other one for receiving messages.
var broadcaster = new BroadcastChannel('Consumer');
var messageReceiver= new BroadcastChannel('Consumer');
messageReceiver.onmessage = function(event) {
alert(event.data);
}
broadcaster.postMessage("hello");
See this in action: https://jsfiddle.net/h56d3y27/
Or wrapped in a reusable class:
(note: class is not supported by all browsers. See : https://caniuse.com/#search=class for browser compatibility)
class AllInclusiveBroadcaster {
constructor(listener, channelName) {
if (!channelName) channelName = "channel";
this.broadcaster = new BroadcastChannel(channelName);
this.messageReceiver = new BroadcastChannel(channelName);
this.messageReceiver.onmessage = (event) => {
listener(event.data);
}
}
postmessage(data) {
this.broadcaster.postMessage(data);
}
}
var broadcaster = new AllInclusiveBroadcaster((data) => alert(data));
broadcaster.postmessage("Hello BroadcastChannel");
See this also in action a JSFiddle
You could dispatch an event (call it what you like) to, say, document, with the same data ... then have a single handler that listens for BroadcastChannel messages and to the event name you created above
in the following, the code creates and listens for fakeBroadcastMessage
created a function to send both the bc message and the "local" message
var bc = new BroadcastChannel('Consumer');
function handleBroadcastMessage(event) {
// do things here
}
bc.addEventHandler('message', handleBroadcastMessage);
document.addEventListener('fakeBroadcastMessage', handleBroadcastMessage);
function sendMessage(data) {
bc.postMessage(data);
var ev = new Event('fakeBroadcastMessage');
ev.data = data;
document.dispatchEvent(ev);
}
sendMessage('hello');
Is it possible to listen to the loading of images (or stylesheets) via the Mozilla Add-On SDK?
Having the user load a new URL can be found via the Page-Mod module, AJAX calls via overriding XMLHttpRequest.prototype.*().
Yet both only listen to loading of entirely new pages, not to the attached images of a page. Also, the image source might be changed for example in Javascript.
(It might be possible to use a http-on-modify-request, as pointed to here, but how can you access the nsIHttpChannel's URL and parameters?)
You can listen to every network request via
var {Cc, Ci} = require("chrome");
var httpRequestObserver = {
observe: function(subject, topic, data) {
if (topic == "http-on-modify-request") {
var httpChannel = subject.QueryInterface(Ci.nsIHttpChannel);
var myURL = httpChannel.URI.spec;
console.log("url: " + myURL);
}
},
register: function() {
var observerService = Cc["#mozilla.org/observer-service;1"]
.getService(Ci.nsIObserverService);
observerService.addObserver(this, "http-on-modify-request", false);
},
unregister: function() {
var observerService = Cc["#mozilla.org/observer-service;1"]
.getService(Ci.nsIObserverService);
observerService.removeObserver(this, "http-on-modify-request");
}
};
httpRequestObserver.register();
exports.onUnload = function(reason) {
httpRequestObserver.unregister();
};
See also Firefox Addon observer http-on-modify-request not working properly.
URI access
The nsIHttpChannel extends nsIChannel, which has a URI Attribute of type nsIURI, which has a spec attribute that contains the whole URL (including schema, parameters, ref, etc).
I'm trying to create something like a dynamic page, but I have this problem. When I fire history.pushState it creates large amount of history entries, even though the action that fires it is run only once. My code is as follows:
var url = 'http://localhost:8888/depeche-mode/violator'; // example url
var plainUrl = url + '/?plain',
startUrl = 'http://localhost:8888/',
newUrl = url.replace(startUrl, '#/');
$('#content').animate({
opacity: 0
}, 250, function() {
$('#content').load(plainUrl +' #content > *', function(response) {
$('#content').animate({opacity:1}, 250, function() {
document.title = pageTitle;
Posts.historyHash(newUrl);
});
});
});
edit:
var Posts = {
historyHash: function(newUrl) {
window.location.hash = newUrl;
$(window).bind('hashchange', function() {
var url = window.location.hash,
nohash = url.replace('#',''),
properUrl = 'http://localhost:8888/'+nohash;
history.pushState('','',newUrl);
});
}
}
The problem is very serious when I want to use Back button in my browser - I need to click it couple of times before I actually get to change the url. What can I do?
You're calling historyHash when you load content, and historyHash registers an event handler on window — every time you call it. So you end up with a bunch of event handlers for the hashchange event.
You presumably only want one. Either just register one, or unregister the previous ones when registering a new one.
I can't quite tell which of those you want, but as the handler uses the newUrl, it may well be that you want to unregister previous handlers. If so, probably best to use an event namespace so you only unregister your own handlers:
var Posts = {
historyHash: function(newUrl) {
window.location.hash = newUrl;
$(window)
.unbind('hashchange.historyhash') // Out with the old
.bind('hashchange.historyhash', function() { // In with the new
var url = window.location.hash,
nohash = url.replace('#',''),
properUrl = 'http://localhost:8888/'+nohash;
history.pushState('','',newUrl);
});
}
}
Although looking at it, you could just use a single handler and remember newUrl on Posts:
var Posts = {
historyHash: function(newUrl) {
window.location.hash = newUrl;
this.newUrl = newUrl;
}
};
$(window).bind('hashchange', function() {
var url, nohash, properUrl;
if (Posts.newUrl) {
url = window.location.hash,
nohash = url.replace('#',''),
properUrl = 'http://localhost:8888/'+nohash;
history.pushState('','',Posts.newUrl);
}
});
I am using this code from the SDK Tutorial Website:
var sidebar = require("sdk/ui/sidebar").Sidebar({
id: 'my-sidebar',
title: 'My Sidebar',
url: require("sdk/self").data.url("sidebar.html"),
onAttach: function (worker) {
// listen for a "ready" message from the script
worker.port.on("ready", function() {
// send an "init" message to the script
worker.port.emit("init", "message from main.js");
});
}
});
This works great. The problem is now that this only allows me to send something via the worker.port onAttach (and on 3 other events for the sidebar).
What I need is to use the emit function outside the scope of this sidebar. For example if I use in the same main.js an listener for a tab like
tabs.on('ready', function(tab) {
sidebar.worker.port.emit("init", "message from main.js");
}
This is not working. I also tried
sidebar.port.emit("init", "message from main.js");
or
worker.port.emit("init", "message from main.js");
Without success.
I have also tried to put the tab listener inside the onAttach of the sidebar (so a listener inside a listener) but that also is not working.
Does anybody have an idea on that one?
thanks.
Use the method described here:
var workers = [];
function detachWorker(worker, workerArray) {
var index = workerArray.indexOf(worker);
if(index != -1) {
workerArray.splice(index, 1);
}
}
var sidebar = require("sdk/ui/sidebar").Sidebar({
...
onAttach: function(worker) {
workers.push(worker);
worker.on('detach', function () {
detachWorker(this, workers);
});
}
});
Then to emit on open sidebars do:
workers.forEach(worker => {
worker.port.emit('some-message', data);
})
var tabs = require("sdk/tabs");
var iofile = require("sdk/io/file");
var widgets = require("sdk/widget");
var selection = require("sdk/selection");
function console_log(text) {
console.log(selection.text);
}
function print(text) {
console.log(text);
}
function dir_object(object_to_parse) {
var name = '';
for (name in object_to_parse) {
print(name);
}
}
function write_text(filename, text) {
var fh = iofile.open(filename, 'w');
var content = fh.read();
dir_object(fh);
selected_text = text + "\n";
fh.write(selected_text);
fh.flush();
fh.close()
}
function select_text_handler() {
write_text('/tmp/foo', selection.text);
}
var widget = widgets.Widget({
id: "scribus-link",
label: "Scribus website",
contentURL: "http://www.mozilla.org/favicon.ico",
onClick: function() {
}
});
selection.on('select', function () { select_text_handler(); });
'open' the file in 'w' and that truncates my existing file! How do i open in 'append' mode and then 'seek'?? https://addons.mozilla.org/en-US/developers/docs/sdk/latest/modules/sdk/io/file.htm
The file module of the SDK is pretty limited. When opening a file for writing it will always be truncated (code). Also, it is uses entirely synchronous I/O on the main thread, which isn't really a good thing to do, as it will block the entire UI during the I/O.
You should probably use another mechanism via the chrome module. See OS.File and/or the MDN File I/O snippets.