Firefox add-on triggers code in each open window - firefox

I have a Firefox add on that displays a pop-up when it sees a certain response header. This works fine when I have a single window. However, when I have multiple windows open, and one of the tabs in a window triggers the pop-up code in my add-on (due to the presence of said header) I get a pop-up in each of my open windows. For example, if I have 3 windows open, I get 3 different pop ups, one for each windows. Is this the default behavior, and is there an easy in-built way to fix this using their SDK.
Edit:
I have the following code:
Util.requestBlock(httpChannel) {
/*load response headers here*/
if (responseHeaders.includes("header_xyz"))
alert("show popup");
}
Util.monitor = function(w) {
this.obsService = Components.classes['#mozilla.org/observer-service;1'].getService(Components.interfaces.nsIObserverService);
this.obsService.addObserver(this, 'http-on-examine-response', false);
}
Util.monitor.prototype = {
'observe': function(subject, topic, data). {
if (topic == 'http-on-examine-response'). {
var channel = subject.QueryInterface(Components.interfaces.nsIHttpChannel);
var block_response = new Util.RequestBlock(channel);
}
},
};
The Util.monitor adds an observer. Whenever a response is received, the "Observe" function is called.

var windows = require("window-utils");
for (window in windows.browserWindowIterator)
doToWindow(window);

Related

tabs onUpdated event not detected on Safari extension?

I am trying to develop a simple web extension/addon under Safari, which is using the tabs onUpdated event. I used the Safari XCRUN converter: https://developer.apple.com/documentation/safariservices/safari_web_extensions/converting_a_web_extension_for_safari
What I am trying to do is :
Open new tab on Google Scholar with set prefs params, from "options.js" script (Options page code below)
Listen for this tab to be updated and ready (e.g. tab status is complete)
Then, inject a content script that will simulate the user click on save button (i.e. on GScholar page)
Then remove the listener, and wait 1,5s (for GS tab to reload and finish saving) in order to finally close this tab.
// Detect browser language
const gsUrl = currentBrowser.i18n.getUILanguage().includes("fr")
? GSCHOLAR_SET_PREFS_FR_URL
: GSCHOLAR_SET_PREFS_COM_URL;
// Listener to detect when the GS tab has finished loading
const gsTabListener = (tabId, changeInfo, tabInfo) => {
if (changeInfo.url && changeInfo.url.startsWith(GSCHOLAR_HOST)) {
currentBrowser.tabs.executeScript(
tabId,
{
code: `document.getElementsByName("save")[0].click();`,
},
() => {
currentBrowser.tabs.onUpdated.removeListener(gsTabListener);
setTimeout(() => currentBrowser.tabs.remove(tabId), 1500);
}
);
}
};
currentBrowser.tabs.onUpdated.addListener(gsTabListener); // Add tab listener
currentBrowser.tabs.create({
url: `${gsUrl}?inst=${gScholarInstIdList.join("&inst=")}&save=#2`,
active: false,
}); // Open GS tab according to browser language
The problem is that it works well on Chrome/Edge/Firefox (on MacOS), but not on Safari : the GS tab is opended but isn't closed and nothing happens :-/
PS:
It seems tabs onUpdated event is well supported on Safari according to MDN.
https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/tabs/onUpdated
I have also tried webNavigation onCompleted event, but same !
Developing on : MacBookAir under MacOS Monterey 12.4, Safari 15.4 (17613.2.7.18), XCode 13.3.1 (13E500a), extension is bundled with Webpack 5.68.0 (e.g. building all assets files).
I really don't see what I am doing wrong and why wouldn't this tab event be intercepted ?
Thanks for your feedback.
After debugging I finally sloved this by noticing that in fact the events were triggered, but missed because of the availability and values of parameters passed into callabck (changeInfo, details) depending on the browser we're on.
So I switched from onUpdated to webNavigation.onCompleted API, which is better suited to our need (tab page fully loaded) and whose parameter is simple and consistent across browsers :-)
const uiLanguage = currentBrowser.i18n.getUILanguage().includes("fr")
? "fr"
: "com"; // Detect browser language
const gsUrl = `${GSCHOLAR_SETTINGS_HOST}.${uiLanguage}`;
// Listener to detect when the GS tab has finished loading
const gsTabListener = (details) => {
if (details && details.url && details.tabId) {
if (details.url.startsWith(`${gsUrl}/scholar_settings?`)) {
currentBrowser.tabs.executeScript(details.tabId, {
code: `document.getElementsByName("save")[0].click();`,
});
} else if (details.url.startsWith(`${gsUrl}/scholar?`)) {
currentBrowser.webNavigation.onCompleted.removeListener(
gsTabListener
);
currentBrowser.tabs.remove(details.tabId);
}
}
};
currentBrowser.webNavigation.onCompleted.addListener(gsTabListener); // Add GS tab listener
currentBrowser.tabs.create({
url: `${gsUrl}/scholar_settings?inst=${gScholarInstIdList.join(
"&inst="
)}&save=#2`,
active: false,
}); // Open GS tab according to browser language

