content_script not running on GitHub when following links - google-chrome-extension

I am writing a Chrome Extension mainly for Pull Requests on Github Enterprise and have ran into an issue. When the page is loaded via a refresh or direct entering of the url from your browser it runs, when it is ran from clicking a link within Github it does not.
For instance if you go to this page with Pull Requests and click into one of them it will not run. But if you refresh that same page it will run.
manifest.json
{
"name": "Github Sample",
"manifest_version": 2,
"version": "0.0.1",
"description": "Sample",
"permissions": [
"tabs", "<all_urls>",
"http://github.com/*"
],
"content_scripts": [
{
"matches": [ "https://github.com/*" ],
"js": ["github.sample.js"],
"run_at": "document_end"
}
]
}
github.sample.json
// ==UserScript==
// #author Jacob Schoen
// ==/UserScript==
alert("Extension has Ran");
To make this easier I have pushed this to github.
Any ideas on how to address this?

GitHub site uses jquery-pjax library (see How to make github style page transitions by pjax).
Basically you only need to run the content script once and attach pjax event handlers inside an injected <script> element code which will be reusing a jQueryor $ variable of the site.
In those handlers you can send a message with document.dispatchEvent to your content script that will receive it in its window.addEventListener("blabla", ...)
or you can allow the access to chrome.runtime.sendMessage on the github site in manifest.json so that page-injected code will be able to send a message that can be received by the extension in chrome.runtime.onMessageExternal listener.
Alternatively you can use chrome.webNavigation.onHistoryStateUpdated in the background script but that will cause a warning during the installation that the extension can "Read your browsing history" which is a global permission unlike the content script solution.

The working example I came up with, in case it helps anyone else.
manifest.json
{
"name": "Github Sample",
"manifest_version": 2,
"version": "0.0.1",
"description": "Sample",
"permissions": [
"activeTab",
"tabs", "<all_urls>",
"http://github.com/*"
],
"content_scripts": [
{
"matches": [ "https://github.com/*" ],
"js": ["github.sample.js"],
"run_at": "document_idle"
}
],
"web_accessible_resources": ["inject.js"]
}
github.sample.js
// ==UserScript==
// #author Jacob Schoen
// ==/UserScript==
function myAlert() {
alert("Extension has Ran");
}
window.addEventListener("pageLoadTransition", myAlert);
var s = document.createElement('script');
// TODO: add "script.js" to web_accessible_resources in manifest.json
s.src = chrome.extension.getURL('inject.js');
s.onload = function() {
this.parentNode.removeChild(this);
};
(document.head||document.documentElement).appendChild(s);
//still have to load this one for direct page loads
myAlert();
inject.js
$(document).on('pjax:success', function() {
var evt=document.createEvent("CustomEvent");
evt.initCustomEvent("pageLoadTransition", true, true, null);
document.dispatchEvent(evt);
})
I also updated the Github repository with the working example.

Related

Enable Chrome extension action button only on one host

I've been trying to migrate one of my Chrome extensions to manifest v3, and I'm having trouble with the page_action. In manifest v3, the page_action and browser_action are merged into action, which is all good, but it's not clear to me how I can get the behavior I had previously with the new APIs.
A bit of background; the extension in question is only supposed to run on one host (let's say https://example.com). As such, I want to grey out the icon on pages with a different host. It has a popup with some settings but the main functionality is inserted via a content script (this works).
The old extension using manifest v2 used
{
"manifest_version": 2,
"name": "...",
"description": "...",
"version": "...",
"permissions": ["declarativeContent", "storage", "https://example.com/", "tabs"],
"page_action": {
"default_icon": { ... },
"default_popup": "popup.html"
},
"background": {
"scripts": ["background.js"],
"persistent": false
},
"icons": { ... },
"content_scripts": [{
"js": ["content.js"],
"matches": ["https://example.com/*", "https://www.example.com/*"]
}]
}
and in background.js I used
chrome.runtime.onInstalled.addListener(function(){
const pageUrl = {hostEquals: 'www.example.com'};
const {
onPageChanged,
PageStateMatcher,
ShowPageAction
} = chrome.declarativeContent;
onPageChanged.removeRules(undefined, function(){
onPageChanged.addRules([{
conditions: [new PageStateMatcher({pageUrl})],
actions: [new ShowPageAction()]
}]);
});
});
this works fine and the icon gets greyed out except on https://example.com. When migrating, the manifest looks like
{
"manifest_version": 3,
"name": ...,
"description": ...,
"version": ...,
"permissions": ["storage", "tabs", "declarativeContent", "activeTab"],
"background": {"service_worker": "service-worker.js"},
"action": {
"default_icon": { ... },
"default_popup": "popup/index.html"
},
"icons": { ... },
"content_scripts": [{
"matches": ["*://*.example.com/*"],
"js": ["content/detect-theme.js"]
}]
}
I cannot seem to get this to work properly. I've tried adding host_permissions, removing the declarativeContent-related code (as it doesn't seem to affect the icon whatsoever) but the extension stays available on all hosts. I know I can use the chrome.action.enable and chrome.action.disable methods to simulate this behavior but it seems overkill for such a simple use-case.
Actually, the action being available even on other pages is not breaking by any means, but I would like to make it more clear to my users that the extension only does things on https://example.com and nowhere else. Perhaps this is not even the right approach; if it isn't, I accept that as an answer as well.
TLDR; how do I only enable the (page-)action on a specific host with manifest v3?
Had the same issue myself, and I just solved it!
I fixed it by disabling the extension in the handler before adding the activation rule. I've removed the actual handler for brevity.
chrome.runtime.onInstalled.addListener(() => {
chrome.action.disable(); // The important line!
// actual handler...
});
I would guess that ShowPageAction doesn't disable the extension by default anymore.

