I'm writing an extension that has a button with different functions.
So I'm trying to figure out how to retrieve number of tabs that are open and
in my content.js I want to execute different thing when clicking the button depending on how many tabs that are open.
Been trying different approaches with chrome.tabs.query in background.js and sending it back but having troubles since it's asynchronous.
content.js
if (tabcount < 2) {
$(function () {
$("#Back").on('click', function (e) {
e.preventDefault();
window.history.back();
});
});
console.log("one tab");
}
if (tabcount > 1) {
$(function () {
$("#Back").on('click', function (e) {
e.preventDefault();
window.location.href = 'exit';
});
});
console.log("more tabs");
}
Edit: added message code
Background message code
function sendMessageToTab(tabId, message) {
return new Promise((resolve) => {
chrome.tabs.sendMessage(tabId, message, resolve)
})
}
chrome.tabs.onUpdated.addListener(function (tabId, changeInfo, tab) {
if (changeInfo.status == 'complete') {
chrome.tabs.query({ windowType: 'normal' }, function (tabs) {
const msg = tabs.length;
sendMessageToTab(tabs[0].id, msg)
});
} return true;
});
content recieve
chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
if (request > 1) {console.log('more than 1 tab open'); }
});
Related
What I want to do:
opening an popup and send the postMessage when the popup is ready.
Problem:
I ran into Race Condition which the popup is not ready but the message is sent. I tried to listen to the "INIT" message and in the popup send back message. But the problem is when network latency or some slow computer will not receive the initial message.
The setTimeout obviously not a good solution
Code I have problem with:
Parent Window
const windowFeatures = "height=800,width=1280,toolbar=1,menubar=1,location=1";
const printWindow = window.open("/print", "Print_Me", windowFeatures);
setTimeout(() => {printWindow.postMessage({printPageStatus: "INIT"}, window.origin)}, 1000)
window.addEventListener("message", (event) => {
if(event.origin !== window.origin) return;
if(event.data.printPageStatus === "READY") {
printWindow.postMessage({message: "from parent", window.origin);
return;
}
});
The popup window
constructor() {
window.addEventListener("message", event => {
if(event.origin !== window.origin) return;
if(event.data.printPageStatus === "INIT")
this.sendReadyConfirmation(event);
if(event.data.message === "from parent") {
this.processMessages(event.data);
}
}, false);
}
sendReadyConfirmation(e): void {
e.source.postMessage({printPageStatus: "READY"}, e.origin);
}
Thank you
What you need to do is send the message when the window has loaded successfully :
const printWindow = window.open("/print", "Print_Me", windowFeatures);
printWindow.onload = () => {
printWindow.postMessage({printPageStatus: "INIT"}, window.origin)
};
I created long-lived connection between popup and content pages. It works successfully. But I want to send message to content when user click the button.
contentscript.js
console.log("content script loaded!");
var port = chrome.runtime.connect({name: "content-script"});
port.onMessage.addListener(function(message){
if(message.key=="color"){
document.body.style.backgroundColor='lightgrey';
}
else if(message.key=="color2"){
document.body.style.backgroundColor='blue';
}
});
popup.js
document.addEventListener('DOMContentLoaded', function() {
var checkPageButton = document.getElementById('btnStart');
checkPageButton.addEventListener('click', function() {
GetImages("");
}, false);
}, false);
function GetImages(pageURL){
if(activePort!=null){
activePort.postMessage({key:"color2"});
}
}
var activePort=null;
chrome.runtime.onConnect.addListener(function (port) {
console.assert(port.name == "content-script");
activePort=port;
port.onMessage.addListener(function(message) {
if(message.key=="download"){
port.postMessage({key:"download", text: "OK"});
}
else if(message.key="load"){
port.postMessage({key:"color"});
console.log(message.text);
}
})
});
In the GetImages() function I try to
port.postMessage({key:"color2"});
naturally it can't find the "port". So I create the activePort variable and try to post message with it. But It didn't work properly.
I changed the codes below and it works correctly. Now I opened the port in popup.js and I can send message with button click.
newpopup.js
function GetImages(pageURL){
port.postMessage({text:"ok"});
}
var port;
chrome.tabs.query({active: true, currentWindow: true}, function(tabs) {
port = chrome.tabs.connect(tabs[0].id,{name: "knockknock"});
});
newcontentscript.js
chrome.runtime.onConnect.addListener(function(port) {
console.assert(port.name == "knockknock");
port.onMessage.addListener(function(msg) {
console.log(msg);
});
});
I want to make a small extension that injects a simple html into a YouTube page right under the video. It works fine if I simple visiting a youtube url. However if I choose a video from youtube offers then my html code is injected twice but removed. I can see that it to appear and then disappear almost immediately.
My code is:
background.js
chrome.tabs.onUpdated.addListener(function(tabId, changeInfo, tab) {
if ( changeInfo.status == 'complete' && tab.status == 'complete' && tab.url != undefined ) {
chrome.tabs.query({active: true, currentWindow: true}, function(tabs) {
chrome.tabs.sendMessage(tabs[0].id, {method: "reDraw"}, function(response) {
console.log("Injection ready!");
});
});
}
});
content.js
chrome.runtime.onMessage.addListener(
function(request, sender, sendResponse) {
if (request.method == "reDraw") {
$.get(chrome.extension.getURL('/mytest.html'), function(data) {
$(data).insertAfter('#placeholder-player');
});
}
}
);
chrome.tabs.onUpdated will also fire for iframes, and for youtube, there are many iframes will trigger this event, besides that, youtube doesn't reload the page when you go from one video to another. For more details about youtube issue, you could take a look at these threads:
https://bugs.chromium.org/p/chromium/issues/detail?id=165854
Chrome webNavigation.onComplete not working?
chrome extension chome.tabs.onUpdate running twice?
So my recommendation would be using chrome.webNavigation api, and combining webNavigation.onCompleted with webNavigation.onHistoryStateUpdated, sample code would look like the following
Considering you are detecting youtube video page, I would suggest you used chrome.webNavigation.onHistoryStateUpdated
// To handle youtube video page
chrome.webNavigation.onHistoryStateUpdated.addListener(function(details) {
if(details.frameId === 0) {
// Fires only when details.url === currentTab.url
chrome.tabs.get(details.tabId, function(tab) {
if(tab.url === details.url) {
console.log("onHistoryStateUpdated");
}
});
}
});
Here is a way to only fire one time using chrome.webNavigation.onHistoryStateUpdated
// background.js
'use strict';
console.log('QboAudit Background Script');
chrome.runtime.onInstalled.addListener(details => {
console.log('QboAudit previousVersion', details.previousVersion);
})
chrome.webNavigation.onHistoryStateUpdated.addListener( (details) => {
console.log('QboAudit Page uses History API and we heard a pushSate/replaceState.')
if(typeof chrome._LAST_RUN === 'undefined' || notRunWithinTheLastSecond(details.timeStamp)){
chrome._LAST_RUN = details.timeStamp
chrome.tabs.getSelected(null, function (tab) {
if(tab.url.match(/.*\/app\/auditlog$/)){
chrome.tabs.sendRequest(tab.id, 'runQboAuditLog')
}
})
}
})
const notRunWithinTheLastSecond = (dt) => {
const diff = dt - chrome._LAST_RUN
if (diff < 1000){
return false
} else {
return true
}
}
In short you make a global var chrome._LAST_RUN to track if this event has already fired less than a second ago.
// contentscript.js
chrome.extension.onRequest.addListener((request, sender, sendResponse) => {
//console.log('request', request)
if (request == 'runQboAuditLog')
new QboAuditLog()
});
I guess youTube is using Ajax calls on some pages to load things, so your code is being replaced with the Ajax response.
Use a setTimeout() function to check after a few seconds if your code is on the page, if it's not, add it again.
I have the following code in popup.js which responds to a button press to get all the tabs, create a new tab (template.html), and send the tabs as an array to the new tab. Later I will delete the current tabs and display the links on one page to save space (That's the idea of the extension).
function saveAll() {
var openTabs = [];
chrome.tabs.query({}, function(tabs) {
for (var i = 0; i < tabs.length; i++) {
openTabs[i] = tabs[i];
}
createSavedPage(openTabs);
});
}
function createSavedPage(tabsToSave) {
chrome.tabs.create({
"url": chrome.extension.getURL("template.html"),
"active": false
},
function(tab) {
sendListToPage(tab, tabsToSave);
}
);
}
function sendListToPage(tab, tabsArray) {
chrome.tabs.sendMessage(tab.id, {
"action": "fill-list",
"data": tabsArray
});
}
var saveAllButton = document.getElementById("select-all-tabs");
saveAllButton.addEventListener("click", saveAll);
The tab that is created includes a template.js file:
function fillList(array) {
var list = document.getElementById("link-list");
for (var item in array) {
//Something
}
}
chrome.runtime.onMessage.addListener(
function(request, sender, sendResponse) {
if (request.action == "fill-list") {
var data = request.data;
fillList(data);
}
}
);
When I create the new tab, the message is sent from the extension but never received. What is causing this issue?
I also thought that the message was being sent before the created tab was fully loaded. So I've changed popup.js by adding a chrome.tabs.onUpdated listener:
function createSavedPage(tabsToSave) {
chrome.tabs.create({
"url": chrome.extension.getURL("template.html"),
"active": false
},
function(tab) {
chrome.tabs.onUpdated.addListener(function(tabId, info) {
if (tabId == tab.id && info.status == "complete")
sendListToPage(tab, tabsToSave);
});
}
);
}
And it seems to solve the issue. Is there still a better way?
I send a message from a ContextMenu within content.js to background.js. When I send a message, I expect to see an alert of just two variables which are sent with the request. When I send multiple request(few times in a row) I receive alerts including previously sent messages. It seems that all messages are stored somewhere. How do you disable this? I would like to see alerts of only the most recent message.
contents.js:
$(document).mousedown(function(event) {
var url = window.location.href;
var contents = window.getSelection().toString();
switch (event.which) {
case 3:
chrome.runtime.sendMessage({contents: contents, url: url}, function(response) {
//console.log(response.farewell);
});
break;
}
});
background.js
chrome.extension.onMessage.addListener(function (message, sender, sendResponse) {
if (message.url) {
chrome.contextMenus.onClicked.addListener(function testFunc2(info, tab){
alert(message.url);
alert(typeof message.contents);
}
)
}
});
manifest.json
"background": {
"scripts": ["jquery-1.11.1.min.js", "background.js"],
//"page": "background.html",
"persistent": true
},
It's because of this code
chrome.extension.onMessage.addListener(function (message, sender, sendResponse) {
if (message.url) {
chrome.contextMenus.onClicked.addListener(function testFunc2(info, tab){
alert(message.url);
alert(typeof message.contents);
}
)
}
});
What you are saying is that every onMessage event add a listener for onClicked events. So if you send three messages you end up with three testFunc2 methods acting on onClicked events.
Since you are trying to use information from two different asynchronous events. You will have to store one of them temporarily. Something like this would probably work.
var lastMessage;
chrome.extension.onMessage.addListener(function(message, sender, sendResponse) {
if (message.url) {
lastMessage = message;
} else {
lastMessage = undefined;
}
});
chrome.contextMenus.onClicked.addListener(function(info, tab) {
if(lastMessage !== undefined) {
testFunc2(message, info, tab);
}
});
function testFunc2(info, tab){
alert(message.url);
alert(typeof message.contents);
// cleanup
lastMessage = undefined;
});