Triggering a custom event with attributes from a Firefox extension - javascript

I have a Firefox extension that modifies the content of the page that the user is looking at. As part of that process the extension needs to trigger a custom event that the extension itself adds to the page source. I am having difficulties passing parameters to that custom event. What am I doing wrong?
Script block inserted into the head of the page:
document.addEventListener("show-my-overlay", EX_ShowOverlay, false, false, true);
function EX_ShowOverlay(e) {
alert('Parameter: ' + e.index);
// ...
}
Code in the extension:
var event = content.document.createEvent("Events");
event.initEvent("show-my-overlay", true, true);
event['index'] = 123;
content.document.dispatchEvent(event);
The event gets triggered successfully, but e.index is undefined.
I managed to get it working by creating an element on the page and then having the event handler find the element and read its attributes, but it didn't feel elegant. I want to do it without the element.
EDIT:
I also tried triggering the event with CustomEvent, but it throws an exception in the handler:
var event = new CustomEvent('show-my-overlay', { detail: { index: 123 } });
content.document.dispatchEvent(event);
function EX_ShowOverlay(e) {
alert('Parameter: ' + e.detail.index);
// ...
}
Permission denied to access property 'detail'

OP has solved their problem using postMessage. For those of us who actually do have to solve it using CustomEvent (being able to specify message types is useful), here's the answer:
Firefox won't allow the page script to access anything in the detail object sent by the content script via CustomEvent unless you clone the event detail into the document first using the Firefox-specific cloneInto() function.
The following does work over here to send an object from the extension to the page:
var clonedDetail = cloneInto({ index: 123 }, document.defaultView);
var event = new CustomEvent('show-my-overlay', { detail: clonedDetail });
document.dispatchEvent(event);
The Mozilla docs have more detail on cloneInto.

You cannot access "expandos" (additional properties defined on a native prototype object) across security boundaries. The security boundary in this case being between the fully privileged chrome (add-on) code and the non-privileged website code.
So you need to pass data using something "standard". The CustomEvent stuff would do, however your code is wrong. You have to call the constructor or initCustomEvent() correctly:
var event = new CustomEvent('show-my-overlay', { detail: { index: 123 } });
content.document.dispatchEvent(event);
Another alternative is the postMessage API.

Related

JavaScript: Open new tab and detect URL change

I am trying to open a new window with a URL for oAuth, however I cannot add any event listener.
const wind = window.open(msg.data.url);
console.log(wind);
wind.onload = function () {
wind.onpopstate = function (e) {
console.log('pop', e);
};
};
It just does nothing. However it turned out that window.open() has given me a not full window object. And that is all what I have got. How do I add event listener for that?
According to the MDN web docs regarding the window.open() function:
The returned Window reference can be used to access properties and
methods of the new window as long as it complies with Same-origin
policy security requirements.
That means if you call window.open("/questions") in a terminal on this webpage, you get a full window object, but if you call window.open("https://google.com"), it returns only a simplified object on which you will not be able to add event listeners. This is to prevent cross-origin attacks. You may, however, transfer data to the new window object via Window.postMessage(), if the new window is listening for that type of event. See here for more information on that.

Firefox Addon SDK message from chrome to unprivileged code

I am trying to pass messages between a Firefox Addon worker script and the webpage's javascript. I found this which explains how to send a message to the script and get a reply back, but I want to simply send the message from chrome to the unprivileged code. I have already tried using the following two methods.
I am sending from a worker attached to a panel and I want to receive the message in some javascript that I have injected into the page DOM.
To send
var element = document.createElement("MyExtensionDataElement");
element.setAttribute('detail', "hi");
document.documentElement.appendChild(element);
console.log("created the event", element);
var evt = document.createEvent("Events");
evt.initEvent("MyExtensionEvent1", true, false);
element.dispatchEvent(evt);
To receive
document.addEventListener("MyExtensionEvent", function(e) {
myExtension.myListener(e);
}, false, true);
And also via a simple CustomEvent
To send
var e = new CustomEvent("event",{detail:"string here"});
window.dispatchEvent(e);
To recieve
window.addEventListener("event",function(e){
console.log(e.detail);
});
The first one sends the message but it isnt received, and the second one fails to even create the CustomEvent in the first place. I'd appreciate any help on this matter and do apologize if the question seems amateurish. Im new to Firefox Addon Development.
Your receive is wrong, you need to use add 4th argument and set it to try:
So
window.addEventListener("event",function(e){
console.log(e.detail);
});
Goes to
window.addEventListener("event",function(e){
console.log(e.detail);
}, false, true);
See this topic - initCustomEvent pass data for method doesn't work anymore

