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
Related
I'm using the sample app Projection found here, which allows me to have one main app window open, and display on my second monitor some other stuff.
Now what I want is for the javascript between the two windows to have some way of sharing state or passing messages. In scenario1.js, the code to start the new window looks like this:
var view = window.open("ms-appx:///html/secondaryView.html", null, "msHideView=yes");
Now I know for a fact that this variable has a view.postMessage() function, but in the javascript for the new window (secondaryView.js), I don't know how to listen for the message. I also don't know if there's any other obvious way of sharing state that I haven't thought of, between the two windows.
[edit] The localStorage solution provided in here is fine and works, but isn't as good as using view.postMessage
From any javascript code in your first window, you can post a message like so:
view.postMessage({ a: 'b' }, '*'); // may replace * with the beginning of the result of window.location.href
And in the second window, this code will receive the message:
window.addEventListener('message',
function(event) {
var message = event.data; // event.data = { a: 'b' }
});
To post a message from the second window to the first window, it's quite similar, but use window.opener to refer to the view that opened this view:
window.opener.postMessage({ a: 'this is from the second window' }, '*');
And watch for it in the original javascript process, again, with window.addEventListener('message', (event) => {})
https://developer.mozilla.org/en-US/docs/Web/API/Window/postMessage
Thanks to Raymond Chen
I need to develop a simple Chrome Extension for work that inspects
Chrome's JS console for a certain value.
Further Explanation:
Basically, the need for this, is I need to know when a certain JS event has completed. I have placed a console.log("complete!") in my code to inform me when it is done.
The problem I am facing, I cannot seem to find a way for a chrome extension to read output from the JS Console.
As far as I know you can't read the console output from JS, not even on a regular webpage. You would have to hijack the console methods and save what is passed to them, and then do searches on the saved data.
From your goal it seems like you simply want to trigger a new event. Which you can do by creating a Event/CustomEvent, setting up a listener for it, and firing off the event whenever you need it to be triggered.
document.addEventListener("eventdone",function(e){
console.log("The events done, now do work here");
});
//then whereever you had console.log("complete!")
var event = new CustomEvent('eventdone', { 'detail': 'Extra data' });
document.dispatchEvent(event);
Demo
document.addEventListener("eventdone",function(e){
document.body.innerHTML = "Event done";
});
document.querySelector("button").addEventListener("click",function(e){
setTimeout(function(){
var event = new CustomEvent('eventdone', { 'detail': 'Extra data' });
document.dispatchEvent(event);
},2000);
});
<button>Click me</button>
I have been having some issues sending JavaScript to an iFrame that uses a different port and after searching online it seems that the 'different port' part is causing the issue.
Here is the code sending JavaScript to the iFrame:
<script>
var network = document.getElementById("1").contentWindow.kiwi.components.Network();
$(".irc-channel-selector").click(function(event){
network.join('#' + $(this).attr('data-irc-channel'));
});
</script>
The iFrame does not use port 80 which appears to be the problem:
<iframe id="1" src="http://www.example.com:7888">
I understand that I can use something called postMessage to do the same as what I need but having read up on it online I'm not sure how it should be used, it seems pretty complex whereas I'm only used to basic JavaScript such as the code that I wrote above.
Can someone provide an example on how I can use this postMessage to mimic the behaviour above? Reading online documentation I do not understand how to use it in my scenario! :(
It's not very complicated to achieve this with postMessage. First, inside the iframe, you must expect a message:
var network = kiwi.components.Network();
function receive(event) {
// it is important to check the origin. Here I'm assuming that the parent window has that origin (same address, default port).
// if you don't check the origin any other site could include your iframe and send messages to it
if (event.origin === "http://www.example.com") {
var message = event.data;
// Assuming you might want different message types in the future. Otherwise message could be just the channel itself.
if (message.type === "JOIN") {
network.join(message.channel);
}
}
}
window.addEventListener("message", receive, false);
Now your iframe page is expecting a message to make it join a channel. The parent page can send that message with:
$(".irc-channel-selector").click(function(event){
var message = {
type: "JOIN",
channel: '#' + $(this).attr('data-irc-channel')
};
// Notice that again we're using the specific origin you used in your iframe
document.getElementById("1").contentWindow.postMessage(message, "http://www.example.com:7888");
});
Here's a far more simple fiddle where a message is sent to the same window, since I'd have to host a page somewhere to have an iframe in jsfiddle: https://jsfiddle.net/3h1Lw0j4/1/ -- Anyway it's useful to see how event.origin behaves.
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.
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.