firefox sdk: redefine content-script function - javascript

I have firefox addon, which injects two content scripts to all pages.
var workers = [];
pageMod.PageMod({
include: "*",
contentScriptFile: [
self.data.url("content/autofill/lib_generic.js"),
self.data.url("content/autofill/lib.js"),
],
// add worker to the list
onAttach: function(worker)
{
workers.push(worker);
var filename = getDomainSpecificFilename(worker.contentURL);
worker.on("detach", function()
{
var index = workers.indexOf(worker);
if (index >= 0)
workers.splice(index, 1);
});
}
});
lib_generic.js contains one function named apply_forms(...) (its description is not important). The function is called from lib.js file. But this procedure doesn't work with several pages, so for each such page a I have a specific script - these file also contain only one function named apply_forms(...).
I have a function, which takes current domain as input and returns name of desired specific script or false if generic should be used.
What I need is - when neccessary - redefina generic apply_forms with specific apply_forms.
I've tried to use
tabs.activeTab.attach({
contentScriptFile: [ filename ]
});
worker.port.emit("apply_forms_loaded");
and in one of content scripts:
var apply_forms_loaded = false;
self.port.on("apply_forms_loaded", function() {
console.log("LOADED");
apply_forms_loaded = true;
});
and the whole procedure is started like this:
var timer;
timer = setInterval(function(){
if (apply_forms_loaded) {
clearInterval(timer);
start(); // apply_forms is called somewhere inside this call
}
}, 10);
Unfortunately it seems that tabs.activeTab.attach injects content scripts in different context so generic function is called allways.
Is there anything I can do to convince activeTab to add content scripts in same context or should I do it different way? (which one then)
Or could problem be in - I don't know - that content script is not fully injected when I send apply_forms_loaded message?
I've been trying to redefine function definition also for Chrome and I've made it work (url to SO question)
Thanks for advice.

That's correct, scripts added via pagemod are in a different JS sandbox from those added by tabs.attach. What you could do is inject the functions into the actual window using the new exportFunction() function - this way the function is in the real window and will always get overwritten. You can then call it from unsafeWindow..

Related

Load JS library from another JS library, and let the page use it

For my purposes, I want to be able to load a JavaScript library (file1.js) from the HTML page (file.html) and within file1.js, load file2.js. I then want file2.js's functions to be able to run in file.html without loading it directly from file.html.
Also, file1.js successfully loads from file.html, and file2.js successfully loads from file1.js. However, file2.js's functions can't be accessed in file.html. How would I do this?
This is my code:
file.html:
<script src="../path/to/file1.js">
functionFromFile2(); // This doesn't work!
</script>
file1.js:
var script = document.createElement("script");
script.src = "../path/to/file2.js";
script.onload = script.onreadystatechange = init1;
document.head.appendChild(script);
var init1 = function() {
functionFromFile2(); // This works!
}
file2.js
function functionFromFile2() {
// code...
}
How can I get functionFromFile2() working in file.html? Btw, this question is out of context and this setup IS REQUIRED - I can't combine the 2 files. I also don't want to reference file2.js from file.html, because the path of file2.js can change, and both file.html and file1.js use functions from file2.js.
I don't NEED dynamic imports, however, if I can use a dynamic import, then I can keep the single line in file.html, which is the goal, to keep it as just one line. I know that it's not needed in this example, but that's because it's an example and in my actual version it's required.
I require vanilla JavaScript. No libraries, please! I'm sure that there's some kind of library that will do what I want, but I want to keep my project %100 vanilla.
Seems you want javascript "includes" :) Which is probably not forseen. But you could come to the idea to fetch the script source by ajax. Write a tiny pre processor which loads other files, combine them whatever. At this moment its just text and then either add a new script object to the dom like you are thinking of or simply evaluate the final string. Which will also make all functions and variables availeable.
function GetText(url, fkt) {
var xhttp = new XMLHttpRequest();
xhttp.onreadystatechange = function() {
if (this.readyState === 4 && this.status === 200) {
return this.responseText;
}
}
xhttp.open("GET", url, false);
xhttp.send();
if (typeof fkt === 'undefined') {
return xhttp.responseText;
} else {
return fkt(xhttp.responseText);
}
}
this will get you any kind of file in return. You can also add a function to do something with the file inside the script;
and now we just do a
var src=GetText("../js/script2.js");
eval(src);
or
var dynamicURL ="./js/startrek.js"
var src=GetText(dynamicURL);
eval(src);
Note that the scope of the eval can be limited also to objects.
As you can see you can change the script location also variable.
See also this nice tutorial Dynamic add scripts
I think you need to use a custom event. this way you can let your code in the HTML know when your file was loaded in file1.
Link to example code
Example (timeout for simulating file1)
<script type="text/javascript">
// Pretending to be File1 (Aysnc Stuff)
(function() {
function init() {
console.log('The file loaded, creating an event to let the document know')
var fileReadyEvent = new CustomEvent('file2ready');
document.dispatchEvent(fileReadyEvent);
}
setTimeout(init, 2000);
})();
</script>
<script type="text/javascript">
(function() {
function doStuff() {
console.log('Im doing stuff');
}
document.addEventListener('file2ready', doStuff);
})();
</script>
Note: Custom events are not supported in old internet explorer so a polyfill would be needed to support ancient browsers. Also if you are using jquery it has/had custom events with trigger(), and on().