How to properly handle chrome extension updates from content scripts

In background page we're able to detect extension updates using chrome.runtime.onInstalled.addListener.
But after extension has been updated all content scripts can't connect to the background page. And we get an error: Error connecting to extension ....
It's possible to re-inject content scripts using chrome.tabs.executeScript... But what if we have a sensitive data that should be saved before an update and used after update? What could we do?
Also if we re-inject all content scripts we should properly tear down previous content scripts.
What is the proper way to handle extension updates from content scripts without losing the user data?
If you've established a communication through var port = chrome.runtime.connect(...) (as described on
https://developer.chrome.com/extensions/messaging#connect), it should be possible to listen to the runtime.Port.onDisconnect event:
tport.onDisconnect.addListener(function(msg) {...})
There you can react and, e.g. apply some sort of memoization, let's say via localStorage. But in general, I would suggest to keep content scripts as tiny as possible and perform all the data manipulations in the background, letting content only to collect/pass data and render some state, if needed.
Once Chrome extension update happens, the "orphaned" content script is cut off from the extension completely. The only way it can still communicate is through shared DOM. If you're talking about really sensitive data, this is not secure from the page. More on that later.
First off, you can delay an update. In your background script, add a handler for the chrome.runtime.onUpdateAvailable event. As long as the listener is there, you have a chance to do cleanup.
// Background script
chrome.runtime.onUpdateAvailable.addListener(function(details) {
// Do your work, call the callback when done
syncRemainingData(function() {
chrome.runtime.reload();
});
});
Second, suppose the worst happens and you are cut off. You can still communicate using DOM events:
// Content script
// Get ready for data
window.addEventListener("SendRemainingData", function(evt) {
processData(evt.detail);
}, false);
// Request data
var event = new CustomEvent("RequestRemainingData");
window.dispatchEvent(event);
// Be ready to send data if asked later
window.addEventListener("RequestRemainingData", function(evt) {
var event = new CustomEvent("SendRemainingData", {detail: data});
window.dispatchEvent(event);
}, false);
However, this communication channel is potentially eavesdropped on by the host page. And, as said previously, that eavesdropping is not something you can bypass.
Yet, you can have some out-of-band pre-shared data. Suppose that you generate a random key on first install and keep it in chrome.storage - this is not accessible by web pages by any means. Of course, once orphaned you can't read it, but you can at the moment of injection.
var PSK;
chrome.storage.local.get("preSharedKey", function(data) {
PSK = data.preSharedKey;
// ...
window.addEventListener("SendRemainingData", function(evt) {
processData(decrypt(evt.detail, PSK));
}, false);
// ...
window.addEventListener("RequestRemainingData", function(evt) {
var event = new CustomEvent("SendRemainingData", {detail: encrypt(data, PSK)});
window.dispatchEvent(event);
}, false);
});
This is of course proof-of-concept code. I doubt that you will need more than an onUpdateAvailable listener.

How do I make a webRequest Event Page that only listens for events when a flag is set?