Chrome Extension Manifest matches not working for clicking into that url

I have the following manifest
{
"manifest_version":2,
"name": "HelloWorld",
"version": "0.1",
"content_scripts": [
{
"matches": [
"*://www.mywebsite.com/"
],
"js": ["home.js"],
"run_at": "document_start"
}
]
}
It works perfectly when I enter www.mywebsite.com through browser url bar. But when I route to the same url www.mywebsite.com through hyperlinks in another url, the script does not trigger. Why is that? I do not see this in
I just figured out a temporary method, you can do
<some_dom_object>.addEventListener("click", function() {
// do the thing you want happen.
})
on that "button element" or "anchor element" or whatever that you clicked to that website, so the code will get executed also when you "click" to the url.

Google Chrome extension's content script not working on few pages

I am developing a simple Chrome extension, that does something when a password-input box is focused. I'm using following content script to achieve the same (code is simplified for asking this question). The code does not work on pages like https://accounts.google.com/ but it works perfectly for pages like https://www.linkedin.com/. Is it because of some Javascript/JQuery conflicts? Or some other reason? Please help. I tried using noConflict api, but it didn't help.
Content script:
var inputs = document.getElementsByTagName('input');
function foo() {
for (var i = 0; i < inputs.length; i++) {
if (inputs[i].type.toLowerCase() == 'password')
inputs[i].addEventListener("focus", bar);
}
}
function bar() {
alert(1);
}
foo();
Above script finds input elements of type passwords, but does not add event listener.
Manifest:
{
"manifest_version": 2,
"name": "MyExtension",
"version": "1.0",
"options_page": "options.html",
"browser_action": {
"default_icon": "img/abcd.png",
"default_title": "MyExtension",
"default_popup": "popup.html"
},"icons": { "16": "img/abcd16.png",
"48": "img/abcd48.png",
"128": "img/abcd128.png" },"description":"abcd abcd",
"permissions": [
"storage","tabs"
],
"content_scripts": [
{
"matches": ["<all_urls>"
],
"js": ["js/content.js","js/jquery.1.8.3.min.js"],
"css": ["css/style.css"],
"run_at": "document_end",
"all_frames": true
}
],"web_accessible_resources": [
"img/*.png",
"css/*.css"
]
}
Have you considered that the input field you seek doesn't exist by the time that the script executes? https://accounts.google.com/ is a dynamic form, the password field is created long after the page is loaded (document_end) and therefore long after you collect inputs.
In addition to binding the handler to existing elements, you need to watch for new ones being added. I would suggest using mutation-summary library with a query {element: "input[type=password]"}, but if you don't want to involve a library the basic building block to look at is MutationObserver.

Chrome extension cannot inject content script into dynamically generated iframes

