Call an external function from content script - google-chrome

Suppose I have boo function inside an external JavaScript file, from domain http://localhost/file.js:
file.js:
function boo() {
return 1;
}
How can I call the boo function from content script in chrome extension?

You'll need to run a content script that creates and injects a <script> element that calls the page's boo() function.
Your content script should have something alone the lines of:
function runBoo() {
var myBoo = document.createElement('script');
myBoo.id = 'myBoo'; //helpful for removing the element later, not required to work
myBoo.innerText = 'boo();'
document.body.appendChild(myBoo); //if you plan on calling boo() multiple times, don't forget to delete the previously added myBoo elements
}
window.onload = function() {
//call runBoo() from content whenever you want to run the page's boo() function
runBoo();
}

According to this doc from Mozilla:
Content scripts can access the DOM of a page, of course, just like any
scripts that the page has loaded (page scripts). But content scripts
are insulated from page scripts:
content scripts don't see any JavaScript objects added to the page by
page scripts
if a page script has redefined the behavior of some DOM object, the
content script sees the original behavior.
Reasons:
it means that content scripts don't leak objects to web pages,
potentially opening up security holes.
it means that content scripts can create objects without worrying
about whether they might clash with objects added by page scripts.
Update
#Xan was right! thanks Xan.
if you need to interact with a function added by other page script, messaging between content script of your add-on and the page script is like this:
// main.js
var tabs = require("sdk/tabs");
var mod = require("sdk/page-mod");
var self = require("sdk/self");
var pageUrl = self.data.url("page.html")
var pageMod = mod.PageMod({
include: pageUrl,
contentScript: "console.log(unsafeWindow.foo);"
})
tabs.open(pageUrl);
where foo is a variable added by a page script.

Related

What is the purpose of HTML evaluation in Apps Script?

I am currently learning Apps Script fundamentals. I've encounter a method:
evaluate()
e.g.
const htmlServ = HtmlService.createTemplateFromFile("main");
const html = htmlServ.evaluate(); //
Within the reference it says:
It evaluates template and returns an HtmlOutput object.
My question: Does evaluate() has anything to do with security internally in Apps Script? Any relationship with HTML entities and its escaping? I want to clear this out.
Thanks.
evaluate() just injects data to your HTML templates. AFAIK it has nothing to do with the security.
The most common use case of this mechanism is when you have (nicely styled :)) HTML template that you want to have dynamic data. For example you have daily report that you want to display on a HTML page or send by email that has HTML body. But each day your Daily Total Revenue is different. You would do something like this:
<!-- Inside index.html file -->
<html>
<div class=my-class">
<p>Daily Total Revenue is: <?= dailyTotalRevenueVar ?>.</p>
</div>
</html>
// Inside Code.gs file
const template = HtmlService.createTemplateFromFile("index");
template.dailyTotalRevenueVar = 1000000; // Add a property to the template
const htmlOutput = template.evaluate(); // Replace var in HTML with actual value 1000000
Logger.log(htmlOutput.getContent()); // or return as a result in doGet()
You have many more options besides the simple value replacing, you can skip the entire HTML block if array has zero length, you can make a for loop to fill the select options...
Take a look at this page (toggle between .gs and .html view in code examples)
https://developers.google.com/apps-script/guides/html/templates
For more info about doGet and publishing GAS project as a web application visit this page:
https://developers.google.com/apps-script/guides/web

Inject HTML signature into Gmail from hosted page

I have the basic shell of a Chrome extension done and have come to the point where I am trying to inject an HTML signature into Gmail using code hosted on an unindexed page on my site. The reason I want to do this is to be able to include web fonts, something that for the life me I can't figure out why Gmail hasn't allowed you to do from their font library.
In any regard, as I said, I have a right-click context menu option ready to trigger a script from my js function page and the extension loads without errors. I need to figure out the best way to inject the HTML into the email and without losing any of the formatting that has been done on the page.
I have created the extension manifest, set the permissions on the context menu and created a function to call back to the js page that will inject the signature.
var contextMenus = {};
contextMenus.createSignature =
chrome.contextMenus.create(
{"title": "Inject Signature",
"contexts": ["editable"]},
function (){
if(chrome.runtime.lastError){
console.error(chrome.runtime.lastError.message);
}
}
);
chrome.contextMenus.onClicked.addListener(contextMenuHandler);
function contextMenuHandler(info, tab){
if(info.menuItemId===contextMenus.createSignature){
chrome.tabs.executeScript({
file: 'js/signature.js'
});
}
}
The end result is nothing enters the page and get massive errors related to cross-site because the domain is not the same obviously. This has obviously been solved as there are numerous signature extensions out there. I would probably use one of theirs but a) I want to build it on my own, b) they all want you to use their templates, none of them that I have seen will let you just use your own code.
So, any ideas?

