browserAction.setBadgeText isn't being executed when used in tabs.executeScript callback - javascript

I'm working on my first Chrome extension. I have a default popup popup.html which loads popup.js.
I used serg's answer to Chrome's tabs.executeScript - passing parameters and using libraries? as inspiration for the popup->page interaction.
The problem is that the following click handler in popup.js works:
function click(e) {
chrome.browserAction.setBadgeText ( { text: "loading" } );
chrome.tabs.executeScript(null,
{code:"globalVarName = {'scriptOptions': {...}};" },
chrome.tabs.executeScript(null, {file: "js/script.js"},
chrome.browserAction.setBadgeText ( { text: "done" } ))
);
window.close();
}
But the following does not:
function click(e) {
chrome.browserAction.setBadgeText ( { text: "loading" } );
chrome.tabs.executeScript(null,
{code:"globalVarName = {'scriptOptions': {...}};" },
chrome.tabs.executeScript(null, {file: "js/script.js"},
function(){chrome.browserAction.setBadgeText ( { text: "done" } );})
);
window.close();
}
I want to be able to do more than one thing on completion.
Edit:
I've realised that the first case immediately executes chrome.browserAction.setBadgeText(), not when the script has finished executing. So that case can be ignored. I've reworded the question title to reflect this.
What I'm looking for is why the second case's callback doesn't execute at all.

I'm pretty sure the culprit here is window.close() which closes the popup. The same popup in which this code is executing (except script.js, that's executing on the actual page).
Therefore, the callback was never being executed. I'm only talking of course about case 2 here (see my edit to the question).
My latest fully working code for any future visitors:
var tabId = null;
function click(e) {
chrome.browserAction.setBadgeText ( { text: "..." } );
chrome.tabs.executeScript(tabId,
{code:"globalVarName= {...}" },
function(){
chrome.tabs.executeScript(tabId, {file: "js/script.js"},
function(){chrome.browserAction.setBadgeText ( { text: "done" } );
setTimeout(function() {
chrome.browserAction.setBadgeText ( { text: "" } );
}, 1000);
}
);
}
);
}
Also note that the path to the script (script.js here) is relative to the extension source root, i.e. where the manifest.json is.

Related

Creating and accessing global variable in google chrome extension

All of the information I can find on this is pretty old. Like the title says I am trying to make a global variable in one script and access it from another. The purpose of the extension is to search for a class named "page-title" and then return the innerHTML of that HTML element. Once I get the code working I will specify the URL I want the extension to run on so it's not constantly running.
After a couple iterations trying to accomplish this in different ways I followed the method explained in this answer but my needs have different requirements and I am receiving the error "Unchecked runtime.lastError: Could not establish connection. Receiving end does not exist." tied to the popup.html.
I tried the Unchecked runtime error solution found here but it's been awhile (~ 7 years) since I've dived into any coding and I'm not sure I implemented it correctly.
I've also tried to pass the value between JS documents is the HTML injection method, but without overriding security defaults in the manifest that doesn't really work. It also seemed super bootstrappy and I wanted to pass the information in a more conventional way. I tried creating a global variable by simply declaring the variable outside of a function/class/if statement and loading that .js file first, but that was unsuccessful as well.
Manifest
"name": "P.P. to Sharepoint",
"version": "1.0.0",
"description": "Open P.P. client folder in sharepoint",
"manifest_version": 3,
"author": "Zach Morris",
"action":{
"default_popup": "popup.html",
"default_title": "Open Sharepoint Folder"
},
"background": {
"service_worker": "background.js"
},
"permissions": [
"activeTab",
"tabs",
"scripting",
"notifications"
],
"content_scripts": [{
"js": ["contentScript.js"],
"matches": ["<all_urls>"]
}]
}
popup.html
My popup.html is super simple and really just has a button to press. I included all the .js files in the order I thought necessary
<script src="globalVariable.js"></script>
<script src="contentScript.js"></script>
<script src="popup.js"></script>
<script src="script.js"></script>
<script src="background.js"></script>
globalVariable.js
This one is straight forward. I need to pull the client's name out of the HTML of the page then use it in an API call when I click the button in popup.js This initializes the variable and uses it as place holder.
var clientInfo = {
name: 'test name'
};
ContentScript.js
I only want to run this if importScripts is not undefined. So I threw it in the if statement. Then I make sure I pulled a client name from the page. If not I throw an error message saying no client was found.
if( 'function' === typeof importScripts) {
importScripts('globalVariable.js');
addEventListener('message', onMessage);
function onMessage(e) {
if(b[0]) {
clientInfo.name = b[0].innerHTML;
alert(clientInfo.name + ' was assigned!');
} else {
alert('There is no client on this screen ' + 'b[0] is ' + b[0] + " clientInfo = " + clientInfo.name);
};
};
} else {
console.log("Your stupid code didn't work. ");
}
popup.js
This one pulls up the globalVariable.js to use the clientInfo. and makes a call to the button in background.js
if( 'function' === typeof importScripts) {
importScripts('globalVariable.js');
addEventListener('message', onMessage);
function onMessage(e) {
const text = clientInfo.name;
const notify = document.getElementById( 'myButton' );
notify.addEventListener( 'click', () => {
chrome.runtime.sendMessage( '', {
type: 'notification',
message: text });
} );
}
}
background.js
Same thing here. I import the globalVariable script to use the global variable. The notification will eventually be replaced with the API call when the rest of the code is working properly. I probably don't need to import the script here to access the variable because I can mass it with the event listener in popup.js, but I put it in here out of desperation.
if( 'function' === typeof importScripts) {
importScripts('globalVariable.js');
addEventListener('message', onMessage);
function onMessage(e) {
// do some work here
chrome.runtime.onMessage.addListener( data => {
if ( data.type === 'notification' ) {
chrome.notifications.create(
'',
{
type: 'basic',
title: 'Notify!',
message: data.message || 'Notify!',
iconUrl: 'notify.png',
}
);
console.log("sent notification");
};
});
}
}
You can have the popup.js listen for a button click and content.js handle all the logic of finding the correct element.
popup.js
document.querySelector('#btn').addEventListener('click', () => {
chrome.tabs.query({ active: true, currentWindow: true }, (tabs) =>
chrome.tabs.sendMessage(tabs[0].id, { command: 'getClientName' })
);
});
content.js
chrome.runtime.onMessage.addListener((msg, sender, response) => {
if (msg.command === 'getClientName')
findClientName(document.querySelectorAll('h3.page-title'));
});
Example of findClientName function:
const findClientName = async (element) => {
let clientName;
if (element.length > 0) {
element.length === 1
? (clientName = setClientName(element[0]))
: handleMultipleElements(element);
} else {
handleNoClientNameFound();
}
clientName ? await makeAPIRequest(clientName) : null;
};
Try this method instead maybe?
{
var x = 2;
}
so:
{
var clientInfo = {
name: 'test name'
};
}
Not very good at this language, so I thought maybe you're missing the brackets?

