Inject content script only once in a webpage - javascript

I am trying to inject content script on context menu click in an extension manifest version 3. I need to check if it is already injected or not. If it is not injected , inject the content script. This condition has to be satisfied. Can anyone help me with this?
We can use
ALREADY_INJECTED_FLAG
but this can be checked only in the content script, so this approach will not work as expected.
payload.js(content script)
function extract() {
htmlInnerText = document.documentElement.innerText;
url_exp = /[-a-zA-Z0-9#:%_\+.~#?&//=]{2,256}\.[a-z]{2,4}\b(\/[-a-zA-Z0-9#:%_\+.~#?&//=]*)?/gi;
regex = new RegExp(url_exp)
list_url = htmlInnerText.match(url_exp)
ip_exp = /\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\b/;
list_ip = htmlInnerText.match(ip_exp)
hash_exp = /\b[A-Fa-f0-9]{32}\b|\b[A-Fa-f0-9]{40}\b|\b[A-Fa-f0-9]{64}\b/g
list_hash = htmlInnerText.match(hash_exp)
chrome.storage.local.set({ list_url: list_url, list_ip: list_ip, list_hash: list_hash });
}
chrome.runtime.sendMessage( extract());
background.js
genericOnClick = async () => {
// Inject the payload.js script into the current tab after the backdround has loaded
chrome.tabs.query({ active: true, currentWindow: true }, function (tabs) {
chrome.scripting.executeScript({
target: { tabId: tabs[0].id },
files: ["payload.js"]
},() => chrome.runtime.lastError);
});
// Listen to messages from the payload.js script and create output.
chrome.runtime.onMessage.addListener(async (message) => {
chrome.storage.local.get("list_url", function (data) {
if (typeof data.list_url != "undefined") {
urls = data.list_url
}
});
chrome.storage.local.get("list_ip", function (data) {
if (typeof data.list_ip != "undefined") {
ips = data.list_ip
}
});
chrome.storage.local.get("list_hash", function (data) {
if (typeof data.list_hash != "undefined") {
hashes = data.list_hash;
}
});
if ( hashes.length>0 || urls.length>0 || ips.length>0 ){
chrome.windows.create({url: "output.html", type: "popup", height:1000, width:1000});
}
});
}

on my first context menu click I get the output html once. Second time
I click, I get the output html twice likewise.
This behavior is caused by a combination of two factors.
First factor
You're calling chrome.runtime.onMessage.addListener() inside genericOnClick(). So every time the user clicks the context menu item, the code adds a new onMessage listener. That wouldn't be a problem if you passed a named function to chrome.runtime.onMessage.addListener(), because a named function can only be registered once for an event.
function on_message(message, sender, sendResponse) {
console.log("bg.on_message");
sendResponse("from bg");
}
chrome.runtime.onMessage.addListener(on_message);
Second factor
But you're not registering a named function as the onMessage handler. You're registering an anonymous function. Every click on the context menu item creates and registers a new anonymous function. So after the Nth click on the context menu item, there will be N different onMessage handlers, and each one will open a new window.
Solution
Define the onMessage handler as a named function, as shown above.
Call chrome.runtime.onMessage.addListener() outside of a function.
You don't have to do both 1 and 2. Doing either will solve your problem. But I recommend doing both, because it's cleaner.

Related

Chrome extension: create new window in depends on window presence

I create a new window in my Chrome extension like this:
chrome.windows.create({
url: 'https://example.com',
focused: false,
state: "minimized"
}, function(hiddenWindow) {
var code = "console.log('Some JS code goes here');"
chrome.tabs.onUpdated.addListener(function(tabId, info) {
if (info.status === 'complete') {
chrome.tabs.executeScript(hiddenWindow.tabs[0].id, {
code: code
},
function(results) {
console.log(results);
});
}
});
});
It is possible somehow to do this one:
First time we create a window with one tab inside (like in my code above)
Then each time we check if this window is not closed by user
If the window still exists, then open a new tab in this window and close the previous tab.
If this window no longer exists, then do it all over again starting from #1
Will be grateful for any help and ideas!
Store the id of the window in a variable (global, for example) and use chrome.windows.get - when the window is closed the API will return an error in chrome.runtime.lastError. Also, instead of closing the previous tab it seems simpler to navigate the existing tab to a new URL.
Now, the above scheme would require us to use an elaborate cascade of callbacks at worst or Promises at best, but since it's 2019 let's use the modern async/await syntax instead with the help of Mozilla WebExtension polyfill.
let wndId;
const wndOptions = {
focused: false,
state: 'minimized',
};
const code = `(${() => {
console.log('Some JS code goes here');
}})()`;
async function openMinimized(url) {
const w =
wndId &&
await browser.windows.get(wndId, {populate: true}).catch(() => {}) ||
await browser.windows.create({url, ...wndOptions});
wndId = w.id;
const [wTab] = w.tabs;
if (wTab.url !== url) browser.tabs.update(wTab.id, {url});
return new Promise(resolve => {
browser.tabs.onUpdated.addListener(async function onUpdated(tabId, info) {
if (tabId === wTab.id && info.status === 'complete') {
browser.tabs.onUpdated.removeListener(onUpdated);
resolve(browser.tabs.executeScript(tabId, {code}));
}
});
});
}
Usage:
openMinimized('https://example.com').then(results => {
console.log(results);
});
Notes:
onUpdated listener checks tabId to process only this tab
onUpdated listener unregisters itself
code can be written as normal JS with syntax highlight inside an IIFE in a template string
How to use the polyfill: download browser-polyfill.min.js from its official repo on unpkg, save in your extension directory, and load it just like any other script in your extension, for example, as a background script in manifest.json:
"background": {
"scripts": ["browser-polyfill.min.js", "background.js"]
}

Multiple window onload functions with only Javascript

I have an external JS file that adds a window.onload function to the page.
The basic premise is that it loads up a popup window on your website whenever the user clicks on certain link class. It's written in PHP / JS so assume that the function works by itself.
Inside this JS file has the following code.
window.onload = function() {
var anchors = document.getElementsByClassName("vyper-triggers");
for (var i = 0; i < anchors.length; i++) {
var anchor = anchors[i];
anchor.onclick = function() {
if (isMobile.any()) {
window.open("$url");
} else {
document.getElementById("clickonthis").click();
}
}
}
}
Now my problem is when my user wants to add 2 different popup windows, the window.onload function doesn't stack. Also because this is an embedded javascript that my user adds himself, there is no way for me to put both functions inside one big window.onload function.
My user might put one JS file in one area of their site, and another JS file in another area, if that makes sense.
So how do I make it so that the window.onload function will stack no matter the placing of these external JS files on the page and considering that each function must be kept separate?
Rather than setting window.onload, you should use addEventListener. Listeners added this way will stack automatically.
window.addEventListener('load', function() {
console.log('First listener');
});
window.addEventListener('load', function() {
console.log('Second listener');
});
window.addEventListener('load', function() {
console.log('Third listener');
});
If you have to support versions of IE before IE9, there's a polyfill which will make this work correctly.
Probably you have multiple files and u want to check something onload.
Let's implement a basic function to add other functions and run all of them when the event onload is triggered.
So, first we check if windows.onload has a function object if not add our function. If is contains a function object merge it with our function like this:
function addLoadEvent(callback) {
const previous = window.onload
if (typeof previous === 'function') {
window.onload = (e) => {
if (previous) previous(e)
callback(e)
}
}
...
}
This is an example how to use it:
function addLoadEvent(callback) {
const previous = window.onload
if (typeof previous === 'function') {
window.onload = (e) => {
if (previous) previous(e)
callback(e)
}
} else {
window.onload = callback
}
}
function func1() {
console.log('This is the first.')
}
function func2() {
console.log('This is the second.')
}
addLoadEvent(func1);
addLoadEvent(func2);
addLoadEvent(() => {
console.log('This is the third.')
document.body.style.backgroundColor = '#EFDF95'
})

Backbone.Marionette extending region stops onClose() function from calling

I've created a Backbone, Marionette and Require.js application and am now trying to add smooth transitioning between regions.
To do this easily* ive decided to extend the marionette code so it works across all my pages (theres a lot of pages so doing it manually would be too much)
Im extending the marionette.region open and close function. Problem is that it now doesnt call the onClose function inside each of my views.
If I add the code directly to the marionette file it works fine. So I'm probably merging the functions incorrectly, right?
Here is my code:
extendMarrionette: function () {
_.extend(Marionette.Region.prototype, {
open : function (view) {
var that = this;
// if this is the main content and should transition
if (this.$el.attr("id") === "wrapper" && document.wrapperIsHidden === true) {
this.$el.empty().append(view.el);
$(document).trigger("WrapperContentChanged")
} else if (this.$el.attr("id") === "wrapper" && document.wrapperIsHidden === false) {
$(document).on("WrapperIsHidden:open", function () {
//swap content
that.$el.empty().append(view.el);
//tell router to transition in
$(document).trigger("WrapperContentChanged");
//remove this event listener
$(document).off("WrapperIsHidden:open", that);
});
} else {
this.$el.empty().append(view.el);
}
},
//A new function Ive added - was originally inside the close function below. Now the close function calls this function.
kill : function (that) {
var view = this.currentView;
$(document).off("WrapperIsHidden:close", that)
if (!view || view.isClosed) {
return;
}
// call 'close' or 'remove', depending on which is found
if (view.close) {
view.close();
}
else if (view.remove) {
view.remove();
}
Marionette.triggerMethod.call(that, "close", view);
delete this.currentView;
},
// Close the current view, if there is one. If there is no
// current view, it does nothing and returns immediately.
close : function () {
var view = this.currentView;
var that = this;
if (!view || view.isClosed) {
return;
}
if (this.$el.attr("id") === "wrapper" && document.wrapperIsHidden === true) {
this.kill(this);
} else if (this.$el.attr("id") === "wrapper" && document.wrapperIsHidden === false) {
//Browser bug fix - needs set time out
setTimeout(function () {
$(document).on("WrapperIsHidden:close", that.kill(that));
}, 10)
} else {
this.kill(this);
}
}
});
}
Why don't you extend the Marionette.Region? That way you can choose between using your custom Region class, or the original one if you don't need the smooth transition in all cases. (And you can always extend it again if you need some specific behavior for some specific case).
https://github.com/marionettejs/backbone.marionette/blob/master/docs/marionette.region.md#region-class
var MyRegion = Marionette.Region.extend({
open: function() {
//Your open function
}
kill: function() {
//Your kill function
}
close: function() {
//Your close function
}
});
App.addRegions({
navigationRegion: MyRegion
});
Perhaps your issue is that you are not passing a function to your event listener, but instead calling the code directly in the code below.
setTimeout(function(){
$(document).on("WrapperIsHidden:close", that.kill(that));
}, 10)
It is likely that you want something like this:
setTimeout(function(){
$(document).on("WrapperIsHidden:close", function (){ that.kill(that); });
}, 10)
Another possible problem is that you are mixing up your references to this/that in your kill function. It seems like you probably want var view to either be assigned to that.view or to use this rather than that throughout the method.
Answer to your additional problems:
You should try passing the view variable from the close function directly into your kill function because the reference to currentView is already changed to the new view object when you actually want to old view object. The reason this is happening is that you are setting a timeout before executing the kill function. You can see this if you look at the show source code. It expects close, open and then currentView assignment to happen synchronously in order.

firefox sdk: how to use emit for sidebar outside widget

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);
})