Add JavaScript file to all sites I browse

I'm new to Chrome extension development, I need to make something sample with extension.
I Need to add JavaScript file to all sites I browse when the extension enabled, this is the code i want to add to all pages
<script type="text/javascript" src="web-retina-emulator.js"></script>
This file make pages look like when the website on retina display.
Is there easy way to make this happen?
You can use this basic structure to add JavaScript file to all sites when the extension enabled.
Method 1
If web-retina-emulator.js is an individual file which do not use global variables or functions of pages where it is injected it is suggested to use this method
Advantage:
It has access to certain sections of chrome API*
Draw Back
It can not use javascript variables and functions of pages where it is injected.
Demonstration
manifest.json
{
"name":"Custom Script",
"description":"http://stackoverflow.com/questions/14165629/add-javascript-file-to-all-sites-i-browse",
"version":"1",
"manifest_version":2,
"content_scripts":[{
"matches":["<all_urls>"],
"js":["web-retina-emulator.js"],
}
]
}
Method 2
If web-retina-emulator.js need some javascript methods or variables of pages, use this approach
Advantage:
It has access to javascript variables and methods of pages
Draw Back
It can not use chrome API*.
Demonstration
manifest.json
{
"name":"Custom Script",
"description":"http://stackoverflow.com/questions/14165629/add-javascript-file-to-all-sites-i-browse",
"version":"1",
"manifest_version":2,
"content_scripts":[{
"matches":["<all_urls>"],
"js":["myscript.js"],
}
]
}
myscript.js
var script = document.createElement('script'); // Create a Script Tag
script.src = chrome.extension.getURL("web-retina-emulator.js"); //Fetch the content script
script.onload = function () {
this.parentNode.removeChild(this); //Remove script after script executed
};
(document.head || document.documentElement).appendChild(script); //ADD script tag
//to head or Html element
Method 3
Inserting code into a page programmatically is useful when your JavaScript or CSS code shouldn't be injected into every single page that matches the pattern — for example, if you want a script to run only when the user clicks a browser action's icon.
Demonstration
background.html
chrome.tabs.executeScript(null,
{file:"web-retina-emulator.js"});
manifest.json
Ensure permissions are set in manifest file
"permissions": [
"tabs", "http://*/*"
],
References
Content Scripts
Execute Script API
Check out tampermonkey. It's the chrome equivalent to greasemonkey.

Cannot call functions to content scripts by clicking on image