chrome extension - issue with propertie "active" of chrome.tabs.create

I am creating a new tab and and injecting some code in it straight after.
But the problem is that the code to be injected is not injected properly when using the property active:true(which I need to use) on tabs.create.
Here is the code in popup.js:
chrome.tabs.create({url: "http://localhost:3000/", index: newTabId, active: false}, (newTab) => {
chrome.tabs.onUpdated.addListener(function(tabId, changeInfo, tab) {
// check status so that it sends only one message, and not one for each status change
if(changeInfo.status === "loading") {
if (tab.id === newTab.id) {
chrome.tabs.sendMessage(newTab.id, {message: "watch_video", videoData: selectedVideoData},
function (resp) {
console.log("Resp",resp);
return true;
}
);
}
}
});
})
Here is the problematic line: chrome.tabs.create({url: "http://localhost:3000/", index: newTabId, active: false}. When active is false, the code is injected, but when it is true, nothing seems to happen.
inject.js:
chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
if (request.message === "watch_video") {
console.log("inject soon")
injectScript(request.videoData);
sendResponse("watch_video script is to be injected in " + window.location.href)
}
});
function injectScript(videoData) {
console.log("injected")
$(document).ready(function() {
document.test = "ABCDE"
const checkState = setInterval(() => {
$(".bkg").css({ "background-color": "#ffffff"})
}, 100)
})
}
Here I tried something with setInterval(), it does not work when active is true.
However it does work with a timeout. But does not not work without any timeout or interval when active is set to true.
I could use just use a timeout, but it is not really clean, I would prefer to understand why it behaves like it does. I am using react btw.
Here is what it is said about the active property:
Whether the tab should become the active tab in the window. Does not affect whether the window is focused (see windows.update). Defaults to true.
Source: https://developer.chrome.com/extensions/tabs#method-create

Chrome inject script inconsistency issue

Below function I did was to redirect user to a login page, and then inject a js to login the user. The code below worked well but not consistent, I hardly can debug it because the flow contain refresh of the whole page.
in my setLogin.js I try to debug with alert() wrap within $(function(){}); I found that sometime it run sometime it doesn't. So I suspect the script sometime got injected sometime not, but why is it like so?
chrome.tabs.update(null, {
url: 'https://example.com/index.php?act=Login'
}, function () {
chrome.tabs.onUpdated.addListener(function (tabId, changeInfo, tab) {
if (changeInfo.status == 'complete') {
chrome.tabs.executeScript(null, {
file: "jquery.js"
}, function () {
chrome.tabs.executeScript(null, {
code: 'var passedData = {username:"' + username + '",pass:"' + pass+'"}'
}, function () {
chrome.tabs.executeScript(null, {
file: "setLogin.js"
}, function () {
window.close(); //close my popup
});
});
});
}
});
});
By default scripts are injected at document_idle which doesn't work consistently with jQuery, probably because it's big or uses some asynchronous initialization.
Solution: explicitly specify that the injected scripts should run immediately.
chrome.tabs.executeScript({file: "jquery.js", runAt: "document_start"}, function(result) {
});

