I am learning how to create a Firefox add-on. I want to do a simple add-on that will inject a script in a page. I have read the documentation but I can't solve this problem.
in the cfx run logs, I can see that it runs the script various times in the same page when it should do it only once.
main.js
var pageMod = require('sdk/page-mod')
var data = require('sdk/self').data
pageMod.PageMod({
include: ['*'],
contentScriptWhen: 'end',
contentScriptFile: data.url('starter.js')
})
starter.js
var script = document.createElement('script');
script.id = 'i-inject';
script.type = 'text/javascript';
script.src = 'http://localhost:8888/injectme.js';
document.getElementsByTagName('head')[0].appendChild(script);
console.log('injected');
Do you see this console.log('injected') ? I can see that it's printed 5 times in the console when I do cfx run and each time I reload the page. I don't understand that behaviour.
Any help greatly appreciated :)
I just finished asking the same question, for some reason multiple searches didn't lead me to this question until now.
The link you posted concerning iFrames lead me to this solution, which seems to be working well.
In Firefox, iFrames are still Pages, and by default the SDK doesn't discriminate between iFrames and top Pages, causing content scripts to attach to ANY loaded page ( or iFrame ) that meets your pageMod 'match' requirements. Rather than use page-mod, you can use tabs to inject the scripts and data you need.
var data = require("sdk/self").data;
var tabs = require('sdk/tabs');
tabs.on('ready', function(tab) {
var worker = tab.attach({
contentScriptOptions: {},
contentScriptFile: [data.url('myScript.js')]
});
// now set up your hooks:
worker.port.on('someKey', function(data) {
//do whatever with your data
});
});
Alternatively, you can modify the behavior of PageMod to only apply to the top page, which will prevent it from running in any iFrames on the page.
var data = require("sdk/self").data;
var pageMod = require('sdk/page-mod');
pageMod.PageMod({
match: [*],
attachTo: ["top"],
contentScriptOptions: {},
contentScriptFile: [data.url('myScript.js')],
onAttach: function(worker) {
//function body
}
});
More details on how to further control page-mod behavior can be found in their API docs
Related
I'm looking to override the existing console commands via my Chrome extension - the reason for this is I wish to record the console logs for a specific site.
Unfortunately I cannot seem to update the DOM, this is what i've tried so far:
// Run functions on page change
chrome.tabs.onUpdated.addListener( function (tabId, changeInfo, tab) {
var s = document.createElement('script');
// TODO: add "script.js" to web_accessible_resources in manifest.json
s.src = chrome.runtime.getURL('core/js/app/console.js');
s.onload = function() {
this.remove();
};
(document.head || document.documentElement).appendChild(s);
});
console.js
// Replace functionality of console log
console.defaultLog = console.log.bind(console);
console.logs = [];
console.log = function(){
console.defaultLog.apply(console, arguments);
console.logs.push(Array.from(arguments));
};
// Replace functionality of console error
console.defaultError = console.error.bind(console);
console.errors = [];
console.error = function(){
console.defaultError.apply(console, arguments);
console.errors.push(Array.from(arguments));
};
// Replace functionality of console warn
console.defaultWarn = console.warn.bind(console);
console.warns = [];
console.warn = function(){
console.defaultWarn.apply(console, arguments);
console.warns.push(Array.from(arguments));
};
// Replace functionality of console debug
console.defaultDebug = console.debug.bind(console);
console.debugs = [];
console.debug = function(){
console.defaultDebug.apply(console, arguments);
console.debugs.push(Array.from(arguments));
};
The script runs successfully with an alert().
The goal for me is to access console.logs - but its undefined which means I haven't gotten access to the DOM, despite injecting a script.
If not possible, even a third party integration would be helpful i.e. Java or C?
Any thoughts would be greatly appreciated :)
I found this post and I think Tampermonkey injects a script with the immediate function that you add in the Tampermonkey Chrome extension page, I found something similar in extensions like Wappalyzer, and looks good and safe, you could use WebRequest to inject to your website the new "polyfill" before the page is fully loaded as the post says.
Here the example of Wappalyzer that I mentioned before, this is the JS load in StackOverflow with Wappalyzer using the code injection, I didn't test it with Tampermonkey yet
EDIT
Checking Wappalyzer, how to inject the code is the easy part, you can use (Wappalyzer github example):
const script = document.createElement('script')
script.setAttribute('src', chrome.extension.getURL('js/inject.js'))
This probably will not fix your problem, this code is executed after all the content was loaded in the DOM. But, you can find how to fix that problem in this post
I'll suggest to use onCommitted event (doc1/doc2)
Using the mozilla.org example you will have something like
const filter = {
url: //website to track logs
[
{hostContains: "example.com"},
{hostPrefix: "developer"}
]
}
function logOnCommitted(details) {
//Inject Script on webpage
}
browser.webNavigation.onCommitted.addListener(logOnCommitted, filter);
It might be worth trying to redefine the entire console object:
const saved = window.console
window.console = {...saved, log: function(...args){ saved.log("Hello", ...args) }}
But it's probably impossible, because content scripts live in an isolated world:
Isolated worlds do not allow for content scripts, the extension, and the web page to access any variables or functions created by the others. This also gives content scripts the ability to enable functionality that should not be accessible to the web page.
Although in Tampermonkey this script works.
I believe Tampermonkey handles this by knowing the subtleties and tracking changes in the extensions host's protection mechanism.
BTW, for small tasks, there is a decent alternative to chrome extensions in the form of code snippets.
I am writing a Firefox add-on which uses context-menu and page-mod modules.
This add-on adds a new menu item to the context menu when the context is input control. When the menu item is clicked, I request to the server to get some data and pushes the result to the content script which page-mod is using.
For some reason, onAttach function is not getting called. There are no errors in the console. I am not sure why it is not getting called. Here is what I am doing.
var data = require("self").data,
contextMenu = require("context-menu"),
request = require("request").Request;
var workers = [];
var pageMod = require("page-mod");
pageMod.PageMod({
include: '*',
contentScriptWhen: 'ready',
contentScriptFile: [data.url("jquery-1.8.2.min.js"), data.url("varnam.js")],
onAttach: function(worker) {
console.log("onAttach");
workers.push(worker);
worker.on("detach", function() {
var index = workers.indexOf(worker);
if (index >= 0) workers.splice(index, 1);
});
}
});
Any help to fix this issue would be great. Full code is available here.
When the url pattern begins with (or is just) an asterisk, then it is matched against urls with a scheme of http, https and ftp.
Since your test page is in the data directory its url scheme is resource. And that's why PageMod is not triggered.
You can add the test page url by doing something like
var varnam = pageMod.PageMod({
// blah blah
});
varnam.include.add(data.url("test.html"));
I'm trying to build an addon for LinkedIn but the contentscript is outputted in every frame...
My main.js:
exports.main = function() {
var pageMod = require("page-mod");
pageMod.PageMod({
include: "http://www.linkedin.com/*",
contentScriptWhen: 'ready',
contentScript: 'alert("test")'
});
};
by doing a check for frame elements i can do an action if its the top frame
if(window.frameElement === null){ alert("YEAH this is the right place!); }
But my content srcipt is complex and uses jquery, and this script still puts the script in every frame...
UPDATE, the SDK's page-mod api now supports 'attachTo', so you can do this instead:
var data = require("sdk/self").data;
var page = require('sdk/page-mod');
page.PageMod({
match:['*'],
contentScriptOptions: {},
contentScriptFile: [data.url('myScript.js')],
attachTo: 'top',
onAttach: function(worker) {
//set up hooks or other background behavior
},
});
See this more recent question for more info.
There are two approaches you could look into:
1. attach your content scripts using the tabs module. This works because the tabs module only deals with top-level documents. Here is a simple example:
https://builder.addons.mozilla.org/package/22176/latest/
2. do an initial load of a very small content script via page-mod, and then if thepage is something you really want to mod, inject scripts by sending them via port.emit messages. There is an example of this sort of scheme from the dotjs add-on:
https://github.com/rlr/dotjs-addon
I'm working on a Firefox extension, and I need to be able to communicate between the addon script and the content scripts. I have one direction of this working: passing the URL of a script from the addon script to a content script. However, I need to be able to go in the reverse direction, as well. My main.js file looks like this:
var data = require("self").data;
var pageMod = require("page-mod");
pageMod.PageMod({
include: "https://trello.com/board/*",
contentScriptWhen: 'end',
contentScriptFile: data.url("scrumello_beta.user.js"),
onAttach: function(worker) {
worker.postMessage(data.url("scrumello_beta.js"));
worker.on("message", function(addonMessage)
{
console.log(addonMessage);
});
}
});
In the client script, I have the following method:
function OpenProcess(SCRNumber)
{
self.postMessage(SCRNumber);
}
However, when this method is called, I get the following error:
Timestamp: 8/7/2012 12:15:58 PM
Error: NS_ERROR_XPC_NOT_ENOUGH_ARGS: Not enough arguments [nsIDOMWindow.postMessage]
Source File: resource://jid0-3mulsijczmtjeuwkd5npayasqf8-at-jetpack/scogan-3/data/scrumello_beta.js
Line: 1038
This prevents the worker.on("message"... event from ever being triggered. As far as I know, postMessage only takes one argument, so any help here would be appreciated.
EDIT:
I've changed the postMessage call to
self.postMessage(SCRNumber, "*");
I wrapped it in console.log's, both of which are being printed, so I have to assume the message is actually being posted. However, the event handler in main.js never picks up the message, because the console.log I have in there is never printed.
Here's how I did it. (Notice that I never used self.postmessage)
Addon script (main.js) to content script communication:
contentPage = pageMod.PageMod({
onAttach: function(worker) {
// Post a message directly to the content script
worker.postMessage("any thing you want to respond");
// Depending on the message, respond with different data
worker.port.on('getFact', function() {
worker.postMessage("any thing you want to respond");
});
worker.port.on('getEnabled', function() {
worker.postMessage("any thing you want to respond");
});
}
});
--
Here's the content script responding to the add-on script:
// Get data from the addon script
self.on('message', function(msg) {
// Do something depending on the message passed
});
--
Last, the content script can communicate to the add-on script like this:
self.port.emit("message to send to add-on script")
The above code will trigger the worker.port.on code in the main.js.
I am writing a chrome extension which will enable transliteration for specific textboxes in facebook.
I have used the script tab to load https://www.google.com/jsapi in background.html
here is the code i have used in a content script
i tried to load using ajax and the generic way.
when i checked it said google undefined.
/*
$.ajax({
url: "https://www.google.com/jsapi",
dataType: "script",
});
*/
var script = document.createElement("script");
script.setAttribute('type','text/javascript');
script.setAttribute('src','https://www.google.com/jsapi?'+(new Date()).getTime());
document.body.appendChild(script);
$(document).ready(function()
{
alert(google)
if(window.location.href.indexOf('facebook.com'))
yes_it_is_facebook();
})
function yes_it_is_facebook()
{
// document.getElementsByName('xhpc_message_text')[0].id = 'facebook_tamil_writer_textarea';
// alert(document.getElementsByName('xhpc_message').length)
google.load("elements", "1", { packages: "transliteration" });
google.setOnLoadCallback(onLoad);
}
function onLoad()
{
var options = {
sourceLanguage:
google.elements.transliteration.LanguageCode.ENGLISH,
destinationLanguage:
[google.elements.transliteration.LanguageCode.HINDI],
shortcutKey: 'ctrl+g',
transliterationEnabled: true
};
var control = new google.elements.transliteration.TransliterationControl(options);
control.makeTransliteratable(['facebook_tamil_writer_textarea']);
}
and i have https://www.google.com/jsapi in manifest.json content script array.
"content_scripts": [
{
"matches": ["<all_urls>"],
"js": ["js/jquery-1.7.2.min.js", "js/myscript.js", "https://www.google.com/jsapi"]
}
],
it showed an error
Could not load javascript https://www.google.com/jsapi for content
script
here is my manifest.json
{
"name": "Facebook Tamil Writer",
"version": "1.0",
"description": "Facebook Tamil Writer",
"browser_action": {
"default_icon": "images/stick-man1.gif",
"popup":"popup.html"
},
"background_page": "background.html",
"content_scripts": [
{
"matches": ["<all_urls>"],
"js": ["js/jquery-1.7.2.min.js", "js/myscript.js", "https://www.google.com/jsapi"]
}
],
"permissions": [
"http://*/*",
"https://*/*",
"contextMenus",
"tabs"
]
}
in that i have added https://www.google.com/jsapi for your understanding and i have tested removing that also.
so how do i load that javascript into a document context . that is when ever a web page is loaded... here i specifically loading for facebook. still i have to correct the indexof condition because it is not giving the proper result but that is not the problem to this context of my question.
so please suggest me.
I don't seem to find any documentation regarding this but I think you cannot mention an http:// path in content_scripts option. A possible work around could be this:
$('head').append("<script type='text/javascript' src='http://google.com/jsapi'>");
Or loading it via ajax request as you have commented out in your code.
Secondly google.com/jsapi will have to be loaded before you can use it in your script. In your manifest you are loading your script first and then google.com/jsapi.
A friendly advice:
jQuery by default disallows caching by appending timestamp at the end of url. Since the script you are trying to load is not likely to change in short durations you can pass cache: false as an option for saving load time. Check out this page for more info.
Better yet you can bundle the script with your package so that there is no ajax request associated with your extension, that will add to the speed of your extension.
One of the biggest issues with google.load is that it cannot properly load resources after the page has fully loaded, because the API uses document.write to inject scripts/styles. To fix the issue, two methods have to be patched:
(function(g) {
var loader_d = g.loader.d,
setOnLoadCallback = g.setOnLoadCallback;
// Force not to use document.write when the document is loaded
g.loader.d = g.loader.writeLoadTag = function(a, b) {
loader_d(a, b, document.readyState === 'complete');
};
// Executes functions directly when page has loaded
g.setOnLoadCallback = function(a_listener, b) {
if (b || document.readyState !== 'complete') {
setOnLoadCallback(a_listener, b);
} else {
// When the API is not loaded yet, a TypeError with google.
// will be thrown. Not a ReferenceError, because google.* is defined
// Retry max *c* times.
var c = 5;
b = function() {
try {
a_listener();
} catch (e) {
if (e instanceof TypeError && (''+e).indexOf('google.')!=-1) {
if (c--) setTimeout(b, 2000);
}
}
};
b();
}
};
})(google);
Now, problem 2: Content Scripts run in an isolated environment: any properties of the global namespace, window, are not accessible. So, any injected APIs are not visible to your Content Script.
To fix this, see the following Stack Overflow answer: Building a Chrome Extension.
This might help with understanding Chrome's security policies
CSP
In there is says that if you attach a script tag to the page (not the popup or content script) it loads in the context of the page not your extension. And script from the extension can not talk to scripts of the page. If you look at the page script's you'll see it there but not under your extension scripts.
I discovered this while trying to inject the Google API script.
script = document.createElement('script');
script.src = "https://apis.google.com/js/client.js?onload=init";
(document.head||document.documentElement).appendChild(script);
The init function is defined in my content script. But the Goolge API script is loaded as a page script. So if I do this
var script = document.createElement('script');
script.innerText = "function init(){ alert('hi'); }";
(document.head||document.documentElement).appendChild(script);
script = document.createElement('script');
script.src = "https://apis.google.com/js/client.js?onload=init";
(document.head||document.documentElement).appendChild(script);
The injected init function is called and I see the alert 'hi'. Not sure if this helps but I figured I'd make a note of it for anyone else struggling with loading the Google apis. I'll update this answer if I figure out a way to actually get it loaded.