Searching for for javascript within page via Chrome extension

I wanted to build a simple Chrome extension that would search the HTML/DOM of the current active tab and print out in a popup the number of elements that contained javascript matching a certain source.
I read in the Chrome extension guides that the Content Scripts are unable to either interact with or even see other javascript on the page, leading me to believe this is not possible. Does anyone know for sure if creating this type of extension is feasible?
I did something similar not long ago; I needed to see elements' onclick and other attributes, which is not normally possible:
It's worth noting what happens with JavaScript objects that are shared by the page and the extension - for example, the window.onload event. Each isolated world sees its own version of the object.
There is a technique of injecting code into the page's context. Such code can reach the window's JS context and then pass it to your content script. In my case, I just added an extra attribute to nodes with JS attached.
// Fill inline handler copies
function fillClickHandlers(callback) {
var injected = function() {
// Note: This executes in another context!
// Note: This assumes jQuery in the other context!
$("[onclick]").each(function() {
this.dataset["onclick"] = this.attributes["onclick"].value;
});
$("[onsubmit]").each(function() {
this.dataset["onsubmit"] = this.attributes["onsubmit"].value;
});
$("[onload]").each(function() {
this.dataset["onload"] = this.attributes["onload"].value;
});
}
var s = document.createElement('script');
s.textContent = "(" + injected + ")();";
(document.head||document.documentElement).appendChild(s);
// Script is synchronously executed here
s.parentNode.removeChild(s);
callback();
}
// Erase inline handlers copies
function eraseClickHandlers(callback) {
$("[data-onclick], [data-onsubmit], [data-onload]").each(function() {
delete this.dataset.onclick;
delete this.dataset.onsubmit;
delete this.dataset.onload;
});
callback();
}
// Usage:
fillClickHandlers(function() {
doActualWork(function() {
eraseClickHandlers(doSomethingElse)
});
});
Note that for actual <script> tags, you can freely inspect src or textContent attribute.

Script not loaded directly

