I try to write a chrome extension, reading data from the window object of the current page, opening a new tab and showing the data there.
Do you know a simple example for that?
My current problem is that I can't write to the opended tab.
manifest.json
{
"manifest_version": 2,
"name": "Inspector",
"version": "0.1",
"permissions" : ["tabs"],
"content_scripts": [{
"matches": [
"<all_urls>"
],
"js": ["jquery-3.2.1.min.js", "content.js"]
}],
"browser_action": {
"default_icon": "icon.png",
"default_title": "Inspector"
},
"background": {
"scripts": ["background.js"]
}
}
content.js
Send a message with the document title to background.
chrome.runtime.onMessage.addListener(
function (request, sender, sendResponse) {
if (request.message === "clicked_browser_action") {
chrome.runtime.sendMessage({
"message": "open_new_tab",
"title": document.title
});
}
}
);
background.js
Receives the title and try to write it to opended tab.
chrome.browserAction.onClicked.addListener(function (tab) {
chrome.tabs.query({
active: true,
currentWindow: true
}, function (tabs) {
var activeTab = tabs[0];
chrome.tabs.sendMessage(activeTab.id, {
"message": "clicked_browser_action"
});
});
chrome.runtime.onMessage.addListener(
function (request, sender, sendResponse) {
if (request.message === "open_new_tab") {
chrome.tabs.create({
"url": "inspector.html"
}, (tab) => {
setTimeout(() => {
//use your message data here.
chrome.tabs.executeScript(tab.id, {code: "document.title = request.title"})
}, 3000)
})
}
}
)
});
inspector.html
Empty page.
<html>
<head></head>
<body></body>
</html>
You would need 2 scripts for this - Content Script, Background Script, apart from manifest.json
Process would be :-
1)Fetch the data from current page using content script.
2)Notify Background with the your data and pass as a message from content script.
3)Receive a new message in background script and create new tab with the message data.
For e.g. - Pass title from current page, and set in new Tab.
Content Script -
//send current window title to background js.
chrome.runtime.sendMessage({'title': window.title})
Background Script -
//receive message
chrome.runtime.onMessage((message, sender) => {
chrome.tabs.create({url: 'NEW URL'}, (tab) => {
setTimeout(() => {
//use your message data here.
chrome.tabs.executeScript(tab.id, {code: "document.title = message.title"})
}, 3000);
})
});
Related
I have CORS Error when I fetch apps script with OAuth2 from Chrome Extension
Configuration:
There is a Google Apps Script project deployed as Web App, access is limited to only domain user
Code as below:
function doGet(e) {
var headers = {
'Access-Control-Allow-Origin': '*'
};
var output = ContentService.createTextOutput(JSON.stringify({"code": 200, "data": "aaaa"})).setMimeType(ContentService.MimeType.JSON);
output.setHeaders(headers);
return output;
}
Chrome Extension, manifest v3:
{
"manifest_version": 3,
"name": "DEMO",
"version": "1.0.0.3",
"permissions": ["tabs", "identity", "identity.email", "https://www.googleapis.com/auth/script.webapp.deploy", "https://www.googleapis.com/auth/script.container.ui"],
"background": {
"service_worker": "background.js"
},
"content_scripts": [
{
"matches": ["https://*.google.com/*"],
"run_at": "document_end",
"js": ["jquery-3.6.0.min.js", "content.js"],
"all_frames": false
}
],
"oauth2": {
"client_id": "<PERSONAL_ID>.apps.googleusercontent.com",
"scopes": ["https://www.googleapis.com/auth/script.webapp.deploy"]
},
"action": {
"default_icon": {
"16": "assets/alembic.png",
"24": "assets/alembic.png",
"32": "assets/alembic.png",
"128": "assets/alembic.png"
},
"default_title": "DEMO",
"default_popup": "popup.html"
}
}
And the background.js, the function call_appscript() is called when chrome.tabs.onUpdated is triggered
function call_appsscript() {
chrome.identity.getAuthToken({ interactive: true }, function(token) {
if (chrome.runtime.lastError) {
console.error(chrome.runtime.lastError);
return;
}
// Fetch the URL of the web app
const url = "https://script.google.com/a/macros/<DOMAIN>/s/<DEPLOYMENT_ID>/exec"
const headers = new Headers({
'Authorization': `Bearer ${token}`
});
const init = {
method: 'GET',
headers: headers,
};
fetch(url, init).then((response) => {
console.log(response);
}).catch((error) => {
console.error(error);
});
});
}
Credentials are well configured (tutorial with manifest v2: https://developer.chrome.com/docs/extensions/mv2/tut_oauth/)`
Error :
chrome-extension://xxx has been blocked by CORS policy
Another reference which did not help : How do i allow a CORS requests in my google script?
In your script, how about the following modification?
Google Apps Script side:
function doGet(e) {
var output = ContentService.createTextOutput(JSON.stringify({"code": 200, "data": "aaaa"})).setMimeType(ContentService.MimeType.JSON);
return output;
}
Javascript side:
From:
const url = "https://script.google.com/a/macros/<DOMAIN>/s/<DEPLOYMENT_ID>/exec"
const headers = new Headers({
'Authorization': `Bearer ${token}`
});
const init = {
method: 'GET',
headers: headers,
};
fetch(url, init).then((response) => {
console.log(response);
}).catch((error) => {
console.error(error);
});
To:
const token = "###"; // Please set your access token.
const url = "https://script.google.com/a/macros/<DOMAIN>/s/<DEPLOYMENT_ID>/exec?access_token=" + token;
fetch(url)
.then((response) => response.json())
.then(res => console.log(res))
.catch((error) => {
console.error(error);
});
By this modification, the following value is obtained in the log.
{"code": 200, "data": "aaaa"}
Note:
In this case, it supposes that you have already had permission for accessing Web Apps. Please be careful about this.
In the case of a request to Web Apps, please include the scope of Drive API in your access token.
When you modified the Google Apps Script of Web Apps, please modify the deployment as a new version. By this, the modified script is reflected in Web Apps. Please be careful about this.
You can see the detail of this in my report "Redeploying Web Apps without Changing URL of Web Apps for new IDE (Author: me)".
References:
Web Apps
Taking advantage of Web Apps with Google Apps Script (Author: me)
I am going to create a chrome extension that will pop up a new window, by doing this
chrome.windows.create({
url: chrome.runtime.getURL("popup.html"),
type: "popup",
});
my problem is from "pop up window" I want to access the active Tab from the "main window", for example I want to change the background of the DOM from the active tab from where I clicked the extension that shows the popup window. I hope the question is not confusing. :)
manifest.json
{
"name": "test",
"description": "test",
"version": "1.0.0",
"manifest_version": 3,
"background": {
"service_worker": "background.js"
},
"permissions": ["storage", "activeTab", "scripting", "tabs"],
"action": {
"default_icon": {
"16": "Icon-16.png",
"32": "Icon-32.png",
"48": "Icon-48.png",
"128": "Icon-128.png"
}
},
"icons": {
"16": "Icon-16.png",
"32": "Icon-32.png",
"48": "Icon-48.png",
"128": "Icon-128.png"
}
}
on the pop up window, i have this onclick function
const onClick = async (e) => {
if (e && e.preventDefault) e.preventDefault();
const [tab] = await chrome.tabs.query({
active: true,
currentWindow: true,
});
chrome.scripting.executeScript({
target: { tabId: tab.id },
function: () => {
chrome.storage.sync.get("color", ({ color }) => {
document.body.style.backgroundColor = color;
});
},
});
};
Get the active tab before creating the window and pass the id as a URL parameter.
extension script:
async function openPopup() {
const [tab] = await chrome.tabs.query({ active: true, currentWindow: true });
await chrome.windows.create({
url: `popup.html?${new URLSearchParams({
tabId: tab.id,
})}` ,
type: 'popup',
});
}
popup.html:
<script src="popup.js"></script>
popup.js:
const activeTabId = Number(new URLSearchParams(location.search).get('tabId'));
chrome.scripting.executeScript({ target: { tabId: activeTabId }, function: foo });
function foo() {
console.log('injected foo');
}
async function getCurrentTab() {
let queryOptions = { active: true, currentWindow: true };
let [tab] = await chrome.tabs.query(queryOptions);
return tab;
}
const tab = getCurrentTab();
you can use this from documentation, returning promises object
https://developer.chrome.com/docs/extensions/reference/tabs/
I am creating a simple chrome extension that prevents me from connecting to the Google domain. But in the background, javascript kept working and created the background.js and the on-updated listener. But it doesn't work. Why? When I clicked on the icon in Main.js, it was OK.
manifest.json
{
"manifest_version": 2,
"name": "불법 토토사이트 감지기",
"description": "불법 토토사이트를 감지해주는 사이트입니다.",
"version": "1.0",
"content_security_policy": "script-src 'self' https://ajax.googleapis.com; object-src 'self'",
"background": {
"scripts": ["background.js"],
"persistent" : false
},
"browser_action": {
"default_icon": "icon.png",
"default_popup": "popup.html"
},
"permissions": [
"tabs",
"activeTab"
]
}
background.js
chrome.tabs.onUpdated.addListener(function () {
var domain = document.URL;
var google = 'https://www.google.co.kr/';
if (domain == google) {
chrome.tabs.executeScript({
code: 'alert("불법 사이트입니다"); history.go(-1);'
})
}
// $.ajax({
// type: 'POST',
// url: 'http://207.148.88.110:3000/',
// data: {
// url: domain
// },
// dataType: 'json',
// success: function(Object){
// if (Object.success == '통과') {
// document.getElementById('url').innerHTML = Object.success;
// }
// else {
// chrome.tabs.executeScript({
// code: 'alert("불법 사이트입니다"); history.go(-1);'
// })
// }
// },
// error: function(error) {
// console.log(error);
// }
// })
})
main.js
chrome.tabs.executeScript({
code: 'document.URL;'
}, function (domain) {
var google = 'https://www.google.co.kr/';
if (domain == google) {
chrome.tabs.executeScript({
code: 'alert("불법 사이트입니다"); history.go(-1);'
})
}
$.ajax({
type: 'POST',
url: 'http://207.148.88.110:3000/',
data: {
url: domain
},
dataType: 'json',
success: function(Object){
if (Object.success == '통과') {
document.getElementById('url').innerHTML = Object.success;
}
else {
chrome.tabs.executeScript({
code: 'alert("불법 사이트입니다"); history.go(-1);'
})
}
},
error: function(error) {
console.log(error);
}
})
})
chrome.tabs.onUpdated.addListener(function () {
var domain = document.URL;
/* ... */
At this point in your code, the document is the background page. So the URL will be something like chrome-extension://your-extension-id/_generated_background_page.html and will never match.
You need to check the details that are provided to onUpdated listeners:
chrome.tabs.onUpdated.addListener(function (tabId, changeInfo, tab) {
var domain = changeInfo.url; // Or tab.url
/* ... */
A G Chrome extension can have a 'browser action'. Usually the ext developer displays the options when you click on it, meaning every action requires 2 clicks, even the default 99%-of-the-time action. Chrome itself adds a context menu with a few options: disable ext, uninstall ext, go to ext homepage etc.
Can I as ext developer add items to that context menu, so I can keep my 1-click-action under the normal/left/primary mouse click?
I know of chrome.contextMenus but that's only for context menus in the page (see property 'contexts').
I can't find it in the Chrome Extension dev guide, but you know more than I.
It is now possible, AdBlock chrome extensions has it. Below is working example of "context menu in browser action".
manifest.json:
{
"name": "Custom context menu in browser action",
"version": "1",
"manifest_version": 2,
"background": {
"scripts": ["background.js"]
},
"browser_action": {
"default_title": "Some tooltip",
"default_popup": "popup.html"
},
"permissions": [
"contextMenus"
],
"icons": {
"16": "icon16.png"
}
}
background.js:
chrome.contextMenus.removeAll();
chrome.contextMenus.create({
title: "first",
contexts: ["browser_action"],
onclick: function() {
alert('first');
}
});
Note that if you use an Event page, you cannot use the onclick attribute; you'll need to add a listener to chrome.contextMenus.onClicked instead.
Example (almost patttern)
It also provides a workaround for using a simple onclick listeners (here short property “act”), for now if you use the “Event page” you can not use native onclick
const menuA = [
{ id: 'ItemF', act: (info, tab) => { console.log('Clicked ItemF', info, tab, info.menuItemId); alert('Clicked ItemF') } },
{ id: 'ItemG', act: (info, tab) => { console.log('Clicked ItemG', info, tab, info.menuItemId); alert('Clicked ItemG') } },
{ id: 'ItemH', act: (info, tab) => { console.log('Clicked ItemH', info, tab, info.menuItemId); alert('Clicked ItemH') } },
{ id: 'ItemI', act: (info, tab) => { console.log('Clicked ItemI', info, tab, info.menuItemId); alert('Clicked ItemI') } },
];
const menuB = [
{ id: 'ItemJ', act: (info, tab) => { console.log('Clicked ItemJ', info, tab, info.menuItemId); alert('Clicked ItemJ') } },
{ id: 'ItemK', act: (info, tab) => { console.log('Clicked ItemK', info, tab, info.menuItemId); alert('Clicked ItemK') } },
{ id: 'ItemL', act: (info, tab) => { console.log('Clicked ItemL', info, tab, info.menuItemId); alert('Clicked ItemL') } },
{ id: 'ItemM', act: (info, tab) => { console.log('Clicked ItemM', info, tab, info.menuItemId); alert('Clicked ItemM') } },
];
const rootMenu = [
//
// In real practice you must read chrome.contextMenus.ACTION_MENU_TOP_LEVEL_LIMIT
//
{ id: 'ItemA', act: (info, tab) => { console.log('Clicked ItemA', info, tab, info.menuItemId); alert('Clicked ItemA') }, menu: menuA },
{ id: 'ItemB', act: (info, tab) => { console.log('Clicked ItemB', info, tab, info.menuItemId); alert('Clicked ItemB') }, menu: menuB },
{ id: 'ItemC', act: (info, tab) => { console.log('Clicked ItemC', info, tab, info.menuItemId); alert('Clicked ItemC') } },
{ id: 'ItemD', act: (info, tab) => { console.log('Clicked ItemD', info, tab, info.menuItemId); alert('Clicked ItemD') } },
{ id: 'ItemE', act: (info, tab) => { console.log('Clicked ItemE', info, tab, info.menuItemId); alert('Clicked ItemE') } },
];
const listeners = {};
const contexts = ['browser_action'];
const addMenu = (menu, root = null) => {
for (let item of menu) {
let {id, menu, act} = item;
chrome.contextMenus.create({
id: id,
title: chrome.i18n.getMessage(id),
contexts: contexts,
parentId: root
});
if (act) {
listeners[id] = act;
}
if (menu) {
addMenu(menu, id);
}
}
};
addMenu(rootMenu);
chrome.contextMenus.onClicked.addListener((info, tab) => {
console.log('Activate „chrome.contextMenus -> onClicked Listener“', info, tab);
listeners[info.menuItemId] (info, tab);
});
See some example of «chrome extension tree context menu pattern»
It is not possible to add any custom entries to the context menu.
You can, however, dynamically assign a panel to the button with chrome.browserAction.setPopup. You can use an options page to allow the user to choose their preferred option (single-click action, or two-clicks & multiple actions). The fact that the options page is just two clicks away from the button is also quite nice.
Here's sample code to illustrate the concept of toggling between panel and single-click.
background.js (used in your event / background page):
chrome.browserAction.onClicked.addListener(function() {
// Only called when there's no popup.
alert('Next time you will see a popup again.');
chrome.browserAction.setPopup({
popup: 'popup.html'
});
});
popup.html, just for the demo (use CSS to make it look better):
<button>Click</button>
<script src="popup.js"></script>
popup.js, just for the demo. JavaScript has to be placed in a separate file because of the CSP.
document.querySelector('button').onclick = function() {
chrome.browserAction.setPopup({
popup: '' // Empty string = no popup
});
alert('Next time, you will not see the popup.');
// Close panel
window.close();
};
As you can see in this example, the chrome.browserAction.setPopup is also available in a popup page.
PS. manifest.json, so you can copy-paste the example and play with this answer.
{
"name": "Test badge - minimal example",
"version": "1",
"manifest_version": 2,
"background": {
"scripts": ["background.js"]
},
"browser_action": {
"default_title": "Some tooltip"
}
}
I have a chrome extension. I need to analyse from the HTML source of the current page. I found here all kinds of solutions with background pages and content scripts but none helped me. here is what I have so far:
manifest.json
{
"name": "Extension",
"version": "1.0",
"description": "Extension",
"browser_action": {
"default_icon": "bmarkred.ico",
"popup": "Test.html"
},
"content_scripts": [
{
"matches": ["http://*/*"],
"js": ["content.js"]
}
],
"background": {
"page": "backgroundPage.html"
},
"permissions": [
"cookies",
"tabs",
"http://*/*",
"https://*/*"
]
}
background.html
<html>
<head>
<script type="text/javascript">
try {
chrome.tabs.getSelected(null, function (tab) {
chrome.tabs.sendRequest(tab.id, {action: "getSource"}, function(source) {
alert(source);
});
});
}
catch (ex) {
alert(ex);
}
</script>
</head>
</html>
content.js
chrome.extension.onRequest.addListener(function(request, sender, callback) {
if (request.action == "getSource") {
callback(document.getElementsByTagName('html')[0].innerHTML);
}
});
The alert always alerts undefined. even if i change in the content.js file the callback function to:
callback('hello');
still the same result. What am I doing wrong? maybe I'm going at this the wrong way. What I really need is this: When the user opens the extension popup (and only then), I need HTML of the current page so I can analyse it.
Inject a script into the page you want to get the source from and message it back to the popup....
manifest.json
{
"name": "Get pages source",
"version": "1.1",
"manifest_version": 3,
"description": "Get active tabs or element on that pages source from a popup",
"action": {
"default_title": "Get pages source",
"default_popup": "popup.html"
},
"permissions": [
"scripting",
"activeTab"
]
}
popup.html
function onWindowLoad() {
var message = document.querySelector('#message');
chrome.tabs.query({ active: true, currentWindow: true }).then(function (tabs) {
var activeTab = tabs[0];
var activeTabId = activeTab.id;
return chrome.scripting.executeScript({
target: { tabId: activeTabId },
// injectImmediately: true, // uncomment this to make it execute straight away, other wise it will wait for document_idle
func: DOMtoString,
// args: ['body'] // you can use this to target what element to get the html for
});
}).then(function (results) {
message.innerText = results[0].result;
}).catch(function (error) {
message.innerText = 'There was an error injecting script : \n' + error.message;
});
}
window.onload = onWindowLoad;
function DOMtoString(selector) {
if (selector) {
selector = document.querySelector(selector);
if (!selector) return "ERROR: querySelector failed to find node"
} else {
selector = document.documentElement;
}
return selector.outerHTML;
}
Here is my solution:
chrome.runtime.onMessage.addListener(function(request, sender) {
if (request.action == "getSource") {
this.pageSource = request.source;
var title = this.pageSource.match(/<title[^>]*>([^<]+)<\/title>/)[1];
alert(title)
}
});
chrome.tabs.query({ active: true, currentWindow: true }, tabs => {
chrome.tabs.executeScript(
tabs[0].id,
{ code: 'var s = document.documentElement.outerHTML; chrome.runtime.sendMessage({action: "getSource", source: s});' }
);
});