I am making an extension that can be ON or OFF, which I can check via a flag stored in local data.
When ON, I want to listen for all webRequests and redirect them like so:
chrome.webRequest.onBeforeRequest.addListener(
// callback
function(info) {
console.log("Got request: " + info.url + "\n Going to redirect");
return {redirectUrl: chrome.extension.getURL("redirect.html")};
},
// filters
{
urls: [
"<all_urls>"
]
},
// extraInfoSpec
["blocking"]);
But when OFF I do not want the event to fire at all. Ideally when OFF I wouldn't even be listening to events (if that would cut down on performance overhead in any significant way). One option I see is checking the flag in my callback function and simply not redirecting if OFF, but that still has the event being handled. As I understand it, the event will not be handled if the RequestFilter does not pass. Can I modify the RequestFilter to also check my ON/OFF boolean flag before trying to handle the event? Or is the RequestFilter only meant for checking URLs, headers, etc.?
Also my main reasoning for wanting to only handle events when ON is that it seems like a needless performance hit to try to handle EVERY webRequest - even if briefly. Would an immediate flag checking in the callback function not make any noticeable impact on performance anyway?
I am new to Chrome Extension dev and webdev in general, so if there is a much cleaner/easier way of doing this then please let me know.
Thanks!
The chrome.webRequest API cannot be used on event pages.
If you implement ExpertSystem's answer, then your extension won't add overhead to requests, but it will still waste memory (because using the webRequest API implies that you're using background pages. These pages always remain active even when the extension appears to do nothing).
The chrome.declarativeWebRequest is similar to the webRequest API, except that its API is declarative, allowing it to be used on event pages as well. The only downside of the API is that it is currently only enabled on the beta or dev channel. It will eventually be available on the stable channel though, probably within a few releases.
The following example shows how to redirect any URL whose host contains "google" ("google.com", "www.google.nl", but NOT "notgoogle.com") to a page within your extension:
var rules = [{
id: 'redirect-to-my-extension',
conditions: [
new chrome.declarativeWebRequest.RequestMatcher({
url: {
hostContains: '.google.'
}
})
],
actions: [
new chrome.declarativeWebRequest.RedirectRequest({
redirectUrl: chrome.runtime.getURL('redirect.html')
})
]
}];
// Whenever you're ready...
chrome.declarativeWebRequest.onRequest.addRules(rules);
// To disable the rules, simply remove the rules (by the previously specified id)
var ruleIds = rules.map(function(rule) { return rule.id; });
chrome.declarativeWebRequest.onRequest.removeRules(ruleIds);
This is merely an example. The declarativeWebRequest API has lots of other ways to construct conditions or actions, just take a look at the reference documentation and URL filters.
RequestFilters do not allow you to specify arbitrary conditions (such as if a flag is set). You can un-register the listener using removeListener() whenever the flag is set to OFF and register it back when the flag is set to ON. E.g.:
function myListener(...) {...}
function setEnabled(enabled) {
localStorage.enabled = enabled ? 'ON' : 'OFF';
if (enabled) {
chrome.webRequest.onBeforeRequest.addListener(myListener);
} else {
chrome.webRequest.onBeforeRequest.removeListener(myListener);
}
}
function isEnabled() {
return (localStorage.enabled !== 'OFF'); // <-- 'ON' by default
}
setEnabled(isEnabled());

Accessing every document that a user currently views from an extension

I'm writing an extension that checks every document a user views on certain data structures, does some back-end server calls and displays the results as a dialog.The problem is starting and continuing the sequence properly with event listeners. My actual idea is:
Load: function()
{
var Listener = function(){ Fabogore.Start();};
var ListenerTab = window.gBrowser.selectedTab;
ListenerTab.addEventListener("load",Listener,true);
}
(...)
ListenerTab.removeEventListener("load", Listener, true);
Fabogore.Load();
The Fabogore.Load function is first initialized when the browser gets opened. It works only once I get these data structures, but not afterwards. But theoretically the script should initialize a new listener, so maybe it's the selectedTab. I also tried listening to focus events.
If someone has got an alternative solution how to access a page a user is currently viewing I would feel comfortable as well.
The common approach is using a progress listener. If I understand correctly, you want to get a notification whenever a browser tab finished loading. So the important method in your progress listener would be onStateChange (it needs to have all the other methods as well however):
onStateChange: function(aWebProgress, aRequest, aFlag, aStatus)
{
if ((aFlag & Components.interfaces.nsIWebProgressListener.STATE_STOP) &&
(aFlag & Components.interfaces.nsIWebProgressListener.STATE_IS_WINDOW) &&
aWebProgress.DOMWindow == aWebProgress.DOMWindow.top)
{
// A window finished loading and it is the top-level frame in its tab
Fabogore.Start(aWebProgress.DOMWindow);
}
},
Ok, I found a way which works from the MDN documentation, and achieves that every document a user opens can be accessed by your extension. Accessing every document a user focuses is too much, I want the code to be executed only once. So I start with initializing the Exentsion, and Listen to DOMcontentloaded Event
window.addEventListener("load", function() { Fabogore.init(); }, false);
var Fabogore = {
init: function() {
var appcontent = document.getElementById("appcontent"); // browser
if(appcontent)
appcontent.addEventListener("DOMContentLoaded", Fabogore.onPageLoad, true);
},
This executes the code every Time a page is loaded. Now what's important is, that you execute your code with the new loaded page, and not with the old one. You can acces this one with the variable aEvent:
onPageLoad: function(aEvent)
{
var doc = aEvent.originalTarget;//Document which initiated the event
With the variable "doc" you can check data structures using XPCNativeWrapper etc. Thanks Wladimir for bringing me in the right direction, I suppose if you need a more sophisticated event listening choose his way with the progress listeners.

Categories

Resources