How to get the current tab's history in a Web Extension in Firefox?

Is there an API that makes it possible to get the current tab's history in a Web Extension in Firefox? Just like when clicking and holding on the Back button, a dropdown will appear to show the current tab's history.
No. You cannot ask for the list for a certain tab by default.
You can, however, listen for the tab events onUpdated, onCreated etc. Using the tabId which stays the same, you can keep a list of URLs in a background script (background.js) which is always running if the addon is enabled.
You would do it like this:
let arr=[]; // At the top of background.js
browser.tabs.onCreated.addListener(handleCreated); // Somewhere in background.js
function handleCreated(tab) {
let tabId = tab.id;
if(arr[tabId]==null) arr[tabId] = [];
arr[tabId].push(url);
}
function getHistoryForCurrentTab(){
function currentTabs(tabs) {
// browser.tabs.query returns an array, lets assume the first one (it's safe to assume)
let tab = tabs[0];
// tab.url requires the `tabs` permission (manifest.json)
// We will now log the tab history to the console.
for(let url of arr[tab.id]){
console.log(url);
}
}
function onError(error) {
console.log(`This should not happen: ${error}`);
}
browser.tabs.query({currentWindow: true, active: true}).then(currentTabs, onError);
}
The above code is a proof of concept. Some improvements you will need to consider: implement onClosed which resets the tab history for that id (arr[tabId] = null), implement onUpdated (will be needed for sure, same logic as in handleCreated).
Links:
https://developer.mozilla.org/en-US/Add-ons/WebExtensions/API/tabs

Force context menu to appear for form inputs

I'm trying to develop a ff addon that allows a user to right-click on a form element and perform a task associated with it.
Unfortunately somebody decided that the context menu shouldn't appear for form inputs in ff and despite long discussions https://bugzilla.mozilla.org/show_bug.cgi?id=433168, they still don't appear for checkboxes, radios or selects.
I did find this: https://developer.mozilla.org/en-US/docs/Offering_a_context_menu_for_form_controls but I cannot think how to translate the code to work with the new add-on SDK.
I tried dumping the javascript shown into a content script and also via the observer-service but to no avail.
I also cannot find the source for the recommended extension https://addons.mozilla.org/en-US/firefox/addon/form-control-context-menu/ which considering it was 'created specifically to demonstrate how to do this' is pretty frustrating.
This seems like very basic addon functionality, any help or links to easier documentation would be greatly appreciated.
** UPDATE **
I have added the following code in a file, required from main, that seems to do the trick.
var {WindowTracker} = require("window-utils");
var tracker = WindowTracker({
onTrack: function(window){
if (window.location.href == "chrome://browser/content/browser.xul") {
// This is a browser window, replace
// window.nsContextMenu.prototype.setTarget function
window.setTargetOriginal = window.nsContextMenu.prototype.setTarget;
window.nsContextMenu.prototype.setTarget = function(aNode, aRangeParent, aRangeOffset) {
window.setTargetOriginal.apply(this, arguments);
this.shouldDisplay = true;
};
};
}
, onUntrack: function(window) {
if (window.location.href == "chrome://browser/content/browser.xul") {
// In case we were called because the extension is uninstalled - restore
// original window.nsContextMenu.prototype.setTarget function
window.nsContextMenu.prototype.setTarget = window.setTargetOriginal;
};
}
});
Unfortunately this still does not bring up a context menu for disabled inputs, but this is not a show-stopper for me.
Many Thanks
The important piece of code in this extension can be seen here. It is very simple - it replaces nsContextMenu.prototype.setTarget function in each browser window and makes sure that it sets shouldDisplay flag for form controls.
The only problem translating this to Add-on SDK is that the high-level modules don't give you direct access to browser windows. You have to use the deprecated window-utils module. Something like this should work:
var {WindowTracker} = require("sdk/deprecated/window-utils");
var tracker = WindowTracker({
onTrack: function(window)
{
if (window.location.href == "chrome://browser/content/browser.xul")
{
// This is a browser window, replace
// window.nsContextMenu.prototype.setTarget function
}
},
onUntrack: function(window)
{
if (window.location.href == "chrome://browser/content/browser.xul")
{
// In case we were called because the extension is uninstalled - restore
// original window.nsContextMenu.prototype.setTarget function
}
}
});
Note that WindowTracker is supposed to be replaced in some future SDK version. Also, for reference: nsContextMenu implementation

