How to write a Thunderbird extension (webextension) to modify the message display? - javascript

I'd like to write an extension for Thunderbird that modifies the message display (e.g. insert/replace text/markup/image).
Unfortunately, the documentation is lacking (due to recent changes?).
https://developer.mozilla.org/en-US/docs/Mozilla/Thunderbird/Thunderbird_extensions
is outdated
https://developer.thunderbird.net/
does not have useful examples (yet)
https://thunderbird-webextensions.readthedocs.io/
no examples either
Some examples can be found at
https://github.com/thundernest/sample-extensions
Building on https://github.com/thundernest/sample-extensions/tree/master/messageDisplay
I've modified background.js
browser.messageDisplay.onMessageDisplayed.addListener((tabId, message) => {
console.log(`Message displayed in tab ${tabId}: ${message.subject}`);
console.log(message.id);
browser.messages.getFull(message.id).then((messagepart) => {
console.log(messagepart);
body = messagepart['parts'][0]['parts'][0]['body'];
console.log(body);
body += "modified!";
console.log(body);
});
browser.windows.getCurrent().then((window)=>{
console.log(window.type);
});
browser.tabs.getCurrent().then((tab)=>{
console.log("tab",tab);
});
});
which gives me the message body (using magic indexes) but expectedly, the change is not reflected in the message display.
The window type returned is normal, not messageDisplay.
The tab is undefined despite adding permissions
"permissions": [
"messagesRead",
"activeTab",
"tabs",
"tabHide"
],
but I assume that's because the script is running as background.
So I'd need a script running on the content / access to the tab and then some hints on how to modify the displayed message content (I do not want to modify the message).
Where would I find the equivalent documentation to
https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/manifest.json/content_scripts
specific to Thunderbird?
Specifying content_scripts in manifest.json causes "Error: Error reloading addon messageDisplay#sample.extensions.thunderbird.net: undefined".
executeScript() from background does not seem to work either, even with tabId specified.

This was not possible to do when you wrote your question, the API for modifying displayed messages was missing.
As of this writing (September 2020), the browser.messageDisplayScripts API landed a few days ago, see bug 1504475 and related patch for examples. It works as follows: You can register your content script (to modify the displayed messages) like this
let myPromise = browser.messageDisplayScripts.register({
css: [{
file: "/style.css",
}],
js: [{
file: "/content_script.js",
}],
});
And you can later unregister with
myPromise.then((script) => { script.unregister(); });
You need to register the script just once for all messages (you do not need a listener that would load it each time a message is displayed).
Note that your manifest.json needs to include the messagesModify permission for this to work.
The new API will be in Thunderbird version 82, so if I understand the release process correctly it should be in stable version 88 (unless it is backported before that). You can try it already (v82 is the current EarlyBird).

Documentation https://thunderbird-webextensions.readthedocs.io/en/68/tabs.html#getcurrent
says:
May be undefined if called from a non-tab context (for example: a background page or popup view).
Since the background.js is not called from a tab context the tab is undefined.

Related

Exception Using chrome.desktopCapture.chooseDesktopMedia

In Chrome Manifest V2 I was able to easily capture the desktop. Attempting this in Manifest version 3 I have had no luck. I feel I may be missing something here in attempting this in Manifest V3.
I have been using this as a reference. https://developer.chrome.com/docs/extensions/reference/desktopCapture/
My manifest has these permissions granted to it
"permissions": [
"idle",
"tabs",
"storage",
"notifications",
"alarms",
"desktopCapture"
],
This is the sample code I have been testing just to see if I can get the screen selector to come up. I have not included the tabs.tab as this is labeled as optional and I wanted to see if I could have the plugin trigger the screen recording feature on its own.
chrome.desktopCapture.chooseDesktopMedia(["screen"], (streamID, options) => {console.log(id)});
I have been attempting to have this run in my background.js file.
Each time it runs chrome crashes completely with no errors given.
Reviewing the crash dump I can see the following information.
Exception Code: 0xC0000005
Exception Information: The thread tried to read from or write to a virtual address for which it does not have the appropriate access.
I solve it this way for Manifest v3.
It was giving me errors too. When I checked the documentation, chooseDesktopMedia it wanted three parameters.
DesktopCaptureSourceType[]
Tab
Callback Function
I see in your question your have put 1 and 3. I did the same it was giving me errors. So I made sure I get the active tab first before calling chrome.desktopCapture.chooseDesktopMedia in the background script.
So the code looks like this
chrome.tabs.query({ active: true }, (tabs) => {
if (tabs.length) {
const tab = tabs[0];
var pending = chrome.desktopCapture.chooseDesktopMedia(["window"], tab, (streamId) => {
//console.log(streamId, tab);
});
}
return false;
})
And it worked. I hope this helps

Can't access document element in chrome extension