I'm loading a HTML partial through ajax. The partial is attached to the DOM by using innerHTML on an existing node.
The partial contains a few script tags at the bottom, something like:
<script src="/Scripts/Griffin.Editor.js" type="text/javascript"></script>
<script type="text/javascript">
marked.setOptions({
renderer: new marked.Renderer(),
gfm: true,
tables: true,
breaks: false,
pedantic: false,
sanitize: true,
smartLists: true,
smartypants: false
});
var textParser = {
parse: function (text) {
return marked(text);
}
}
var prismHighlighter = {
highlight: function (blockElements, inlineElements) {
blockElements.forEach(function(item) {
Prism.highlightElement(item);
});
}
};
var editor = new Griffin.Editor('editor', textParser);
editor.syntaxHighlighter = prismHighlighter;
editor.preview();
</script>
However, as the script tags are not executed, I traverse the loaded partial to identify all script tags. I then create new script nodes in the DOM and attach them to the HEAD.
Something like:
var scripts = viewElem.getElementsByTagName('script');
for (let i = 0; i < len; i++) {
var scriptTag = scripts[0];
let node = document.createElement('script');
if (scriptTag.src && scriptTag.src.length > 0) {
node.src = scriptTag.src;
node.type = scriptTag.type;
} else {
node.text = scriptTag.text;
node.type = scriptTag.type;
//had eval here before (instead of attaching the embedded script to the HEAD).
}
document.head.appendChild(node);
scriptTag.parentNode.remove(scriptTag);
}
From what I understand the browser should load the referenced scripts before invoking the embedded script. That is however not the case for me, because the JS console complains about not finding the object defined in the dependency script.
If I use a timer and eval the embedded script in it everything works. But that seems as a ugly workaround and I really want to understand the mechanics behind the load behavior (i.e. why the scripts are not executed when the partial is attached to the DOM and why the referenced scripts are not loaded directly when I add the nodes to the HEAD tag).
From what I've encountered, you can't have immediately executing JavaScript inside an Ajax response. The reason being you are trying to execute JavaScript inside another JavaScript function. So the browser has no idea which executing context to use with this scenario.
I would recommend using deferred execution like you mentioned. Except, you need to let the browser interpret the Ajax response first. For example:
$.get('url', function (html) {
// html = "<script>function myTest () { console.log('here'); }</script>"
$('#result').html(html);
// Now that the DOM has had time to parse the response we can do:
myTest();
});
Notice it is the Ajax callback invoking the response function not the response immediately executing itself. Hope this helps.
I found a really great article explaining in depth how scripts are loaded into the browser.
In essence you can't be sure of execution order per default when you include scripts dynamically. To be sure of the order you need to do one of the following
a. Use async=false if supported
b. Use readyState (for ie<10)
c. Use defer attribute.
Try to use the mentioned features in that order to be sure.
However, even if you do all that you will still get screwed if you mix embedded scripts (code in in the script tag) with referenced scripts (using src attribute).
The problem is that the embedded scripts will run directly, even if the references script tags are added before. To solve that you need to push the embedded scripts into a queue and hook the load event for all referenced scripts.
Once all referenced scripts have toggled the load even you are free to invoke the embedded scripts (either by added the script tags to an element or by eval their text property).
Source: http://blog.gauffin.org/2015/07/embedded-script-tags-in-content-loaded-through-ajax-and-execute-the-script-tags-dynamically/

How to replace a JavaScript function (which is loaded -among other code- from a remote file) with my own implementation using Greasemonkey?

There's a page eg. http://somesite.com that runs some javascript code located in path eg.http://somesite.com/code.js
In that file -among other code- there's a function (badFunction(x,y)).
I'd like to change the value of one of it's local variables, in order to make it equal with some another (local) variable (which is used inside in another function, also located in that remote javascript file).
So, based on these two answers 1 and 2 (i.e this userscript),
I've tried the following simple userscript, in order to replace the entire badFunction with my own implementation, but it doesn't work (i.e my badFunction is not injected in the loaded code.js - I've checked it via Firefox's Debugger).
What I'm trying to accomplish with the userscript, is, while the code.js is loaded, at the time the badFunction definition is reached, to inject my function implementation, and then, let the rest code.js continue it's loading normally.
What is wrong in my userscript? Or is my approach wrong altogether?
I'd prefer not give the actual URL of the forementioned remote javascript file as it's NSFW.
// ==UserScript==
// #name #document-start Example
// #version 1
// #namespace
// #include http://somesite.com/*
// #run-at document-start
// ==/UserScript==
var changed = 1; // script need to be edited with
window.addEventListener('beforescriptexecute', function(e) {
///for external script:
src = e.target.src;
if (src.search(/code\.js/) == "badFunction") {
changed--;
e.preventDefault();
e.stopPropagation();
append(badFunction);
};
///when done, remove the listener:
if(changed == 0) window.removeEventListener(e.type, arguments.callee, true);
}, true);
////// append with new block function:
function append(s) {
document.head.appendChild(document.createElement('script'))
.innerHTML = s.toString().replace(/^function.*{|}$/g, '');
}
function badFunction(x, y)
{
// my code
};
I do not have the source code of your site so I do not know how the badFunction is declared and called. However keep in mind that if the function is called inside a closure you need to inject the code somewhere inside that closure:
(function someClosure() {
function hello() {
// code
}
// hello is available
})();
// hello is not available
In the link they show how to modify the existing source code
window.addEventListener('beforescriptexecute', function(e) {
///for external script:
src = e.target.src;
if (src.search(/bad\.js/) != -1) {
changed++;
e.preventDefault();
e.stopPropagation();
append(NewScript1);
};
});
So basically you need to search for your function code and replace it. Remember you can search for newlines in regex using [\s\S]* link
After reading these two answers (which I upvoted of course):
How to overwrite a function using a userscript? and
Stop execution of Javascript function (client side) or tweak it
(and because badfunction is not global, and code.js fires immediately and is a file, not inline)
the solution I concluded to is:
First, grab a copy of the script and make the desired change to it.
Save this locally.
Then use Adblock Plus to block loading of that
remote script file.
Then use the script offered in the 2nd link to
add my modified local file to the page.
var scriptNode = document.createElement ("script");
scriptNode.setAttribute ("src", "Point to your modified JS file here.");
document.head.appendChild (scriptNode);