I want to access performance.getEntries inside all iframes on a certain page (my aim is to create a deep understanding of what is going on inside banner tags). However, I cannot inject a content script inside dynamically generated iframes it seems.
Am I doing something wrong in the example below?
Or is there a possible other route of getting performance.getEntries for these dynamically generated frames?
Consider an extremely simple html like this:
<html>
<body>
<script>
var iframe = document.createElement('iframe');
var html = '<body><img src="http://dummyimage.com/100x200/333/fff&text=hey_2_1"><img src="http://dummyimage.com/100x200/555/fff&text=hey_2_2"></body>';
iframe.src = 'data:text/html;charset=utf-8,' + encodeURI(html);
document.body.appendChild(iframe);
</script>
<img src="http://dummyimage.com/100x200/333/fff&text=hey_1_1">
<img src="http://dummyimage.com/100x200/333/fff&text=hey_1_2">
</body>
</html>
And an extension like this
manifest.json
{
"manifest_version": 2,
"name": "Banner Spy 1",
"description": "Check all iframes",
"version": "1.0",
"background": {
"scripts": ["background.js"]
},
"content_scripts": [
{
"run_at": "document_end",
"all_frames" : true,
"match_about_blank": true,
"matches": ["<all_urls>"],
"js": ["content_frame.js"]
}
],
"permissions": [
"tabs",
"activeTab",
"<all_urls>"
]
}
content_frame.js
setTimeout(function(){
var pe = performance.getEntries();
console.log(pe);
}, 1000);
Running this extension will only show performance entries for the top page, not the dynamic iframe.

Simple Chrome Content Script Not Running

I wrote a short content script, which stops a particular site from creating new windows for link clicks.
This is my first Chrome extension, and I've scored this website and the internet for a reason why it won't run, but I can't find any. I'm probably making a fundamental amateur mistake somewhere.
Manifest.json:
{
"manifest_version": 2,
"name": "DHS Links",
"description": "Stops the school's site from constantly opening new windows.",
"version": "1.0",
"browser_action": {
"default_icon": "icon.png"
},
"content_scripts":[
{
"matches": ["*://www.darienps.org/dhs/*"],
"js": ["jquery.js", "makeNormalLinks.js"],
"run_at": "document_end"
}
]
}
I tested the Javascript file by itself on a local version of the site, so I'm pretty sure it's fine, but just in case:
makeNormalLinks.js:
$(document).ready(function() {
$("a").each(function(){
$(this).removeAttr("onclick");
});
});
A copy of jQuery is in the same directory and doesn't seem to have any issues.
Here's the onclick code for many links on the website:
onclick="window.open(this.href,'targetWindow','toolbar=no,location=no,status=no,menubar=no,scrollbars=yes,resizable=yes,')
Thank you for looking this over!
Edit:
I tried two of the injection methods from Rob W's response to another question linked to in the comments by Teepeemm.
Here's the new code for Method 1:
Manifest.json:
{
"manifest_version": 2,
"name": "DHS Links",
"description": "Stops the school's site from constantly opening new windows.",
"version": "1.0",
"browser_action": {
"default_icon": "icon.png"
},
"content_scripts":[
{
"matches": ["*://www.darienps.org/dhs/*"],
"js": ["jquery.js", "scriptLauncher.js"]
}
],
"web_accessible_resources": ["makeNormalLinks.js"]
}
scriptLauncher.js:
var s = document.createElement('script');
// TODO: add "script.js" to web_accessible_resources in manifest.json
s.src = chrome.extension.getURL('makeNormalLinks.js');
s.onload = function() {
this.parentNode.removeChild(this);
};
(document.head||document.documentElement).appendChild(s);
Method 2a:
(Uses old Manifest.js)
makeNormalLinks.js:
var actualCode = ['$(document).ready(function(){',
'$("a").each(function(){',
'$(this).removeAttr("onclick");',
'});',
'});'].join('\n');
var script = document.createElement('script');
script.textContent = actualCode;
(document.head||document.documentElement).appendChild(script);
script.parentNode.removeChild(script);
Unfortunately, neither method seems to work. I'm extremely grateful to those who commented and think we're getting close to an answer.
Solution:
Manifest.json:
{
"manifest_version": 2,
"name": "DHS Links",
"description": "Stops the school's site from constantly opening new windows.",
"version": "1.0",
"browser_action": {
"default_icon": "icon.png"
},
"content_scripts":[
{
"matches": ["*://www.darienps.org/dhs/*"],
"js": ["makeNormalLinks.js"]
}
]
}
makeNormalLinks.js:
document.addEventListener("click", function(e) {
e.stopPropagation();
}, true);
Thanks you, Scott and all who commented!
Using the onclick attribute has many weird side effects. A more up to date approach would be to add a listener to the document that filters unwanted events. Like:
document.addEventListener("click", function(e) {
e.stopPropagation();
}, true);
The true argument is important (see event capture). This will block all click event listeners from firing, but the default click action will still be triggered.
I was having a similiar issue getting the content script to fire in Google Mail. I stumbled upon a page that recommended using the "hashchange" event.
// Event listener
window.addEventListener("hashchange", function () {
alert("hashchanged");
}, false);

Resources