I am trying to make a Chrome extension with a content script to inject a script into a webpage before all other scripts in the page. (I am using the xhook library to intercept XHR requests, which overwrites the XHR class. I need to do this because it is currently impossible to modify responses using Chrome extension APIs.) The "document_start" event is executed before any of the DOM is written, so I manually create the body element with the content script. However, this creates 2 body tags in the HTML, which appears to make variables defined within the injected script tag inaccessible to the code in the main page.
How should I do this?
I have simplified version of my code below:
manifest.json
{
// Required
"manifest_version": 2,
"name": "My Extension",
"version": "0.1",
"description": "My Description",
"author": "Me",
"permissions": ["https://example.com/*"],
"content_scripts": [{
"matches": ["https://example.com/*"],
"js": ["xhook.js"],
"run_at": "document_start",
"all_frames": true
}
]
}
xhook.js
var script_tag = document.createElement('script');
script_tag.type = 'text/javascript';
holder = document.createTextNode(`
//Xhook library code
// XHook - v1.4.9 - https://github.com/jpillora/xhook
//...
//Now to use the library
console.log('loading extension');
xhook.after(function (request, response) {
//console.log(request.url);
if (request.url.startsWith("https://example.com/")) {
var urlParams = new URLSearchParams(window.location.search);
fetch('https://example.com/robots.txt')
.then(
function (apiresponse) {
if (apiresponse.status == 200) {
response.text = apiresponse.text();
return;
};
if (apiresponse.status !== 200) {
console.log('File not found. Status Code: ' +
apiresponse.status);
return;
};
});
};
});
xhook.enable();`);
script_tag.appendChild(holder);
document.body = document.createElement("body");
document.head.appendChild(script_tag);
Thanks!
If the extension is loaded at document_start, document.head = null. Hence, to overcome this, do - document.lastChild.appendChild(script_tag);. This creates a script tag in your <html> hierarchy. Hope this helps.
Also, Could you please tell why are you doing the following statement
document.body = document.createElement("body"); I believe this is not required.
Related
I am trying to make a markdown preview tab for a forum I often use that uses markdown in it's formatting but currently does not have any way to preview what that markdown will look like.
I want to use the page down markdown converter however I don't know how to use the files inside the content script
here is my manifest.json
{
"name": "Forum Post Previewer",
"version": "0.1",
"manifest_version":2,
"description":"Adds a preview tab on the post editor",
"permissions": [
"activeTab",
"*://*/*"
],
"content_scripts": [
{
"js": ["previewtab.js"]
}
],
}
and here is the previewtab.js
// Adding the preview tab
var tabs = document.getElementsByClassName("nav nav-tabs");
var list = document.createElement("li");
var tab = document.createElement("a");
tab.innerHTML = "Preview";
tab.setAttribute("data-toggle", "tab");
tab.setAttribute("href", "#tab3");
list.appendChild(tab);
document.getElementById("post-editor").parentElement.firstElementChild.appendChild(list);
var content = document.createElement("div");
content.setAttribute("class", "tab-pane");
content.setAttribute("id", "tab3");
var bar = document.createElement("div");
bar.setAttribute("id", "wmd-button-bar");
var textarea = document.createElement("textarea");
textarea.setAttribute("id", "wmd-input");
textarea.setAttribute("class", "wmd-input");
var preview = document.createElement("div");
preview.setAttribute("id", "wmd-preview");
preview.setAttribute("class", "wmd-panel wmd-preview");
content.appendChild(bar);
content.appendChild(textarea);
content.appendChild(preview);
document.getElementById("post-editor").appendChild(content);
// Using the converter
var converter = Markdown.getSanitizingConverter();
var editor = new Markdown.Editor(converter);
editor.run();
right now I get errors when using the converter because it does not know where Markdown has come from.
Can you help me to find out how to use this external script in a chrome extension
Thanks
Just add the .js files to your extension and include them as content scripts before yours, like this:
"content_scripts": [
{
"js": ["Markdown.Converter", "Markdown.Editor", "Markdown.Sanitizer", "previewtab.js"]
}
],
I have made a little Chrome extension that injects some code in the current page.
This extension has a weird behaviour though, whenever the code is injected, none of the page's Javascript triggers seem to work anymore.
Would one of you have any idea what that happens? On top of fixing the code I'd really like to know why this happens.
Example : on this page : http://www.acti.fr/success-story/ghd/ if the extension injects the picture, I cannot click on either the menu or "continuer la lecture" at the bottom.
Here are the manifest and the actual code :
manifest.json
{
"manifest_version": 2,
"name": "wpi",
"description": "just an other extension",
"version": "1.0",
"content_scripts": [{
"matches": ["http://*/*", "https://*/*"],
"js": ["my-style.js"]
}]
}
my-script.js :
function wpkm_check_content(wpkm_text) {
var wpkm_word = wpkm_text.split(" ");
var wpkm_c = wpkm_word[0].localeCompare("Wordpress");
if (wpkm_c == 1)
return (1);
return (0);
}
var wpkm_html = '<div id="wpkm-bloc" style="position:absolute;right:10px;top:10px;z-index:99999">';
wpkm_html += '<img id="wpkm-img" src="https://nathanarnold.files.wordpress.com/2009/02/ssim51.gif">';
wpkm_html += '</div>';
var wpkm_sdomain = document.domain;
var wpkm_request = new XMLHttpRequest();
wpkm_request.open('GET', '/license.txt', true);
wpkm_request.onreadystatechange = function(){
if (wpkm_request.readyState === 4){
if (wpkm_request.status === 200
&& wpkm_check_content(wpkm_request.responseText) == 1) {
document.body.innerHTML += wpkm_html;
}
else {
console.log("Oh no, it does not exist!");
}
}
};
wpkm_request.send();
Any hints will be appreciated :D
You're effectively reassigning the entire innerHTML of the document body by using += append operator which causes reevaluation and recreation of the entire page and of course all previously attached event handlers aren't reattached automatically.
Use insertAdjacentHTML instead:
document.body.insertAdjacentHTML("beforeend", wpkm_html);
My Chrome extension has a Content Script that adds a DIV with a button. The OnClick JS function defined for the button, however, never executes (defined in the same Content Script), so the button does nothing. Why is that?
contentscript.js
var msg = "Click this button <button onclick='test()'>Test</button>";
var div = document.createElement( 'div' );
div.id = 'testDiv';
document.body.appendChild( div );
document.getElementById('testDiv').innerHTML = msg;
function test()
{
alert('in test()'); // Never gets here
}
manifest.json
"content_scripts": [
{
"matches": [
"http://*/*",
"https://*/*"
],
"css": ["contentstyle.css"],
"js": ["jquery-1.11.2.min.js", "contentscript.js"]
Look at the official docs regarding policies. Inline javascript is strictly prohibited. The docs explain how to do it. https://developer.chrome.com/extensions/contentSecurityPolicy
This problem seems to have been sort of resolved, as long as the URL of the page you're injecting your javascript into starts with www. What do you do if it doesn't? Here's the relevant part of my manifest:
"content_scripts": [
{
"run_at": "document_start",
"matches": ["https://groups.google.com/forum/?fromgroups=#!newtopic/opencomments-site-discussions"],
"js": ["postMsg.js"]
}
],
The problem, according to another stackoverflow post, is because the URL of the page doesn't begin with 'www'. Does that mean that you can't inject javascript into secure pages whose URL doesn't begin with 'www', or is there another way? This had never been a problem in the past, because my extension had run with Version 1 manifests.
Forgot to add the content script:
var subject = document.getElementById("p-s-0");
subject.setAttribute("value", "foo");
The element with ID "p-s-0" is the Subject field in the Google Groups Post page, so the field should display "foo".
A few issues:
That is a not valid match pattern because they only specify up to the URL path (the part before the ?).
Change the matches to:
"matches": ["https://groups.google.com/forum/*"],
The overall URL (https://groups.google.com/forum/?fromgroups=#!newtopic/opencomments-site-discussions) is not practical because Google changes the URL parameters willy nilly. For example, fromgroups is not often present, and may not have the = if it is. Additional parameters, like hl=en come and go. (This is the reason why my earlier answer worked for me, but not for you.)
So, using include_globs in the manifest would be a messy, error-prone exercise.
The solution is to checklocation.hash within the content script.
The script is set to "run_at": "document_start", so the content script is running before there is any node with id p-s-0.
Change the manifest to "run_at": "document_end".
The new Google groups is heavily AJAX driven. So, The "New Topic" page is usually "loaded" without actually loading a whole new page. This means the content script will not rerun. It needs to monitor for "new" AJAX-loaded pages.
Check for "new" pages by monitoring the hashchange event.
Additionally, the p-s-0 element is added by AJAX, and is not immediately available on a "new" page. Check for this element within a setInterval.
Putting it all together,
The manifest.json becomes:
{
"manifest_version": 2,
"content_scripts": [ {
"run_at": "document_end",
"js": [ "postMsg.js" ],
"matches": [ "https://groups.google.com/forum/*" ]
} ],
"description": "Fills in subject when posting a new topic in select google groups",
"name": "Google groups, Topic-subject filler",
"version": "1"
}
The content script(postMsg.js) becomes:
fireOnNewTopic (); // Initial run on cold start or full reload.
window.addEventListener ("hashchange", fireOnNewTopic, false);
function fireOnNewTopic () {
/*-- For the pages we want, location.hash will contain values
like: "#!newtopic/{group title}"
*/
if (location.hash) {
var locHashParts = location.hash.split ('/');
if (locHashParts.length > 1 && locHashParts[0] == '#!newtopic') {
var subjectStr = '';
switch (locHashParts[1]) {
case 'opencomments-site-discussions':
subjectStr = 'Site discussion truth';
break;
case 'greasemonkey-users':
subjectStr = 'GM wisdom';
break;
default:
break;
}
if (subjectStr) {
runPayloadCode (subjectStr);
}
}
}
}
function runPayloadCode (subjectStr) {
var targetID = 'p-s-0'
var failsafeCount = 0;
var subjectInpTimer = setInterval ( function() {
var subject = document.getElementById (targetID);
if (subject) {
clearInterval (subjectInpTimer);
subject.setAttribute ("value", subjectStr);
}
else {
failsafeCount++;
//console.log ("failsafeCount: ", failsafeCount);
if (failsafeCount > 300) {
clearInterval (subjectInpTimer);
alert ('Node id ' + targetID + ' not found!');
}
}
},
200
);
}
i looked everywhere trying to find an answer to this question.
i want my extension to either disable all javascript on the page BUT to allow the insertion of a cotent script that will work. (so chrome.contentSettings.javascript is not a valid option for now)
Alternatively i want a way to remove all script tags before any of them fire (which is kinda the same thing)
i tried inserting content scripts to runat:document_start but the dom is not fully there at the time. itried adding a conte t s ript on tabs.onUpdate when state is loading but that is too late and as well as content scripts at document_end (all of which who try to remove script tags) but it is still too late.
in an act of desperation i tried altering the behavior of the getters and setters of element.innerHTML to. remove the tags but that did not work as well
i am trying to avoid sending an xhr request to location.href and parse and re_set the content as that is too intensive.
any ideas?
After seeing your comments I think this might suit your needs. It works by getting the page's source, render it to a DOM, disable all the JS and then put it back into the page. Not exactly what you wanted but should suit your case well...
mainfest.json
{
"name": "Reload and Kill JS - Using a content script",
"version": "1.0",
"permissions": [
"tabs", "<all_urls>" , "storage"
],
"background": {
"scripts": ["background.js"]
},
"content_scripts": [
{
"matches": ["<all_urls>"],
"js": ["injectedCode.js"],
"run_at" : "document_start"
}
],
"minimum_chrome_version" : "20",
"manifest_version" : 2
}
background.js
chrome.storage.local.set({"blockhttp://paez.kodingen.com/":true});
injectedCode.js
reloadAndKillJS = function() {
document.documentElement.innerHTML = 'Reloading Page...';
var xhr = new XMLHttpRequest();
xhr.open('GET', window.location.href, true);
xhr.onerror = function() {
document.documentElement.innerHTML = 'Error getting Page';
}
xhr.onload = function() {
var page = document.implementation.createHTMLDocument("");
page.documentElement.innerHTML = this.responseText;
var newPage = document.importNode(page.documentElement,true);
var nodeList = newPage.querySelectorAll('script');
for (var i = 0; i < nodeList.length; ++i) {
var node = nodeList[i];
if (node.src) {
node.setAttribute('original-src', node.src);
node.removeAttribute('src');
}
node.innerText = '';
}
document.replaceChild(newPage, document.documentElement);
delete page;
// Do your thing here
}
xhr.send();
}
chrome.storage.local.get("block"+window.location.href,function(items)
{
if (items["block"+window.location.href]){
window.stop();
reloadAndKillJS();
}
});
Well, the only way to truly prevent scripts is with contentSettings. So you need to put your code somewhere else, in another domain, since contentSettings rules can be applied for specific URL's.
Put you content script to run at document start.
contentScript.js:
window.stop();
document.all[0].innerHTML = "\
<html>\
<body>\
<iframe src=\"chrome-extension://ID/inject.html?url="+encodeURIComponent(location.href)+"\"></iframe>\
</body>\
</html>";
inject.html:
<html>
<head>
<script>
var frame = document.querySelector('iframe');
frame.src = location.search.replace('?url=', '');
frame.onload = function() {
//Your stuff here
}
</script>
</head>
<body>
<iframe></iframe>
</body>
<html>
Now your code is in a parent frame and in another domain, but it may cause some CORS issues which you can try found some workarounds later.
Give a try, then tell me if there's something to fix.