chrome extension content script not working as expected [duplicate] - javascript

Let's say I have an extension that loads when you arrive at a YouTube video page.I have noticed that when one navigates back and forth using the Chrome buttons, the extension most probably won't load.
As an example, I have 2 files, the manifest:
{
"name": "back forth",
"version": "0.1",
"manifest_version": 2,
"description": "back forth",
"permissions": ["storage", "*://www.youtube.com/watch*"],
"content_scripts": [
{
"matches": ["*://www.youtube.com/watch*"],
"js": ["contentscript.js"]
}
]
}
and the contentscript
alert("loaded");
The alert does not always show up when navigating back and forth. How can I overcome this, so that the extension loads every time?

YouTube has started a trial with pushState-based navigation. In general, such navigations can only be detected within content scripts by injecting code that intercept calls to history.replaceState / history.pushState (or by using the chrome.webNavigation.onHistoryStateUpdated event in the background page).
The remainder of this answer is specific to YouTube.
YouTube shows a (red) progress bar on top of the page during load. This progress bar is animated using a CSS transition. Since transition events bubble, you can bind a transition event listener to <body>, and check navigation in these cases.
You have to insert your content script at *://www.youtube.com/* instead of *://www.youtube.com/watch*, because pushState can be used to navigate from / to /watch...
function afterNavigate() {
if ('/watch' === location.pathname) {
alert('Watch page!');
}
}
(document.body || document.documentElement).addEventListener('transitionend',
function(/*TransitionEvent*/ event) {
if (event.propertyName === 'width' && event.target.id === 'progress') {
afterNavigate();
}
}, true);
// After page load
afterNavigate();
Note: This method depends on the fact that the progress bar is inserted. Whenever Google decides to rename the ID of the progress bar, or remove the progress bar altogether, your code will cease to work.
Note 2: This only works for active tabs. If you need to detect navigation changes while the tab is not focused, then you need to bind a window.onfocus and window.onblur event, and check whether document.title has changed between these events.

Related

Clicks from Chrome Extension script to (same-origin) dynamically added iframes have no effect