Call initialization function per script block

I made a little script that makes my JS initialization in Partial Pages a bit easier.
It simply searches for all data-onload attributes, and executes the function defined there on-load.
There is also some other functionality. So is the data-onload called automatically when that specific partial view is loaded through an AJAX call.
Anyway, the syntax looks like this:
<div class="some-partial-html-stuff">
<button>[...]</button>
</div>
<script data-onload="partialInit">
function partialInit()
{
// executes onload and on-ajax-load stuff for this Partial Page
$('.some-partial-html-stuff button').doSomething();
}
function otherFunctions()
{
// [...]
}
</script>
The only thing that I still would love to tackle is that right now I need to have a unique functionName for every partial page (otherwise the names will clash when they are both loaded).
So I have manageProfileInit(), editImageInit() etc.
Now is the OCD-devil in me wondering if there is some way to clean this up even further (without too many negative consequences). I would love to have the situation where I can have a simple clean functon init() in any scriptblocks, and have the same funcionality described above.
Of course in the current situation all the functions will override each other. But does anyone know a nice trick or workaround how this could work?
To summarize, I want to make a script that makes sure this will work on every Partial Page, without any clashes.
<div class="some-partial-html-stuff">
<button>[...]</button>
</div>
<script data-autoinit>
function init()
{
// this method is automatically called if the 'data-autoinit' is defined
// executes onload and on-ajax-load stuff for this Partial Page
$('.some-partial-html-stuff button').doSomething();
}
</script>
When I do stuff like this, I call them features. Tags look like this:
<div data-feature="featureName"></div>
Then we get all of the tags that have the data-feature tag and loop over them, creating an array of features the page is going to use:
var featureObjects = $('[data-feature]');
var features = [];
if ( !featureObjects.length ) return false;
for ( var i = 0, j=featureObjects.length; i<j; i++ ) {
var feature = $(featureObjects[i]).data('features');
if ($.inArray(feature, features) == -1){
if (feature !== ""){
features.push(feature);
}
}
};
Now you'll want to load the JS file asychronously and call it's init function once it's loaded:
for (var i=0, j=features.length; i<j; i++){
var feature = features[i];
$.ajax({
url: "path/to/js/" + feature + ".js",
dataType: "script",
async: false,
success: function () {
App.features[feature].init();
},
error: function () {
throw new Error("Could not load script " + script);
}
});
}
The actual modules look like this and attach themselves to App.features for later use:
App.features.featureName = (function(feature){
// INIT FUNCTION
feature.init = function(){
};
return feature;
}(App.features.featureName || {}));
Just remember to make sure App.features is an array before doing all of this, hopefully somewhere towards the top of your main.js file. I keep other functionality such as helpers and utilities in the app, so I usually kick it off with something like:
var App = {
utilities: {},
features: {},
helpers: {},
constants: {}
};
Now you can just tag DOM objects with a data-feature tag and functionality will be added automatically and as-needed, keeping a nice tie between specific JavaScript and specific DOM, but without the need of having to keep the JS inline next to the actual DOM. It also makes those "blurbs" re-usable should they need to be used elsewhere, which lowers maintenance overhead when working on your application.

Categories

Resources