detect new browser window with WATIN

is there a way to test wether a link was opened in a new browser-window (or browser-tab)?
Update:
So far I used the following code:
var newBrowserWindow = Browser.AttachTo<IE>(Find.ByTitle(browserTitle));
Assert.That(newBrowserWindow.hWnd, Is.Not.EqualTo(existingBrowserWindow.hWnd));
Where I used the existingBrowserWindow to open a page and to click on a link. But when the link opens a new tab in the existing browser (default behaviour for IE with targer=_blank) it has the same window-handle, since it's the same browser window. So how can I detect a new tab?
Some code of yours would help...,
Anyway, what I do when a link open a new browser window is
using (var newBrowser = WatiN.Core.Browser.AttachTo<IE>(Find.ByTitle("Analytics - Read conversation"))
{
}
Browser.AttachTo supports Find.ByUri(), Find.ByTitle() and Find.By("hwnd", windowHandle) according to documentation. I only tested Find.ByUri() and Find.ByTitle() methods.
if you want to detect if you action has opened a new window you could do
public bool TryGetNewBrowser(out IE browser, string title)
{
try
{
browser = WatiN.Core.Browser.AttachTo<IE>(Find.ByTitle(title));
return true;
}
catch(WatiN.Core.Exceptions.BrowserNotFoundException)
{
browser = null;
return false;
}
}
As far as I know, there is no support in WatiN for new tab. But the default behavior of internet explorer is to open new links in new window.

Reusing a tab in Firefox, TabOpen event, and beyond

I followed a tutorial on reusing tabs inside Firefox, and right now my extension does it well. However, I also need to reuse tabs that are opened from outside (from some application, start menu etc.). How do I do this?
I tried adding an event listener for TabOpen event, but when I log the
event.target.linkedBrowser.currentURI.spec
it's value is "about:blank". I expected the actual address that I typed into the address bar (automatically opens in a new tab), or the address that I open from some other application, so I can close that tab immediately, and the focus the right one. What am I doing wrong?
Thanks in advance.
Just in case, here's the code that reuses the tab when a new tab is requested from an extension
function openAndReuseOneTabPerURL(url) {
var wm = Components.classes["#mozilla.org/appshell/window-mediator;1"]
.getService(Components.interfaces.nsIWindowMediator);
var browserEnumerator = wm.getEnumerator("navigator:browser");
// Check each browser instance for our URL
var found = false;
while (!found && browserEnumerator.hasMoreElements()) {
var browserWin = browserEnumerator.getNext();
var tabbrowser = browserWin.getBrowser();
// Check each tab of this browser instance
var numTabs = tabbrowser.browsers.length;
for(var index=0; index<numTabs; index++) {
var currentBrowser = tabbrowser.getBrowserAtIndex(index);
if (url == currentBrowser.currentURI.spec) {
// The URL is already opened. Select this tab.
tabbrowser.selectedTab = tabbrowser.mTabs[index];
// Focus *this* browser-window
browserWin.focus();
found = true;
break;
}
}
}
// Our URL isn't open. Open it now.
if (!found) {
var recentWindow = wm.getMostRecentWindow("navigator:browser");
if (recentWindow) {
// Use an existing browser window
recentWindow.delayedOpenTab(url, null, null, null, null);
}
else {
// No browser windows are open, so open a new one.
window.open(url);
}
}
}
When you receive the TabOpen event, it's too early for the page content to be loaded still. When you receive the TabOpen event, however, you should register for load or DOMContentLoaded. When you receive that event, you should be able to access the URI.
I guess you could extract the wanted behaviour from the implementation in the Tab Mix Plus extension

Resources