FireFox AddOn SDK close current tab

I am porting a Chrome Extension for FireFox using the Add-On SDK. I am using require("sdk/page-mod") to run a content script at the start of the document.
In the code, I need to close the current tab if some condition is met. In Chrome, I can send a message to the background.js file to have it close the current tab, but I am not able to figure this out for Firefox.
window.close() is very unreliable and I need to figure out a way to call a function in the main.js file from my content script.
Appreciate your help.
EDIT:
Below is my Chrome code, I need to port the same to FF AddOn SDK (FF Extension).
//in the content.js file
function closeCurrTab() {
chrome.runtime.sendMessage({action: "closeTab"}, function() {});
}
//below in the background.js file
chrome.runtime.onMessage.addListener(
function(request, sender, sendResponse) {
switch (request.action) {
case 'closeTab':
try {
chrome.tabs.getSelected(function(tab) {removeTab(tab.id);});
} catch (e) {
alert(e);
}
break;
}
}
);
function removeTab(tabId) {
try {
chrome.tabs.remove(tabId, function() {});
} catch (e) {
alert(e);
}
}
in content script:
self.port.emit("close-tab");
in main.js
PageMod({
include: "*",
contentScriptFile: "./content-script.js",
onAttach: function(worker) {
worker.port.on("close-tab", function() {
tabs.activeTab.close();
});
}
});
The following might help if you are developing an extension of firefox:
function onError(error) {
console.log(`Error: ${error}`);
}
function onRemoved() {
console.log(`Removed`);
}
function closeTabs(tabIds) {
removing = browser.tabs.remove(tabIds);
removing.then(onRemoved, onError);
}
var querying = browser.tabs.query({currentWindow: true});
querying.than(closeTabs, onError);
This will close the current tab:
require("sdk/tabs").activeTab.close();
Here's an expanded example that implements a toolbar button that closes the current tab ( silly example, I know ):
var ActionButton = require("sdk/ui/button/action").ActionButton;
var button = ActionButton({
id: "my-button-id",
label: "Close this tab",
icon: {
"16": "chrome://mozapps/skin/extensions/extensionGeneric.png"
},
onClick: function(state) {
require('sdk/tabs').activeTab.close();
}
});
For more info, please see the documentation for the tabs module.

Chrome Extension -> ExecuteScript: function call doesn' work

He is my manifest.json:
{
"name": "Page Redder",
"description": "Make the current page red",
"version": "2.0",
"permissions": [
"activeTab","*://*/*"
],
"background": {
"scripts": ["background.js"],
"persistent": false
},
"browser_action": {
"default_icon" : "icon.png",
"default_title": "Make this page red"
},
"manifest_version": 2
}
Here is background.js that works (the page becomes red):
chrome.browserAction.onClicked.addListener(function(tab) {
chrome.tabs.executeScript(null, {code:'document.body.style.backgroundColor="red";'} );
});
If I change background.js in the following way, it fails to work:
function changeColor() {
document.body.style.backgroundColor="red";
}
chrome.browserAction.onClicked.addListener(function(tab) {
chrome.tabs.executeScript(null, {code:';'}, function() {
changeColor();
});
});
Chrome build: 38.0.2125.111
The question: what am I doing wrong here? Why calling a function in executeScript doesn't work?
Thanks,
Racoon
You are not calling a function in executeScript.
You are calling the function in its callback, that runs in the original (background) page. It's a function describing "what to do when executeScript finishes", not the code to run.
The code that actually runs in the page you're injecting code to is ";" (which obviously does nothing).
You can run a function defined in your code with executeScript by properly converting it into a string. But note that it will not have any access to variables defined outside the function.
I think what you're trying to do is to make the code accept a parameter (color). Instead of crafting custom code each time, you should consider using Messaging to pass a command.
Example: add a file content.js with the following:
// Include guard: only execute once
if (!injected) {
injected = true;
init();
}
function init() {
// Get ready to receive a command
chrome.runtime.onMessage.addListener(function(message, sender, sendResponse) {
if(message.action == "colorBackground") {
document.body.style.backgroundColor = message.color;
}
});
}
And in your background, you can do this:
chrome.tabs.executeScript(null, {file: "content.js"}, function() {
// File executed, it's ready for the message
chrome.tabs.sendMessage(null, { action: "backgroundColor", color: "green" });
}

Categories

Resources