I am currently making a chrome extension using the following Chrome tutorial:
https://developer.chrome.com/docs/extensions/mv3/getstarted/
I ran into some issues with my own code initially so now I'm just using their code as a basis for my own thing.
In popup.js, the tutorial has a function that changes the background color on click.
function setPageBackgroundColor() {
chrome.storage.sync.get("color", ({ color }) => {
document.body.style.backgroundColor = color;
});
I replaced the line in the code instead to what I wanted it to do.
document.getElementById(xyz).miscfunction(arg1, arg2);
The getting started extension works as intended (obviously). When I run my line of code in the console in Chrome, it works exactly as intended. However, when I run the line using the extension, it gives me the following error:
Uncaught TypeError: document.getElementById(...).miscfunction is not a function
I have tested and found out that getElementById is indeed getting the element I want because when I try to run this on a tab that does not have said element, it gives me an error saying that the element is null. It does not do so for the tab that actually does have the element.
What is preventing my extension from being able to access the miscfunction? When I was last trying to debug this months ago, I recall having some issues with injection and the content security policy but I do not remember what I did to get to that point. Any help would be appreciated.
EDIT:
I have since updated the popup to call a script js file instead of calling a function, and then put the function in a separate file.
To access the document element of the page you are viewing, you need to paste the required code in the content script.
You first need to add content script and it's scope in manifest.json
"content_scripts" : [{
"matches" : ["https://<website-you-are-working-on>"]
"js" : ["script.js"] //file containing the code you need to execute
}]
The pop up script now needs to send message to content script to do what you want to do.
popup.js
chrome.tabs.query({currentWindow: true, active: true}, function (tabs){
const activeTab = tabs[0];
chrome.tabs.sendMessage(activeTab.id, {
"action" : "change-background"
"payload": {
//required info
}
})});
Now listen for the message in the content script and do the required task.
script.js
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
if(message.action == "change-background"){
//your code here
}
})

Accessing the DOM of a programatically opened tab that contains a different domain than the active tab

