Chrome extension - Collect elements from active tab - google-chrome

I am learning to create a chrome extension. To start, I am trying to just gather all links on the page and display them in the popup window of the extension when the button is clicked. I can't seem to get it right. I am able to use messages and send a message from my chrome tab to the extension. But when I try to pass the array of a tags, then it breaks.
My content script:
window.addEventListener('DOMContentLoaded', function () {
chrome.tabs.executeScript(null, {file: "content.js"});
});
My extension script:
window.addEventListener('message', function(e) {
if (event.source != window)
return;
if (event.data.type && (event.data.type == "FROM_PAGE")) {
console.log("Content script received: " + event.data.text);
console.log(event.data.links);
}
}, false);
if I dont do the links, it works fine and sends the messages. So I can't find another way to send all the links to the extension so I can process them. I have this in a github repo here https://github.com/skiftio/chrome-linkman

Your understanding of the architecture is a bit off. Please read through the Overview page, especially the Architecture part.
I'll even include a helpful picture here:
The popup is counted as "other pages" on this picture. It's an HTML page on its own domain (chrome-extension://yourextensionidhere), which is created when you open the popup and destroyed when you close it.
A content script is a script attached to a real Chrome tab; it gets access to its DOM, but is isolated from the page's own scripts. It also has very limited access to Chrome APIs.
More importantly, there are 2 ways of telling Chrome to add a content script to the page: you can declare it in the manifest so that Chrome automatically injects it upon navigation, or you can programmatically inject it from somewhere in your extension pages. You are mixing up those two.
Your manifest entry refers to scripts.js which is NOT a content script and you should not call it such. For instance, chrome.tabs.executeScript is not allowed to be called from a content script, and it will just throw an error. Since you're also injecting the script from the popup, you should just remove the section from the manifest, you don't need it.
As for messaging, you're trying to use window.postMessage, but this is not standard in Chrome extensions.
Take a look at the full Messaging docs, and I recently gave a short overview here.
In your situation, you could add a listener to chrome.runtime.onMessage to the popup and send a message with chrome.runtime.sendMessage from the content script:
/* --- Popup code (scripts.js) --- */
chrome.runtime.onMessage.addListener(function(message, sender, sendResponse){
if(message.links) {
/* do work */
}
});
chrome.tabs.executeScript({file: "content.js"});
/* --- Content script code (content.js) --- */
var links = document.getElementsByTagName("a");
chrome.runtime.sendMessage({links: links});

Related

JS Userscript: accessing DOM of new tab

