When the chrome.tabs.create function is triggered by a message received, it creates 2 tabs. In the following demo code, 1 cat tab is created, and 2 dog tabs are created.
Is this by design or is it a bug? If it is a known bug, can you provide the bug id so I can track its progress? How can I avoid 2 duplicate tabs being created?
The debug console contains the following output, so in fact the duplicate tab is also getting the content script injected even though only one call to secondaryTabCreationCallback_ is printed in the debug output!!!!
Creating secondary tab
Created secondary tab: 11968
Kill request from tab: 11966
Kill request from tab: 11968
background.js
var handler = {
url1_: 'https://www.google.com/?gws_rd=ssl#q=cat',
url2_: 'https://www.google.com/?gws_rd=ssl#q=dog',
windowId_: chrome.windows.WINDOW_ID_CURRENT,
createPrimaryTab: function() {
chrome.tabs.create(
{'url': handler.url1_, 'active': false, 'windowId': handler.windowId_},
handler.primaryTabCreationCallback_);
},
primaryTabCreationCallback_: function(tab) {
chrome.tabs.executeScript(tab.id, {file: "content_script.js"});
},
createSecondaryTab_: function() {
console.log("Creating secondary tab");
chrome.tabs.create(
{'url': handler.url2_, 'active': false, 'windowId': handler.windowId_},
handler.secondaryTabCreationCallback_);
},
secondaryTabCreationCallback_: function(tab) {
console.log("Created secondary tab: " + tab.id);
chrome.tabs.executeScript(tab.id, {file: "content_script2.js"});
},
};
chrome.runtime.onMessage.addListener(function(message, sender, sendResponse) {
switch (message.type) {
case "CREATE_TAB":
handler.createSecondaryTab_();
break;
case "KILL_ME":
console.log("Kill request from tab: " + sender.tab.id);
// chrome.tabs.remove(sender.tab.id);
break;
default:
alert("Not Reached");
break;
}
});
chrome.browserAction.onClicked.addListener(function(tab) {
chrome.tabs.create({'url': chrome.extension.getURL('background.html')});
});
window.onload = function() {
document.getElementById("start_button").onclick = handler.createPrimaryTab;
}
content_script.js
chrome.runtime.sendMessage({type: "CREATE_TAB"});
content_script2.js
chrome.runtime.sendMessage({type: "KILL_ME"});
background.html
<!doctype html>
<html>
<head>
<script src="background.js"></script>
</head>
<body>
<div>
<input type="button" id="start_button" value="Start">
</div>
</body>
</html>
manifest.json
{
"manifest_version": 2,
"name": "Tab Bug",
"description": "Demonstrates bug in chrome.tabs.create.",
"version": "1.0",
"permissions": [
"activeTab",
"nativeMessaging",
"tabs",
"https://www.google.com/"
],
"icons": { "128": "icon128.png" },
"browser_action": {
"default_icon": "icon19.png"
},
"background": {
"page": "background.html"
}
}
The issue is that there are 2 "background" pages running.
The official background page specified in the manifest file.
The tab created by chrome.tabs.create({'url':
chrome.extension.getURL('background.html')}).
This means there are 2 message listeners, which is why 2 tabs are opening.
The console messages from the official manifest.json background can be found by looking at extension on the chrome extensions page and click on the "Inspect views: background.html". Which shows:
Creating secondary tab
Created secondary tab: 11966
Kill request from tab: 11966
Kill request from tab: 11968
To work around this issue. The manifest.json background file can point to a script "starter.js" instead of an html page, which simply has the following javascript:
chrome.browserAction.onClicked.addListener(function(tab) {
chrome.tabs.create({'url': chrome.extension.getURL('background.html')});
});
My background.js is very simple.
chrome.browserAction.onClicked.addListener(() => {
chrome.tabs.create({
url: chrome.runtime.getURL('popup.html'),
})
})
manifest.json
"background": {
"persistent": false,
"scripts": [
"background.js"
]
},
Tried to disable/enable the extension (so that chrome destroys all the background pages from this extension) but it still opens duplicate tabs!!
So the problem might be there is someone that included the background.js. And guess what! It's popup.html.
The root cause is HtmlWebpackPlugin from webpack.config.js. It defaults to include all chunks (including background).
So we just exclude the background chunk from popup.html config then it should work as expected.
new HtmlWebpackPlugin({
template: 'public/popup.html',
filename: 'popup.html',
excludeChunks: ['background'],
}),
Related
I got the extension to add "dev." to the current URL in a new tab which is working through a pop-up.
I'm trying to disable/remove the pop-up so that the code will work only when pressing the extension button instead.
popup.js:
document.addEventListener("DOMContentLoaded", function() {
console.log("Extension button clicked!");
var button = document.getElementById("change-url");
button.addEventListener("click", function() {
chrome.tabs.query({ active: true, currentWindow: true }, function(tabs) {
var url = new URL(tabs[0].url);
url.hostname = "dev." + url.hostname;
chrome.tabs.create({ url: url.toString() });
});
});
});
manifest.json:
{
"name": "My URL Changer",
"version": "1.0",
"manifest_version": 3,
"permissions": ["scripting"],
"host_permissions": ["<all_urls>"],
"action": {
"default_popup": "popup.html",
"default_title": "My URL Changer"
}
}
popup.html:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>My URL Changer</title>
<script src="popup.js"></script>
</head>
<body>
<h1>My URL Changer</h1>
<button id="change-url">Change URL</button>
</body>
</html>
As woxxom mentioned, the chrome.action.onClicked API is a good fit for this.
The event does not fire if the extension has a popup, so you'll want to remove the default_popup key. However, you'll need to keep the empty action object. This tells Chrome to show an icon for your extension and is also required to make the API available.
With those changes in mind, your manifest would look like this:
{
"name": "My URL Changer",
"version": "1.0",
"manifest_version": 3,
"permissions": [
"scripting"
],
"host_permissions": [
"<all_urls>"
],
"action": {}
}
You should also consider removing <all_urls> and replacing it with just the domains you want your extension to run on, since it's always best to request just the access you need. You could also remove this key entirely and use the activeTab permission instead, which gives your extension access to pages only when the action icon is clicked.
You'll then need to declare a service worker by adding the following to your manifest:
"background": {
"service_worker": "worker.js"
}
And in worker.js, you can copy your code from popup.js making sure to use the action API instead of DOMContentLoaded.
I am pretty new in the Chrome Extensions field.
I am trying to build an extension to visit some websites and collect information from their HTML.
I am having a hard time getting chrome.scripting.executeScript working properly.
Source Code
manifest.json
{
"name": "Scraper",
"description": "Simple Extension to Scrape Websites, and Push Them to ConnectionSphere for Data Enrichment",
"version": "2.0",
"permissions": ["storage", "webRequest", "scripting", "declarativeContent", "activeTab", "tabs", "downloads", "*://*/*", "http://connectionsphere.com:80/*", "http://connectionsphere.com/api1.0/emails/verify.json"],
"host_permissions": [ "https://www.amazon.com/*", "https://connectionsphere.com/*", "https://github.com/*" ],
"manifest_version": 3,
"icons": {
"48": "/48.png",
"128": "/128.png"
},
"action": {
"default_popup": "popup.html",
"default_icon": {
"48": "/48.png",
"128": "/128.png"
}
}
}
popup.html
<!DOCTYPE html>
<html>
<head>
<title>Scraper</title>
</head>
<body>
<div id="notify-wrapper">
<div id="notify-header">
<h1>Scraper!</h1>
</div>
<div id="notify-containers">
<div class="login-form">
<input type="button" id="start" name="start" value="Start" />
<p id="text" name="text"></p>
</div>
</div>
<script src="popup.js"></script>
</body>
</html>
popup.js
'use strict';
let text = document.getElementById('text');
function upload_page() {
alert('b');
document.body.innerHTML = 'hola';
return document.title;
}
start.onclick = function() {
let page_url_value = 'https://github.com/';
text.innerHTML = 'Scraping page...';
chrome.tabs.query({active: true, currentWindow: true}, async function(tabs) {
// get the tab id
var tab_id = tabs[0].id;
// go to the page
chrome.tabs.update({url: page_url_value});
// fired when tab is updated
chrome.tabs.onUpdated.addListener(function openPage(tabID, changeInfo) {
// tab has finished loading
if(tab_id == tabID && changeInfo.status === 'complete') {
// remove tab onUpdate event as it may get duplicated
chrome.tabs.onUpdated.removeListener(openPage);
// execute content script
alert('a');
chrome.scripting.executeScript(
{
target: {tabId: tab_id, allFrames: true},
func: upload_page
},
// Get the value returned by do_login.
// Reference: https://developer.chrome.com/docs/extensions/reference/scripting/#handling-results
(injectionResults) => {
for (const frameResult of injectionResults)
text.innerHTML = frameResult.result;
}
);
}
});
});
};
The Problem
When the user clicks on the "start" button of the popup, the extension visits a page and calls chrome.scripting.executeScript to run a function that works with the DOM of such a page. But such execution is not performed (thealert('a') happens, but the alert('b') is never executed).
If I refresh the page manually from the browser and click on the "start" button again, then the chrome.scripting.executeScript runs.
I researched for an answer into other posts here, but I didn't find anything.
The answers in this post didn't help me.
I wrote a small test-unit to show the problem I am facing.
It worked after I removed the extension and added it again. Thanks!
Hi I'm doing a exercise in which I have to capture the content of a "p" tag inside on any page and show by "alert"
manifest.json
{
"manifest_version": 2,
"name": "Get text",
"description": "Get text",
"version": "1.0",
"browser_action": {
"default_title": "Get text"
},
"background": {
"page": "background.html"
},
"permissions": [
"tabs",
"activeTab"
]
}
background.html
<div>BLA BLA BLA</div>
<p> p tag </p>
<script src="jquery-3.3.1.js"></script>
<script src="background.js"></script>
here I check that I was capturing the elements of this html page instead of the page opened in the tab
background.js
chrome.browserAction.onClicked.addListener(function() {
alert("Text: " + $("p").text());
});
I'd tried to do in the easiest way, I'd been reading numerous post with a lot of diferents methods but none of them works
Try this in your background.js:
chrome.browserAction.onClicked.addListener(function(tab) {
chrome.tabs.executeScript(null, {
code: "alert(document.querySelector('p').innerText)"
});
});
You need to run the script in context of the current tab. To do that you have to use chrome.tabs.executeScript. You can also specify a file to run instead of code. Checkout the documentation.
As a learning exercise I'm attempting to build an example Chrome extension that ensures sites on a 'greylist' are always opened in an incognito window.
Here's how far I have got - using the webNavigation.onBeforeNavigate event fired when a grey listed page is about to be navigated to I open a new tab in an incognito window, but now need to prevent the original tab from opening the page.
manifest.js:
"permissions": [
"webNavigation",
"tabs"
],
"background": {
"scripts": [
"background.js"
],
"persistent": false
},
background.js:
chrome.webNavigation.onBeforeNavigate.addListener(function(details) {
chrome.tabs.get(details.tabId, function(tab) {
if(!tab.incognito) {
// Open the page in an incognito window
chrome.windows.create({ url: details.url, incognito: true});
// TODO stop non-incognito tab opening page!
}
});
}, {
url: [
{ hostEquals: 'badsite.com' }
],
});
To stop the navigation use window.stop() by injecting a content script in the tab:
chrome.tabs.executeScript(details.tabId, {code: 'window.stop()'});
Add a permission in manifest.json, otherwise you'll see an error in the background page console:
"permissions": [
"webNavigation",
"tabs",
"<all_urls>"
],
Partial answer arrived at from wOxxOm's input and further experiments and reading - to at least document what I found out.
manifest.js:
"permissions": [
"webNavigation",
"tabs",
"<all_urls>" // Note: Permission
],
...
background.js:
// Note: onCommitted
chrome.webNavigation.onCommitted.addListener(function(details) {
chrome.tabs.get(details.tabId, function(tab) {
if(!tab.incognito) {
// Stop non-incognito tab opening page
// Note runAt: "document_start"
chrome.tabs.executeScript(details.tabId, { runAt: "document_start", code: 'window.stop(); '})
// Open the page in an incognito window
chrome.windows.create({ url: details.url, incognito: true});
}
});
}, {
url: [
{ hostEquals: 'badsite.com' }
],
});
Listening for chrome.webNavigation.onCommitted events instead of onBeforeNavigate allows the script injected by chrome.tabs.executeScript to run when a grey listed page is navigated to from a new tab and a url is pasted into the omni box.
This prevents the grey listed page from being displayed, but the page is at least partially loaded. A history entry is not created but cookies or local storage items are created, so it does not meet the ultimate need of my original question.
two ways:
base on #wOxxOm
chrome.webNavigation.onBeforeNavigate.addListener((details) => {
chrome.tabs.executeScript(tabid,{code: 'window.stop()'});
});
not refresh
window.history.pushState(“object or string”, “Title”, “/new-url”);
I'm trying to get a simple Google Chrome extension working where a message/variable flows through each of the following steps ...
DOM content (from specific HTML tag)
Contentscript.js
Background.js
Popup.js
Popup.html
I've figured out how to send a message/variable to Background.js and from it in one direction (Background.js -> Popup.js or Background.js -> Contentscript.js), but can't get it through all three successfully (Contentscript.js -> Background.js -> Popup.js). Here are the files in my demo.
Dom
<h1 class="name">Joe Blow</h1>
Content.js
fromDOM = $('h1.name').text();
chrome.runtime.sendMessage({contentscript: "from: contentscript.js", title: fromDOM}, function(b) {
console.log('on: contentscript.js === ' + b.background);
});
Background.js
chrome.tabs.getSelected(null, function(tab) {
chrome.extension.onMessage.addListener(function(msg, sender, sendResponse) {
sendResponse({background: "from: background.js"});
console.log('on: background.js === ' + msg.title);
});
});
Popup.js
chrome.extension.sendMessage({pop: "from: popup.js"}, function(b){
console.log('on: popup.js === ' + b.background);
$('.output').text(b.background);
});
Popup.html
<html>
<head>
<script src="jquery.js"></script>
<script src="popup.js"></script>
</head>
<body>
<p class="output"></p>
</body>
</html>
Manifest.json
{
"name": "Hello World",
"version": "1.0",
"manifest_version": 2,
"description": "My first Chrome extension.",
"background" : {
"scripts": ["background.js"]
},
"permissions": [
"tabs"
],
"browser_action": {
"default_icon": "icon.png",
"default_popup": "popup.html"
},
"content_scripts": [
{
"matches": ["http://*/*"],
"js": ["jquery.js","contentscript.js"],
"run_at": "document_end"
}
]
}
I have a feeling I know what the trip-up is, but the documentation is severely lacking for manifest_version: 2 that its tough to decipher. A simple, reusable example would be very helpful in the learning process, as I'm sure this is a common issue.
Alright, changing a few things in your code should make it work like you want. Not all of the changes I am going to make are necessary, but this is just how I might do it.
Content Script
var fromDOM = $('h1.name').text();
chrome.runtime.sendMessage({method:'setTitle',title:fromDOM});
Background
var title;
chrome.runtime.onMessage.addListener(function(message,sender,sendResponse){
if(message.method == 'setTitle')
title = message.title;
else if(message.method == 'getTitle')
sendResponse(title);
});
Popup.js
chrome.runtime.sendMessage({method:'getTitle'}, function(response){
$('.output').text(response);
});