Overview of the problem
I cannot access the DOM of a window that I open programmatically in my chrome extension. I believe this may be due to origin/cross-site scripting restrictions. I have tried to give it the maximal permissions I can, including "<all_urls>", "activeTab" and "tabs"
I also played around with "content_security_policy_" settings but from the documentation I was hoping rather than expecting that to help.
I have a simple extension that runs code when the button is clicked, I want to open a few different tabs (but from different domains) and then access the DOM of each. However, it only seems to work if I'm on a tab of a domain and then I open another tab of the same of domain. Then I can access, for example, window onload of the new tab. I have no luck when it is a different domain.
e.g. if I press the button with activeTab "foo.com" then if it window.opens a new tab "foo.com/something" then I can access the document of that opened tab. But if it was "bar.com" then I wouldn't be able to access "foo.com/something"'s DOM
p.s. please note that executeScripts is used instead of manifest content scripts because it is necessary for my code to work. I must inject at least some of the files there this way, otherwise my code will not work (for reasons that are not completely apparent in the example)
my Question
What's way(s) can I get around this - I mean be able to access the DOM of any tab that I open, regardless of what site is in the active tab when the extension button is pressed in the toolbar?
Should I inject content scripts into a a tab that has been opened with window.open and somehow pass its document to Code.js? If so, how could I go about doing that? Can I somehow pass the document to the background.js? and somehow pass it to the injected Code.js?
If this will work (get around security restrictions) then can these content scripts be injected programatically (I don't know exactly what sites they will be until runtime, so I can't really specify them in the manifest)
or is there some way I can just relax the security restrictions and be able to directly access window.open's returned window's document? (which as I mentioned above currently only works on same-domain sites)
background.js
// this is the background code...
// listen for our browerAction to be clicked
chrome.browserAction.onClicked.addListener(function (tab) {
executeScripts(null, [ // jquery can be inserted here: https://stackoverflow.com/questions/21317476/how-to-use-jquery-in-chrome-extension
{ file: "lib/jquery-3.2.1.js" },
{ file: "lib/kotlin.js" },
{ file: "Code.js" } // don't include it in manifest "content_scripts" because it gives an error about kotlin.js not present that way
])
});
/*
* https://stackoverflow.com/questions/21535233/injecting-multiple-scripts-through-executescript-in-google-chrome
*/
function executeScripts(tabId, injectDetailsArray)
{
function createCallback(tabId, injectDetails, innerCallback) {
return function () {
chrome.tabs.executeScript(tabId, injectDetails, innerCallback);
};
}
var callback = null;
for (var i = injectDetailsArray.length - 1; i >= 0; --i)
callback = createCallback(tabId, injectDetailsArray[i], callback);
if (callback !== null)
callback(); // execute outermost function
}
code flow
background.js
Code.jscalls a function that starts with var w = window.open(url)
then w.addEventListener("load", ...), which is the problem. It only fires if the url belongs to the same domain as the active tab. (onLoad can't be used any-which-way, it never fires that's why I use addEventListener)
notes
I have "manifest_version": 2
I need to inject this way rather than include content scripts in the manifest (I guess I could put the jquery library in the manifest but the others I can't and I prefer to keep all injections together in the "code")

Injecting into navigation error page gets: Error: No window matching {"matchesHost":["<all_urls>"]}

I am trying to execute a script that shows a green border on the specified tab (by ID). The script should execute when the response for the requested URL is an error. The problem is that, when I load the extension from about:debugging, I get the following error (in the browser console in FF 53):
Error: No window matching {“matchesHost”:[“<all_urls>”]}
I searched for hours and hours and looked at several posts for similar problems but none of them have helped me. For example, this post suggests adding "<all_urls>" permission and it did not help in my case. Another post says that it is not possible to execute script in certain type of hosts such as about:[anything] pages and mozilla pages. I do not see my URL belongs to any of them.
Here is my example:
The manifest.json
{
"manifest_version": 2,
"name": "test",
"version": "1.0",
"background": {
"scripts": ["test.js"]
},
"permissions": [
"<all_urls>",
"activeTab",
"tabs",
"storage",
"webRequest"
]
}
The background script is test.js:
console.log("-- inside js file --");
var target = "<all_urls>";
function logError(responseDetails) {
errorTab=responseDetails.tabId;
console.log("response tab: "+errorTab);
var makeItGreen = 'document.body.style.border = "5px solid green"';
var executing = browser.tabs.executeScript(errorTab,{
code: makeItGreen
});
}//end function
browser.webRequest.onErrorOccurred.addListener(
logError,
{urls: [target],
types: ["main_frame"]}
);
The error you are seeing:
Error: No window matching {"matchesHost":["<all_urls>"]}
is generated when you attempt to inject a script using tabs.executeScript() (or CSS with tabs.insertCSS()) in a tab that is currently displaying a URL which you do not have permission to inject into. In this case, you have specified in your manifest.json the host permission "<all_urls>". The fact that "matchesHost":["<all_urls>"] is displayed indicates that Firefox is aware of your "<all_urls>" permission. That you have still gotten the error means that you have attempted to inject into a URL which does not match <all_urls>.
As you have mentioned, Firefox does not permit injecting into about:* pages. In addition, injecting into pages at the domain addons.mozilla.org is not permitted. None of those pages will match <all_urls>. All such URLs will generate the above error if you attempt to inject into tabs showing them.
But, I'm injecting into some normal URL that had an error
All easily obtainable information to the contrary — including the URL provided in the tabs.Tab data obtained from tabs.get() —, the page you are attempting to inject into is, in fact, an about:* page, not the page (that doesn't exist) at the URL where you got the error. While the URL reported in the tabs.tab structure for the tab in which you received the error will show the URL on which the error occurred, the actual URL for the page being displayed is something like:
about:neterror?e=dnsNotFound&u=[URL you were attempting to get to, but encoded as a query string]
I know this because the last webNavigation.onDOMContentLoaded event when I tested attempting to load the URL: http://www.exampleahdsafhd.com/ was:
webNavigation.onDOMContentLoaded - > arg[0] = Object {
url: "about:neterror?e=dnsNotFound&u=http%3A//www.exampleahdsafhd.com/&c=UTF-8&f=regular&d=Firefox%20can%E2%80%99t%20find%20the%20server%20at%20www.exampleahdsafhd.com.",
timeStamp: 1497389662844,
frameId: 0,
parentFrameId: -1,
tabId: 2,
windowId: 3
}
The fact that the error page is an about:* page, means that you will not be able to inject scripts, or CSS, into it. This means that you will need to find some other way to accomplish what you desire and/or adapt what you desire to do to what is possible. One possibility would be to navigate to a page within your extension which describes the error.

Open chrome://newtab from a Chrome extension

I try to write a Google Chrome extension that simply opens a new tab when I click left-right in a short interval. The JavaScript is no problem but I implemented this as a "content_scripts" script.
In some other threads I read that I can't access the chrome.* APIs from content_scripts (except the chrome.extension API).
Even if it's not necessary to access the chrome.tabs API to open a new window (window.open should do the job) it seems I need it though for opening a new tab with the new tab page which obviously isn't possible via window.open.
So I can't really figure out what is the best way to do that. I could use a background page which I could call from the content_script but I think there should be a much more simple way to do that, I just don't get it.
Anyone have an idea?
I think your content script will have to send a message to your background page to invoke chrome.tabs.create - content scripts cannot use the chrome api, nor can they directly communicate with the background page.
Here's a reference about message passing inside Chrome extensions for further detail, but here's the example code ( modified from the example in said reference )
// in background
chrome.extension.onMessage.addListener(
function(request, sender, sendResponse) {
switch ( request.action) {
case 'newTab' : {
//note: passing an empty object opens a new blank tab,
//but an object must be passed
chrome.tabs.create({/*options*/});
// run callback / send response
} break;
}
return true; //required if you want your callback to run, IIRC
});
// in content script:
chrome.extension.sendMessage({action: "newTab"}, function(response) {
//optional callback code here.
});
simple and easy
document.body.onclick = function openNewWindow( ) {
window.location.href = 'javascript:void window.open( "chrome://newtab" )';
}
manifest:
,"permissions":[
"http://*/*"
,"https://*/*"
]
,"manifest_version": 2
,"content_scripts":[{
"matches":[
"http://*/*"
,"https://*/*"
]
,"js":[
"js/openWindow.js"
]
}]
alright i miss understanding the question... modified

Categories