This is what I have here:
"manifest.json"
{..."permissions": [
"https:/mywebsite.com/"],"content_scripts": [{
"matches" : ["http://*/*", "https://*/*"],
"js": ["js/jquery-1.7.2.min.js", "contentScript1.js", "contentScript2.js"],
"all_frames" : true,
"run_at": "document_end"
} ]}
"contentScript1.js"
$(document).ready(function() {
$('#someDiv').load('https://mywebsite.com/index.html');}
"contentScript2.js"
function showMessage()
{alert ('Hello World!');}
"index.html"
<img src="https://mywebsite.com/images/myimage.png">
What I m actually doing here is injecting a clickable picture to the code of the the page that I m visiting and I expect that by clicking the picture a "Hello World" message will be appeared. Despite the fact that the content scripts and the picture are loaded succesfully, when I click on the image the function is not called and I get the following error in the console:
Uncaught ReferenceError: showMessage is not defined
I suppose that it cannot find the function as it is looking for it in the website that I have injected the code and not in the content scripts. But why is that, I mean if I call the function within the content script when it is loaded and not by clicking the image, the message appears. Can anyone get me out of here?
You did not understand my solution to avoid conflicts does not work with your current code. Instead of using $.noConflict, you're wrapping your script injection function in a $().ready method.
You have to remove jQuery from the "js" part of the manifest:
"js": ["contentScript1.js"],
And contentScript1.js
function injectJs(srcFile) {
var scr = document.createElement('script');
scr.src = srcFile;
document.getElementsByTagName('head')[0].appendChild(scr);
}
injectJs(chrome.extension.getURL('js/jquery-min.js'));
injectJs(chrome.extension.getURL('js/yourscript.js'));
Don't forget to add js/yourscript.js to web_accessible_resources, so that it can be used:
"web_accessible_resources": [
"index3.html",
"js/jquery-min.js"
"js/yourscript.js"
]
In js/yourscript.js, wrap your function logic in an anonymous function in conjunction with $.noConflict. $.noConflict(true) is used to avoid conflicts with scripts in the page. It restores the original value of $ and jQuery.
(function(jQuery, $) {
// Here, you can do anything you want.
// jQuery and $ refer to the same jQuery object from `js/jquery-min.js`
})(jQuery, jQuery.noConflict(true));
After looking at your question again, I noticed that you're loading content through ajax: $('#someDiv').load(...). When the script is injected, it runs in the scope of the page. That's why your AJAX call fails: The request is blocked because of the Same origin policy.
Now, we can use a different approach to fix your code. Instead of moving the logic from Content script to the page (by an injected script), we modify the page index.html. The click event is not pre-set, but added in the content script. For example:
"index.html":
<img src="https://mywebsite.com/images/myimage.png">
"contentscript2.js":
$('#showMessage').click(showMessage);
I think I m gonna answer my own question:
The reason that this happening is because content scripts run in an isolated world
see: http://code.google.com/chrome/extensions/content_scripts.html#execution-environment
So, you simply cannot call functions, once you injected some html code, in content_scripts to perform some work in the current page of user.
What you have to do is to inject your scripts in the page as you do with html code.
So:
(1) add the files you want to inject in web resources in your manifest file
see: http://code.google.com/chrome/extensions/manifest.html#web_accessible_resources
"web_accessible_resources": [
"Script2.js",
"index.html",
"jquery-1.7.2.min.js"]
(2) in contentScript1.js (load this as a content_script)
//inject your javascript files to the head of the page
function injectJs(srcFile) {
var scr = document.createElement('script');
scr.type="text/javascript";
scr.src=srcFile;
document.getElementsByTagName('head')[0].appendChild(scr);
}
injectJs(chrome.extension.getURL('jquery-1.7.2.min.js'));
injectJs(chrome.extension.getURL('Script2.js'));
//inject your html by loading query and passing your html page
$(document).ready(function() {
$('#someDiv').load(chrome.extension.getURL('./index.html'));}
That's all!

JQUERY: Dynamically loading and updating iframe content on change() / keyup()

I've been working on a custom CMS in drupal for about two or three weeks now, and I keep running into this same problem. I'm trying to load a dynamically generated url (by extracting the node id of the target drupal page into $resultCut and appending it to the baseurl of the website). This iframe is embedded next to an instance of CKEditor, and the idea is to have the content in the iframe change when the fields in CKEditor are modified. I have the following Jquery:
<script type="text/javascript">
$(document).ready(function(){
baseurl = urlhere;
url = baseurl+"<?php echo $resultCut ?>"
$('#EmuFrame').attr('src', url);
var HTML = $('#EmuFrame').contents().find('body').html();
alert ( "LOADING COMPLETE" + HTML );
});
$('#edit-field-mobile-page-header-label-0-value').change(function () { // writes changes to the header text to emulaor
var curr = $(this).val();
$('#EmuFrame').contents().find("h1").text(curr);
});
$('#edit-body').keyup(function(e) { // writes changes to the body text to emulator
var curr = $(this).val();
currhead = $('#EmuFrame').contents().find("h1").html();
$('#EmuFrame').contents().find('#content').html("<h1>"+currhead+"</h1>"+curr);
});
where #EmuFrame is the id of an iframe, and the #edit-* tags are the ids of fields in CKEditor that I am monitoring for change. When the user types, the keyup() or change() events is supposed to grab the new html and swap it with the html in the iframe.
As of right now, the LOADING COMPLETE alert fires, but there is no html in the alert. I noticed that the content of the iframe loads AFTER the alert fires, however, which is what led me to believe that it's a problem with the order in which the events trigger.
Further, I had an alert in the callback function of keyup that returned the new html [ alert(curr) ] that was generated when a user started typing, and this alert returns html (although, it is being grabbed from CKEditor). However, the iframe does not reflect any changes. If I append [ alert (currhead) ] though, nothing is alerted at all.
It might be of interest to note that the source url is technically on a different domain than the parent. however, I used a workaround (i'm pretty sure it works, because I've previously gotten the whole html replacement thing working, and then somehow it broke). Also, neither Firebug nor Chrome's console report any XMLHttpRequest errors. Also, I keep getting this error: "Uncaught Syntax error, unrecognized expression: [#disabled]" and I'm not sure what it means, and whether its relevant to my problem as stated above.
That was a ridiculously long plea for help, so THANKS FOR READING, and thank you for any help!!
Your note about about the cross-domain iframe src is worrisome -- you shouldn't be able to access its contents with javascript. Nevertheless:
You have these two lines in quick succession:
$('#EmuFrame').attr('src', url);
var HTML = $('#EmuFrame').contents().find('body').html();
Try waiting for the iframe to load first:
$('#EmuFrame').load(function() {
var HTML = $('#EmuFrame').contents().find('body').html();
}