Call a page-specific function on an opened page - javascript

In a Firefox extension I am currently developing using the addon builder, I open a page in a new tab and I would like to call a JS function defined in a script on this page. For this I use this code:
var toOpenTab = require("tabs");
toOpenTab.on('open', function(tab){
toOpenTab.on('ready', function(tab){
tab.attach({
contentScript:
"function showFile(){PageSpecificFunction()};window.onload=showFile();"
});
});
});
I implement the window.onload event to be sure that the script containing the PageSpecificFunction() definition is loaded in the page, even though I don't think it is necessary (because I use the toOpenTab.onReady event).
The problem is: PageSpecificFunction() is not defined. I know the function is correctly defined and works fine because I can call it in the firebug console and it works perfectly.
Is there a way to make my extension call this function once my page is opened ?

You need to use the global unsafeWindow object:
unsafeWindow.PageSpecificFunction();
However this is a security risk

Related

Chrome Extension - access document/page variable from extension

I'm trying to develop extension that works only on specified pages - If page owner adds global variable into their code (for eg. ACCEPT_STATS = true;) I want to execute specified code.
I've already bind my function to the onload event, i've also found solution how to do that in Firefox:
var win = window.top.getBrowser().selectedBrowser.contentWindow;
if (typeof win.wrappedJSObject.ACCEPT_STATS !== 'undefined') {
// code to run if global variable present
}
but I couldn't make this work under Chrome. Is there any possibility to access document's global variable throw Chrome Extension code?
My extension's code is injected as a content-script.
Yes, including script into the page does run in an isolated context from the pages runtime script.
However, it is possible to work around the isolated worlds issue by pushing inline script into the runtime context via a script tag appended to the document's html. That inline script can then throw a custom event.
The included script in the isolated context can listen for that event and respond to it accordingly.
So code in your included script would look something like this:
// inject code into "the other side" to talk back to this side;
var scr = document.createElement('script');
//appending text to a function to convert it's src to string only works in Chrome
scr.textContent = '(' + function () {
var check = [do your custom code here];
var event = document.createEvent("CustomEvent");
event.initCustomEvent("MyCustomEvent", true, true, {"passback":check});
window.dispatchEvent(event); } + ')();'
//cram that sucker in
(document.head || document.documentElement).appendChild(scr);
//and then hide the evidence as much as possible.
scr.parentNode.removeChild(scr);
//now listen for the message
window.addEventListener("MyCustomEvent", function (e) {
var check = e.detail.passback;
// [do what you need to here].
});
The javascript running on the page is running in a different "isolated world" than the javascript that you inject using content scripts. Google Chrome keeps these two worlds separate for security reasons and therefore you can't just read window.XYZ on any window. More info on how isolated worlds work : http://www.youtube.com/watch?v=laLudeUmXHM
The correct way of implementing this is by communicating with the page is via window.postMessage API. Here're how I would go about it :
Inject a content script into each tab
Send a message to the tab via window.postMessage
If the page understands this message, it responds correctly (again via window.postMessage)
Content script executes the code that it needed to execute.
HTH

Can't connect API to Chrome extension