FireFox Toolbar Prefwindow unload/acceptdialog Event to Update the toolbar

I'm trying to develop a firefox toolbar ;)
so my structure is
In the options.xul is an PrefWindow which i'm opening over an
<toolbarbutton oncommand="esbTb_OpenPreferences()"/>
function esbTb_OpenPreferences() {
window.openDialog("chrome://Toolbar/content/options.xul", "einstellungen", "chrome,titlebar,toolbar,centerscreen,modal", this);}
so in my preferences i can set some checkboxes which indicates what links are presented in my toolbar. So when the preferences window is Closed or the "Ok" button is hitted I want to raise an event or an function which updates via DOM my toolbar.
So this is the function which is called when the toolbar is loaded. It sets the links visibility of the toolbar.
function esbTB_LoadMenue() {
var MenuItemNews = document.getElementById("esbTb_rss_reader");
var MenuItemEservice = document.getElementById("esbTb_estv");
if (!(prefManager.getBoolPref("extensions.esbtoolbar.ShowNews"))) {
MenuItemNews.style.display = 'none';
}
if (!(prefManager.getBoolPref("extensions.esbtoolbar.ShowEservice"))) {
MenuItemEservice.style.display = 'none';
}
}
So I tried some thinks like adding an eventlistener to the dialog which doesn't work... in the way I tried...
And i also tried to hand over the window object from the root window( the toolbar) as an argument of the opendialog function changed the function to this.
function esbTB_LoadMenue(RootWindow) {
var MenuItemNews = RootWindow.getElementById("esbTb_rss_reader");
var MenuItemEservice = RootWindow.getElementById("esbTb_estv");}
And then tried to Access the elements over the handover object, but this also not changed my toolbar at runtime.
So what i'm trying to do is to change the visibile links in my toolbar during the runtime and I don't get it how I should do that...
thanks in advance
-------edit-------
var prefManager = {
prefs: null,
start: function()
{
this.prefs = Components.classes["#mozilla.org/preferences-service;1"]
.getService(Components.interfaces.nsIPrefService)
.getBranch("extensions.esbtoolbar.");
this.prefs.QueryInterface(Components.interfaces.nsIPrefBranch2);
this.prefs.addObserver("", this, false);
},
end: function()
{
this.prefs.removeObserver("", this);
},
observe: function(subject, topic, data)
{
if (topic != "nsPref:changed")
{
return;
}
//Stuff what is done when Prefs have changed
esbTB_LoadMenue();
},
SetBoolPref: function(pref,value)
{
this.prefs.setBoolPref(pref,value);
},
GetBoolPref: function(pref)
{
this.prefs.getBoolPref(pref);
}
}
So this is my implementation.
The trick is to listen to preference changes. That way your toolbar updates whenever the prefs change -- regardless if it happened through your PrefWindow, about:config or some other mechanism.
In Toolbar.js you do the following
var esbTB_observe = function(subject, topic, data) {
if (topic != "nsPref:changed") {
return;
}
// find out which pref changed and do stuff
}
var esbTB_init = function() {
prefs =
Components.classes["#mozilla.org/preferences-service;1"]
.getService(Components.interfaces.nsIPrefService)
.getBranch("extensions.esbtoolbar.");
prefs.QueryInterface(Components.interfaces.nsIPrefBranch2);
prefs.addObserver("", esbTB_observe, false);
}
// Init addin after window loaded
window.addEventListener("load", esbTB_init, false);
Now, when the window loads, the esbTB_init() function is called in which the observer to the pref branch "extensions.esbtoolbar." is added. Later, when a pref in the branch is changed, the esbTB_observe() function is automatically called.
In esbTB_observe() you have to read the values of your prefs and adjust the toolbar.

Categories

Resources