I'm writing a Chrome userscript to open a new tab, then click on some DOM elements. But how can I access the DOM of the new tab?
/* background script */
chrome.browserAction.onClicked.addListener(() => {
chrome.tabs.create({"url": MY_URL, (tab) => {
// What ought I to do?
});
});
You will need to have at least one content script file loaded in your tab (with the right permissions to allow the content script to be loaded).
In the content script, you will have to leverage the Messaging API to communicate with the background page.
If your logic is simple enough, you could even run all logic directly inside content script, as they have access to be same set of chrome API as the background page.

Content script loads automatically

I am developing a chrome extension right now. It serves its purpose but the problem is that the content script gets executed on its own whenever I open a new tab and visit a site. The problem is that there is an alert in the content script & it starts to become annoying when the alert gets triggered on visiting every new site.
Currently I am triggering the content script from my background script by using the chrome.tabs.executeScript API. However, I have noticed that whenever I open a new tab & visit a new site, the code in content script gets executed automatically even if the criteria for it getting triggered hasn't been met in the background script.
Is this the normal behavior? Is this how all chrome extensions are supposed to work? If yes, is there a way to prevent this? For now, I have declared my content script as a background script, but I would still like to find a fix for this.
Here is my manifest - manifest.json:
"content_scripts" : [
{
"matches" : [ "<all_urls>"],
"js": ["raisealert.js"]
}
],
"background": {
"scripts": ["background.js"],
"persistent": false
},
Here is the background script - background.js:
if(condition met)
chrome.tabs.executeScript(null, {
code: 'var config = ' + JSON.stringify(config)
}, function() {
chrome.tabs.executeScript(null, {file: 'raisealert.js'}, function() {
console.log("Alarm triggered");
});
});
And here is the content script - raisealert.js
alert("sample alert raised");
You can have a content_script load automatically and with no programming effort on your part by calling it up in the content_scripts section of the manifest.
You can do this for all URLs or you can define fairly fine-grained url patterns that the script should load on. See Manifest injection
If you want to inject the script conditionally on criteria other than just the URL then you can use programmatic injection - this could be based on criteria such as the content of the page or whether someone clicks the browser action button or selects a right click context menu option you've defined.
Programmatic injection will often use the chrome.tabs.onupdated listener to check for particular urls or content and then inject or not.
If your extension is something that potentially anyone might want to use on any page then it's usually best to inject it based on a deliberate action by the user, e.g. in your case, as you say, it is annoying (understatement) to get an alert on every tab and page that loads.
Even if your script has no visible annoyances, having it load in every page automatically is rarely a good idea unless it has near zero impact on the page.
If it's designed to work on a specific site then injecting via the manifest with an appropriate url pattern filter is often best.

Background scripts vs Content Scripts

I am trying to develop a chrome extension which saves the url of webpages opened in all tabs and then load them whenever needed. Now I know content scripts, background scripts and popup.js. Content scripts mainly deal with the content of the loaded webpage and they have less chrome api interactions, background scripts are executed in an isolated environment and we can use all chrome api methods, popup.js is simply javascript that runs in context of popup.html.
Now here is my problem, I have a button in popup.html named "save" and on click of that button I want to save all the webpage urls opened in multiple tabs under one window. How can I do that?
Should I write a content or a background script?
Sorry for my noobish question. I am new to chrome api. Any help/suggestions?
Neither content script or background page is needed. You could do that just in popup.js, since popup page actually runs in the same context with extension.
In your popup.js, just call chrome.tabs.query to get tab info, including url (you would need to declare tabs permissions in manifest.json). If you want to specify window id, either use WINDOW_ID_CURRENT or retrieve it through other ways (depends on your logic)
chrome.tabs.query({ windowId: YOUR_WINDOW_ID }, (tabs) => {
tabs.forEach((tab) => console.log(tab.url));
});

How can I open my extension's pop-up with JavaScript?

I am trying to write a JavaScript function that will open my extension like when the extension icon is clicked. I know how to open my extension in a new tab:
var url = "chrome-extension://kelodmiboakdjlbcdfoceeiafckgojel/login.html";
window.open(url);
But I want to open a pop-up in the upper right corner of the browser, like when the extension icon is clicked.
The Chromium dev team has explicitly said they will not enable this functionality. See Feature request: open extension popup bubble programmatically :
The philosophy for browser and page action popups is that they must be triggered by user action. Our suggestion is to use the new html notifications feature...
Desktop notifications can be used progammatically to present the user with a small HTML page much like your popup. It's not a perfect substitution, but it might provide the type of functionality you need.
Chrome team did create a method to open the popup programmatically, but it's only enabled as a private API, and plans to make it generally available have stalled due to security concerns.
So, as of March 2018 as of now, you still can't do it.
Short answer is that you cannot open browserAction programmatically. But you can create a dialog with your content script which emulates your browserAction and display that isntead (programmatically). However you won't be able to access your extension's background page from this popup directly as you can from your popup.html. You will have to pass message instead to your extension.
As mentioned there is no public API for this.
One workaround I have come up with is launching the extension as an iframe inside a content script with a button click. Whereby the background script emits the extension URL to the content script to be set as the iframe's src, something like below.
background.js
browser.runtime.onMessage.addListener((request) => {
if (request.open) {
return new Promise(resolve => {
chrome.browserAction.getPopup({}, (popup) => {
return resolve(popup)
})
})
}
})
content-scipt.js
const i = document.createElement('iframe')
const b = document.createElement('button')
const p = document.getElementById('some-id')
b.innerHTML = 'Open'
b.addEventListener('click', (evt) => {
evt.preventDefault()
chrome.runtime.sendMessage({ open: true }, (response) => {
i.src = response
p.appendChild(i)
})
})
p.appendChild(b)
This opens the extension in the DOM of the page the script is running on. You will also need to add the below to the manifest.
manifest.json
....
"web_accessible_resources": [
"popup.html"
]
....
You could emulate the popup by displaying a fixed html element on the page in the same location the popup would be and style it to look like the popup.
I had the same requirement: When the user clicks on the extension icon a small popup should open. In my case, I was writing an extension which will give updates on selective stocks whenever the icon is clicked. This is how my popup looked.
If you were having the same requirement then please read the answer below.
This is how my manifest.json file looked.
All the heavy lifting was handled by manifest.json file only. There is a section browser_action inside which there is a key called default_popup, just put the name of the HTML file that you want the popup to display.
I wanted my extension to work on all the pages that's why I added the attribute matches under content_scripts. I really didn't need to put the jquery file jquery-3.2.1.js inside the js array but the extension manager was not allowing me to keep that array empty.
Hope this helps, do comment if you have any doubt regarding the answer.

Message Passing in Chrome

Have a small doubt in how message passing works in chrome using content scrips. I modified the default example (http://src.chromium.org/viewvc/chrome/trunk/src/chrome/common/extensions/docs/examples/api/messaging/timer/) for message passing given in the chromium documentation to the one that looks below :
popup.html
function testRequest() {
chrome.tabs.getSelected(null, function(tab) {
chrome.tabs.sendRequest(tab.id, {counter: "getHTML"}, function handler(response) {
alert("Inside Client = "+response.counter2);
});
});
}
and my content script looks like this :
page.js
chrome.extension.onRequest.addListener(
function(request, sender, sendResponse) {
alert(request.counter);
alert("Inside server .. Req Counter = "+request.counter);
sendResponse({counter2: "5"});
});
When I execute the testRequest from popup.html, the content script is getting called as expected. I do get both the alerts i have
declared with their respective values. But my popup.html response code doesnt seem to be called .. The alert I have inside the popup.html - alert("Inside Client = "+response.counter2); is not being executed.
On the other hand, If i have a debug point inside the client, its working ! Kinda strange.. Can somebody tell me how and why this is happening ?
Thank you in advance..
your code is correct. I am mistaken what I said before.
Believe me when I say it, I was puzzled why it didn't work. It turned out to be that I am running the browser action on the chrome://extensions/ page. In Chrome Extensions, the API will not let you execute or send any requests to that page. Do it on a normal page like Google.com and you will see your popup.
You cannot show an alert dialog within popup page.
That is why you don't see: alert("Inside Client = "+response.counter2); }
If you want to see it working, you can add a console logger and view it within the Web Inspector. Replace the alert with: console.log(response.counter2);
As far as I can tell, alerts from a popup will only appear if the popup is open.
You see the alert when you're debugging the popup because the debugger keeps the popup open.
I'm pretty sure there are also no problems with creating alerts from the background page.