I’m developing a Chrome Extension for a specific website. The script is injected after page load, the script clicks a button, and an iframe popup is loaded.
inject.js
chrome.extension.sendMessage({}, function(response) {
var readyStateCheckInterval = setInterval(function() {
if (document.readyState === "complete") {
clearInterval(readyStateCheckInterval);
document.getElementById(‘aButton’).click();
NOTE: Unlike many similar questions on SO, the resulting iframe is same-origin. It is NOT from an external site, and there are no warnings in the inspector. (Also confirmed in Sources tab)
The script waits 3 seconds for the popup to fully load, accesses the iframe’s document object, then clicks on the target – AND nothing happens.
setTimeout(() => {
let doc = document.getElementById('iframeOne');
let innerDoc = doc.contentDocument || doc.contentWindow.document;
let innerDocElem = innerDoc.getElementById('innerDocElem');
innerDocElem.click();
}, 3000);
What I’ve Tried:
I’ve set my file’s content scripts to include all frames, and since the iframe is of same origin, the wildcard in matches should give the script access to the iframe.
manifest.json
"content_scripts": [{
"matches": "https://awebsite.com/*" ,
"js": "src/inject/inject.js" ,
"allframes": true
}]
I’ve tried using css transforms to move the element to top-left, and then using x,y coordinates to click on the iframe’s buttons
document.elementFromPoint(250, 204).click();
I’ve also tried replacing the click() method with a simulated mouse event as suggested in these answers:
How to simulate a mouse click using JavaScript?
JavaScript click() method only works once in Chrome extension

Chrome extension - page action: defining pages

I'm trying to build a somehow dummy Chrome extension. I want it to run only in specific pages, so I'm using a Page Action.
Let's say I want the page action to run on the Instagram website, then (accordingly the docs), I would need something like this on my manifest.json right?
{
"manifest_version": 2,
"name": "Some name",
"version": "0.0.3",
"description": "Some description",
"content_scripts": [
{
"matches": [
"https://www.instagram.com/*"
],
"js": ["content.js"]
}
],
"page_action": {
"default_icon": "icon.png"
},
"background": {
"scripts": ["background.js"]
}
}
while the content script runs only on instagram pages as one would expect, the browser extension is not clickable (gray look, and when I click most options are not clickable).
this makes impossible to act upon extension button click. In my background.js I have:
function click(tab) {
console.log('click from ' + tab);
}
chrome.pageAction.onClicked.addListener(click);
that never gets called.
So, what's wrong that makes impossible to act upon extension click on some pages?
Note: I saw this question/answer, but couldn't find the problem/solution How can I add a click for pageAction?
You have to call pageAction.show in order for your pageAction button to be enabled (clickable).
The pageAction documentation says (emphasis mine):
You make a page action appear and be grayed out using the pageAction.show and pageAction.hide methods, respectively. By default, a page action appears grayed out. When you show it, you specify the tab in which the icon should appear. The icon remains visible until the tab is closed or starts displaying a different URL (because the user clicks a link, for example).
With a manifest.json content_scripts entry
Because you already have a content script that runs on the page you desire to have this function on, probably the easiest way to do this is to have your content script send a message to your background script telling it to show the page-action button for that tab.
Your content script could look something like:
chrome.runtime.sendMessage({type: showPageAction});
Your background script could look something like:
chrome.runtime.onMessage(function(message, sender, sendResponse) {
if(typeof message === 'object' && message.type === 'showPageAction') {
chrome.pageAction.show(sender.tab.id);
}
});
Without a manifest.json content_scripts entry
If you did not have a content script, you would probably want to use a webNavigation.onCompleted listener, or tabs.onUpdated listener, to listen for a change in the tab's URL in order to determine that the page-action button should be shown. Obviously, the trigger for calling pageAction.show() does not have to be the URL which is currently displayed in the tab, but that is the most common.

From a browser action popup: open a new tab and fill input fields

I'm trying to build a basic Chrome extension that, from a browser action popup, opens a website in a new tab, and fills in the login credentials. I can get the Chrome extension to open the new page but can't seem to get it to input text into the input fields.
Manifest.json
{
"manifest_version": 2,
"name": "Vena",
"description": "This extension will allow users to login to vena accounts",
"version": "1.0",
"browser_action": {
"default_icon": "images/icon.png",
"default_popup": "popup.html"
},
"permissions": [
"activeTab"
]
}
popup.html
<!doctype html>
<html>
<head>
<title>Auto-Login</title>
<script src="popup.js"></script>
</head>
<body>
<h1>Login</h1>
<button id="checkPage">Login!</button>
</body>
</html>
popup.js
document.addEventListener('DOMContentLoaded', function() {
var checkPageButton = document.getElementById('checkPage');
checkPageButton.addEventListener('click', function() {
var newURL = "https://vena.io/";
chrome.tabs.create({ url: newURL });
var loginField = document.getElementsByClassName('js-email form-control input-lg');
var passwordField = document.getElementsByClassName('js-password form-control input-lg');
loginField.value = 'gsand';
passwordField.value = '123';
}, false);
}, false);
How do I fill in the information in the input areas of the new tab?
Another time, you may want to use something other than a popup (e.g. just a plain browser action button, or a panel) to test out your functionality. It is easier to debug things other than a popup due to the fact that the popup will disappear under so many conditions. Once you have the basic functionality debugged, you can move it into a popup and deal with the issues specific to using a popup.
Issues
You need to use a content script to interact with web pages:
The primary issue is that you have to use a content script to interact with a web page, such as manipulating the DOM, as you desire to do. Content scripts have to be injected into the web page. This can be done with a content_scripts entry in your manifest.json, or with chrome.tabs.executeScript() from JavaScript that is in the background context (background scripts, event scripts, popups, panels, tabs containing pages from your add-on, etc.). For what you are doing, chrome.tabs.executeScript() is the way to go.
Additional issues:
chrome.tabs.create() is asynchronous. You need to wait for the callback to execute so the tab exists in order to inject a content script. You can not inject scripts into a tab that does not yet exist. Note: You could use other, more complex, methods of determining when to inject the content script, but the callback for chrome.tabs.create() is a good way to do it in this case.
Once you create the new tab, you want to inject a script. This is not the "active tab", so you need to add "https://vena.io/*" to your permissions in your manifest.json.
The elements you desire to interact with are not immediately available on the page when the content script is run. You need to wait until they are available. I just used a setTimeout loop to poll until the elements are available. I chose to poll on 250ms intervals a maximum of 100 times (25 seconds). The elements were there each time after the first delay.
document.getElementsByClassName() returns an HTMLCollection, not a single element.
Popups close when you activate a different tab. Once the popup is closed/destroyed, you can not do any more processing within the code for the popup. In order to get around that:
In your chrome.tabs.create(), include active:false to prevent the new tab from becoming active immediately.
Call chrome.tabs.update() in the callback for chrome.tabs.executeScript() to active the tab once the content script has been injected (i.e. when you are done with all the processing you are going to do in the popup).
Code
Changes were only needed in your manifest.json and popup.js.
manifest.json
{
"manifest_version": 2,
"name": "Vena",
"description": "This extension will allow users to login to vena accounts",
"version": "1.0",
"browser_action": {
"default_icon": "icon.png",
"default_popup": "popup.html"
},
"permissions": [
"activeTab", //This is not needed as you never interact with the active tab
"https://vena.io/*"
]
}
popup.js
document.addEventListener('DOMContentLoaded', function() {
var checkPageButton = document.getElementById('checkPage');
checkPageButton.addEventListener('click', function() {
var newURL = "https://vena.io/";
//Must not make the tab active, or the popup will be destroyed preventing any
// further processing.
chrome.tabs.create({ url: newURL,active:false }, function(tab){
console.log('Attempting to inject script into tab:',tab);
chrome.tabs.executeScript(tab.id,{code:`
(function(){
var count = 100; //Only try 100 times
function changeLoginWhenExists(){
var loginField = document.getElementsByClassName('js-email form-control input-lg')[0];
var passwordField = document.getElementsByClassName('js-password form-control input-lg')[0];
//loginField and passwordField are each an HTMLCollection
if( loginField && passwordField ){
loginField.value = 'gsand';
passwordField.value = '123';
} else {
if(count-- > 0 ){
//The elements we need don't exist yet, wait a bit to try again.
//This could more appropriately be a MutationObserver
setTimeout(changeLoginWhenExists,250);
}
}
}
changeLoginWhenExists();
})();
`},function(results){
//Now that we are done with processing, make the tab active. This will
// close/destroy the popup.
chrome.tabs.update(tab.id,{active:true});
});
});
}, false);
}, false);
May need use document.execCommand('insertText', false, text);
Depending on the page, you may need/want to use:
document.execCommand('insertText', false, textValue);
If you do so, you will need to first select/focus the desired element. This would be instead of setting the .value property. Which you use will depend on what you are actually doing and the composition of the page you are altering. For the specific example in the question, setting the element's .value property works. For inserting text, using `document.execCommand('insertText') is more generally applicable.
May need a MutationObserver
In the above code I use a setTimeout() loop to delay until the desired elements exist. While that works in the above case, depending on your use case, it may be more appropriate for you to use a MutationObserver. Largely, which to use will depend on how immediately you need to respond to the desired elements being added and what type of load you are putting on the page by looking for the elements. For more information about watching for DOM changes see: Is there a JavaScript/jQuery DOM change listener?
UI comment
Currently you have a popup that has a single button: "Login". From a user interaction point of view, it would probably be better to just use a plain browser action button. If you are intending to add functionality to your popup, then go ahead and keep the popup. If you are not going to add functionality, it does not make a lot of sense to force your user to click twice (click: open popup, then click: login) when they could have just clicked once.
Use an actual Password Manager
If this is functionality that you desire, rather than just something you are putting together just to learn, you should use an actual Password Manager. The functionality of securely storing passwords and inserting them appropriately in websites is non-trivial. You can easily make mistakes that result in compromising your security. I strongly recommend that you investigate the various ones available and choose one which fits your needs. Basically, all the ones that I have seen would easily provide you with the functionality you have at this time: open a popup; select the site; go to the site and fill in the user name and password. A password manager is a very significant project. It is not a project to be taken on lightly, or for someone who is not experienced in security issues.

How to access a page element from a background js of chrome extension

I have a context menu in my chrome extension and now I need to capture a specific page elements when the user click on that menu.
This is my manifest file:
{
"manifest_version": 2,
"name": "Capture",
"description": "This extension is capturing all text elements in the page",
"version": "0.1",
"permissions": ["contextMenus"],
"background": {
"scripts": ["jquery-2.0.2.js", "background.js"]
},
"manifest_version": 2
}
background.js
function captureTextBoxes(e) {
var textboxes = $(':text') ;
//alert(textboxes.length);
textboxes.each(function (i){
//code here
}
}
chrome.contextMenus.create({
title: "Capture All text box Elements",
contexts:["page"],
onclick: captureTextBoxes,
});
This was capturing 0 text box elements always. So I checked the passed document by adding following line:
alert(document.documentElement.innerHTML);
It returns this :
<head></head>
<body style="">
<script src="jquery-2.0.2.js"></script>
<script src="background.js"></script>
</body>
This is not my actual page, but a dynamic page created by the chrome itself.
Is there anyway to access the actual page content that were right clicked for the context menu? (From a background javaScript)
The contextMenus.onClicked event (which triggers the callback specified by onclick (in persistent background pages only)) is only available to the background page and the background page has no direct access to any web-page's DOM.
If you want to manipulate the web-page DOM, you have to:
Inject a content script into the web-page.
Pass a message to that content script, so it can manipulate the DOM for you.
(There are plenty of resources here in SO explaining how to achieve both.)
Take, also, a look at this answer to a similar question

Chrome extension that captures middle click and replaces URL

I'm completely new to chrome extensions but I've read the getting started and assembled the example from Google. I would like to build an extension that while it's active it captures middle mouse click events on mydomain.com, reads the URL , modifies it and launches the new tab with the newly created URL.
From what I understand so far I need to have a manifest.json file and a my_script.js file that will be injected in all mydomain.com page loads. Is this correct ? If yes how should I proceed next and what should I add to my manifest and javascript file to accomplish the given task. Some code examples would be much appreciated.
I've also read a couple of answers here on stackoverflow and if browserAction is going to be used it can only be used in extension pages, so you can not use it in content scripts. That would mean I would have to place my code in the background page instead of my_script.js . Please advice how should I proceed.
Thank you
Working script solution is:
$(document).click(function(e) {
var urlToGo = window.location.href;
// middle button
if (e.which == 2) {
urlToGo = "http://google.com";
window.open(urlToGo);
e.preventDefault();
e.stopPropagation();
}
});
You can start with my simple Chrome Extension Content Script Skeleton https://github.com/robin-drexler/Simple-Chrome-Extension-Content-Script-Skeleton, which provides a manifest and a content script, that'll be executed on every page you visit.
Now you can go on and implement your desired feature.
Then you could either usewindow.open to open a new tab/window (easier way) or the native Chrome APIs to open a new tab.
window.open (in content script)
$(function() {
$(document).on('click', function(e){
//do some replace magic here
var url = 'http://google.com';
if (e.which === 2) {
window.open(url);
}
});
});
http://jsfiddle.net/886jY/2/
Interesting reads for Chrome APIs
Messaging between background page and content script. IIRC you can only use the CHrome Tab APIs in the background page.
http://developer.chrome.com/extensions/messaging.html
Chrome Tab API
http://developer.chrome.com/extensions/tabs.html
Just as a little side-note, it is possibly not a good idea about middle button, because it is most redundant, least frequently used by users and even may be missing in some devices, especially on touch screens. You should possibly re-consider your approach in favor of using first mouse button, which can be conditionally enabled or disabled for your extinsion by means of a browser action button, which you mentioned yourself, or better yet - page action button, because you want to work only with specific domain.
As for the code of your interest (for middle button), it has nothing special about extensions, it's just a usual JavaScript to work with DOM of the page, where your content script is injected. For example, you can bind your handler for clicks something like this:
document.onmousedown = mouseDown;
function mouseDown(e)
{
var midclick;
if (!e) var e = window.event;
if (e.which) midclick = (e.which == 2);
else if (e.button) midclick = (e.button == 4); // MS
if (midclick)
{
var target;
if (e.target) target = e.target;
else if (e.srcElement) target = e.srcElement;
if (target.innerHTML) // do stuff what you need here,
{ // assume get all the content as url
openNewURL(target.innerHTML);
}
}
}
function openNewURL(newurl)
{
chrome.extension.sendMessage({url: newurl}, function(response) {});
}
Your first understanding was correct: you need manifest and content script, and background page.
In the background page process incoming requests:
chrome.extension.onMessage.addListener(
function(request, sender, sendResponse)
{
if (request.url)
{
chrome.tabs.create({url: request.url});
}
});
You need to have the manifest.json declaring a content script, which are js files that are going to be run for each page (and recursively to each frame inside the document, if you set the property to "all_frames" to true).
Check this sample manifest.json:
{
"name": "MyExtension",
"version": "0.2",
"manifest_version": 2,
"description": "My description.",
"content_scripts": [
{
"matches": ["<all_urls>"],
"js": ["jquery.js", "my_javascript_to_execute_on_each_frame.js"],
"all_frames": true
}
]
}
With this manifest, you would have to provide jquery (optional) and a single js file with the code to listen for the mouse click, change the current document's url and open a new tab. This script could like something like this:
​$(document).click(function(e)​ {
var urlToGo = window.location.href;
// middle button
if (e.which === 2) {
urlToGo = ... // do your url hackery =)
window.open(urlToGo);
e.stopPropagation();
}
});
​

Categories