I am developing chrome extension. I want to connect some API to current tab after click on button in popup.html. I use this code in popup.js:
$('button').click(function() {
chrome.tabs.executeScript({
file: 'js/ymaps.js'
}, function() {});
});
In ymaps.js I use following code to connect API to current tab:
var script = document.createElement('script');
script.src = "http://api-maps.yandex.ru/2.0-stable/?load=package.standard&lang=ru-RU";
document.getElementsByTagName('head')[0].appendChild(script);
This API is needed to use Yandex Maps. So, after that code I create <div> where map should be placed:
$('body').append('<div id="ymapsbox"></div>');
And this simple code only loads map to created <div>:
ymaps.ready(init);//Waits DOM loaded and run function
var myMap;
function init() {
myMap = new ymaps.Map("ymapsbox", {
center: [55.76, 37.64],
zoom: 7
});
}
I think, everything is clear, and if you are still reading, I'll explain what is the problem.
When I click on button in my popup.html I get in Chrome's console Uncaught ReferenceError: ymaps is not defined. Seems like api library isn't connected. BUT! When I manually type in console ymaps - I get list of available methods, so library is connected. So why when I call ymaps-object from executed .js-file I get such an error?
UPD: I also tried to wrap ymaps.ready(init) in $(document).ready() function:
$(document).ready(function() {
ymaps.ready(init);
})
But error is still appearing.
Man below said that api library maybe isn't loaded yet. But this code produces error too.
setTimeout(function() {
ymaps.ready(init);
}, 1500);
I even tried to do such a way...
chrome.tabs.onUpdated.addListener(function(tabId, changeInfo) {
if (changeInfo.status == "complete") {
chrome.tabs.executeScript({
file: 'js/gmm/yandexmaps.js'
});
}
});
ymaps is not defined because you're trying to use it in the content script, while the library is loaded in the context of the page (via the <script> tag).
Usually, you can solve the problem by loading the library as a content script, e.g.
chrome.tabs.executeScript({
file: 'library.js'
}, function() {
chrome.tabs.executeScript({
file: 'yourscript.js'
});
});
However, this will not solve your problem, because your library loads more external scripts in <script> tags. Consequently, part of the library is only visible to scripts within the web page (and not to the content script, because of the separate script execution environments).
Solution 1: Intercept <script> tags and run them as a content script.
Get scriptTagContext.js from https://github.com/Rob--W/chrome-api/tree/master/scriptTagContext, and load it before your other content scripts. This module solves your problem by changing the execution environment of <script> (created within the content script) to the content script.
chrome.tabs.executeScript({
file: 'scriptTagContext.js'
}, function() {
chrome.tabs.executeScript({
file: 'js/ymaps.js'
});
});
See Rob--W/chrome-api/scriptTagContext/README.md for documentation.
See the first revision of this answer for the explanation of the concept behind the solution.
Solution 2: Run in the page's context
If you -somehow- do not want to use the previous solution, then there's another option to get the code to run. I strongly recommend against this method, because it might (and will) cause conflicts in other pages. Nevertheless, for completeness:
Run all code in the context of the page, by inserting the content scripts via <script> tags in the page (or at least, the parts of the extension that use the external library). This will only work if you do not use any of the Chrome extension APIs, because your scripts will effectively run with the limited privileges of the web page.
For example, the code from your question would be restructed as follows:
var script = document.createElement('script');
script.src = "http://api-maps.yandex.ru/2.0-stable/?load=package.standard&lang=ru-RU";
script.onload = function() {
var script = document.createElement('script');
script.textContent = '(' + function() {
// Runs in the context of your page
ymaps.ready(init);//Waits DOM loaded and run function
var myMap;
function init() {
myMap = new ymaps.Map("ymapsbox", {
center: [55.76, 37.64],
zoom: 7
});
}
} + ')()';
document.head.appendChild(script);
};
document.head.appendChild(script);
This is just one of the many ways to switch the execution context of your script to the page. Read Building a Chrome Extension - Inject code in a page using a Content script to learn more about the other possible options.
This is not a timing issue, rather an "execution environment"-related issue.
You inject the script into the web-page's JS context (inserting the script tag into head), but try to call ymaps from the content script's JS context. Yet, content-scripts "live" in an isolated world and have no access to the JS context of the web-page (take a look at the docs).
EDIT (thx to Rob's comment)
Usually, you are able to bundle a copy of the library and inject it as a content script as well. In your perticular case, this won't help either, since the library itself inserts script tags into to load dependencies.
Possible solutions:
Depending on your exact requirements, you could:
Instead of inserting the map into the web-page, you could display (and let the user interact with) it in a popup window or new tab. You will provide an HTML file to be loaded in this new window/tab containing the library (either referencing a bundled copy of the file or using a CDN after relaxing the default Content Security Policy - the former is the recommended way).
Modify the external library (i.e. to eliminate insertion of script tags). I would advise against it, since this method introduces additional maintainance "costs" (e.g. you need to repeat the process every time the library is updated).
Inject all code into the web-page's context.
Possible pitfall: Mess up the web-pages JS, e.g. overwriting already defined variables/functions.
Also, this method will become increasingly complex if you need to interact with chrome.* APIs (which will not be available to the web-page's JS context, so you'll need to device a proprietary message passing mechanism, e.g. using custom events).
Yet, if you only need to execute some simple initialization code, this is a viable alternative:
E.g.:
ymaps.js:
function initMap() {
ymaps.ready(init);//Waits DOM loaded and run function
var myMap;
function init() {
myMap = new ymaps.Map("ymapsbox", {
center: [55.76, 37.64],
zoom: 7
});
}
}
$('body').append('<div id="ymapsbox"></div>');
var script1 = document.createElement('script');
script1.src = 'http://api-maps.yandex.ru/2.0-stable/?load=package.standard&lang=ru-RU';
script1.addEventListener('load', function() {
var script2 = document.createElement('script');
var script2.textContent = '(' + initMap + ')()';
document.head.appendChild(script2);
});
document.head.appendChild(script1);
Rob already pointed to this great resource on the subject:
Building a Chrome Extension - Inject code in a page using a Content script
There is a much easier solutioin from Yandex itself.
// div-container of the map
<div id="YMapsID" style="width: 450px; height: 350px;"></div>
<script type="text/javascript">
var myMap;
function init (ymaps) {
myMap = new ymaps.Map("YMapsID", {
center: [55.87, 37.66],
zoom: 10
});
...
}
</script>
// Just after API is loaded the function init will be invoked
// On that moment the container will be ready for usage
<script src="https://...?load=package.full&lang=ru_RU&onload=init">
Update
To work this properly you must be sure that init has been ready to the moment of Yandex-scirpt is loaded. This is possible in the following ways.
You place init on the html page.
You initiate loading Yandex-script from the same script where init is placed.
You create a dispatcher on the html page which catches the ready events from both components.
And you also need to check that your container is created to the moment of Yandex-script is loaded.
Update 2
Sometimes it happens that init script is loaded later than Yandex-lib. In this case it is worth checking:
if(typeof ymaps !== 'undefined' && typeof ymaps.Map !== 'undefined') {
initMap();
}
Also I came across a problem with positioning of the map canvas, when it is shifted in respect to the container. This may happen, for example, when the container is in a fading modal window. In this case the best is to invoke a window resize event:
$('#modal').on('shown.bs.modal', function (e) {
window.dispatchEvent(new Event('resize'));
});

open url using javascript

I need a way to load a website - something like gBrowser.loadURI, window.location or window.open - but I need to execute some more code AFTER that website has been loaded (and parsed by the browser). The functions I've mentioned don't block execution of my code until the site is fully loaded, but only until it has started loading.
In case it matters: This code will not be part of my/a website, but will be a FireGestures script.
https://developer.mozilla.org/en/Code_snippets/Tabbed_browser#Manipulating_content_of_a_new_tab seems to be what you want. They suggest:
var newTabBrowser = gBrowser.getBrowserForTab(gBrowser.addTab("http://www.google.com/"));
newTabBrowser.addEventListener("load", function () {
// use newTabBrowser.contentDocument to manipulate DOM
// or do whatever you want on-load
}, true);
See also docs for tabbrowser and browser.

Call JavaScript function on a page automatically with Chrome?

When I load a particular webpage, I'd like to call a Javascript function that exists within their page. I could use a bookmarklet with javascript:TheFunction();, but I'd like to know if there's an easier way to do it, either with a Chrome extension or otherwise.
With chrome, you can either install a grease monkey script directly or get the Blank Canvas script handler plugin (the latter of which I use).
Chrome extensions run in a sandbox so you cannot call a function directly from the webpage code how you want. you either have to use javascript:fuction(); and set document.location, or you can create script elements on the page with a callback to your own extension. check out how this guy did it:
https://convore.com/kynetx/kbx-writing-durable-code/
i am refering to this post, and the one above and below it specifically
var anewscript = document.createElement("script");
anewscript.type = 'text/javascript';
anewscript.innerHTML=' callback_tmp= function (mydata){ ' +
' document.getElementById("someElement").onclick(mydata);' +
'}';
document.getElementsByTagName("head")[0].appendChild(anewscript);
An alternative option is to modify the javascript function to make it globally accessible from the [Chrome] debug console.
Change the function from for example
function foo(data)
to
foo = function(data)
then using the debug console, call the method with the attributes required
data = {my: "data"}
foo(data)

Opener.Location.Reload(); displayed Permission denied error in java script

I had two domains for ex. domain1 and domain2, I am opening domain2/index.aspx page as popup from domain1/default.aspx page. While closing domain2 page i need to reload the domain1 page, i had given the javascript code as "Opener.Location.Reload();". I am getting Permission denied javascript error. Any ideas about this issue.
I found that setting a parentUrl variable in the popup window (gotten from a query string)
and then using :
window.opener.location.href = parentUrl;
works.
I don't know why, I think it's magic, but it works (tested on IE, chrome and Firefox).
You cannot read the value of window.opener.location.href, but you can set it to whatever url you want. I use this oddity to do the refresh.
Hope it helps
Certain properties and actions are specifically blocked in cross-domain scenarios. What you might be able to do is create a function on the parent that does the code you want, then call that function from the child.
Example:
// On the parent...
function DoTheRefresh()
{
location.reload();
}
Then, on the child:
opener.DoTheRefresh();
I have done this in the past, so I don't know for sure if it's still an option. I hope it works out for you :)
You can accomplish this by putting code in the parent window to detect when the child window has closed.
var win2;
function openWindow()
{
win2 = window.open('http://...','childwindow',...);
checkChild();
}
function checkChild() {
if (win2.closed) {
window.location.reload(true);
} else setTimeout("checkChild()",1);
}

Categories