I'm developing a chrome extension and bumped into a big problem.
I'm using content scripts to inject my javascript code on a web site. The web site has an iframe.
I can change the source code of the iframe but don't seem to get any access to the iframe's contentWindow property. I need it to insert text at the current carret position.
So basically this code works perfectly in the context of the page:
$("#iframe1").contentWindow.document.execCommand("InsertHTML", false, 'test text');
But when I try it to run in the context of my chrome extension I get this error:
TypeError: Cannot read property 'document' of undefined
What's strange is that I can access the html of the iframe. So this code works perfectly from the chrome extension:
$("#iframe1").contents().find('div').html('test')
I tried putting "all_frames": true in the manifest file but no luck :(
To understand why your code does not work, I include a fragment of my previous answer:
Content scripts do not have any access to a page's global window object. For content scripts, the following applies:
The window variable does not refer to the page's global object. Instead, it refers to a new context, a "layer" over the page. The page's DOM is fully accessible. #execution-environment
Given a document consisting of <iframe id="frameName" src="http://domain/"></iframe>:
Access to the contents of a frame is restricted by the Same origin policy of the page; the permissions of your extension does not relax the policy.
frames[0] and frames['frameName'], (normally referring to the the frame's containing global window object) is undefined.
var iframe = document.getElementById('frameName');
iframe.contentDocument returns a document object of the containing frame, because content scripts have access to the DOM of a page. This property is null when the Same origin policy applies.
iframe.contentDocument.defaultView (refers to the window object associated with the document) is undefined.
iframe.contentWindow is undefined.
Solution for same-origin frames
In your case, either of the following will work:
// jQuery:
$("#iframe1").contents()[0].execCommand( ... );
// VanillaJS
document.getElementById("iframe1").contentDocument.execCommand( ... );
// "Unlock" contentWindow property by injecting code in context of page
var s = document.createElement('script');
s.textContent = 'document.getElementById("iframe1").contentWindow.document.execCommand( ... );';
document.head.appendChild(s);
Generic solution
The generic solution is using "all_frames": true in the manifest file, and use something like this:
if (window != top) {
parent.postMessage({fromExtension:true}, '*');
addEventListener('message', function(event) {
if (event.data && event.data.inserHTML) {
document.execCommand('insertHTML', false, event.data.insertHTML);
}
});
} else {
var test_html = 'test string';
// Explanation of injection at https://stackoverflow.com/a/9517879/938089 :
// Run code in the context of the page, so that the `contentWindow`
// property becomes accessible
var script = document.createElement('script');
script.textContent = '(' + function(s_html) {
addEventListener('message', function(event) {
if (event.data.fromExtension === true) {
var iframe = document.getElementById('iframe1');
if (iframe && (iframe.contentWindow === event.source)) {
// Window recognised, post message back
iframe.contentWindow.postMessage({insertHTML: s_html}, '*');
}
}
});
} + ')(' + JSON.stringify(test_html) + ');';
(document.head||document.documentElement).appendChild(script);
script.parentNode.removeChild(script);
}
This demo is for educational purposes only, do not use this demo in a real extension. Why? Because it uses postMessage to pass messages around. These events can also be generated by the client, which causes a security leak (XSS: arbitrary HTML injection).
The alternative to postMessage is Chrome's message API. For a demo, see this answer. You won't be able to compare the window objects though. What you can do is to rely the window.name property. The window.name property is automatically set to the value of the iframe's name attribute (just once, when the iframe is loaded).
Related
How can I communicate from a JavaScript code of a webpage to the main code of the add-on?
For example, something like this: If some element is clicked, in the corresponding event handler of the page script, which is the syntax that can be used to send some message to the main code?
Specifically, something like this, where the frame now must be replaced by a generic webpage. Is it possible?
Edit: I have tried the suggested code, but how I had said, the application returns this error:
console.error: sherlock:
Message: ReferenceError: document is not defined
Stack:
A coding exception was thrown in a Promise resolution callback.
See https://developer.mozilla.org/Mozilla/JavaScript_code_modules/Promise.jsm/Promise
Full message: ReferenceError: document is not defined
Previously my question, I had infact tried something similar without any effect.
Yes it is possible.
document.onload = function() {
var elementYouWant = document.getElementById("someID");
elementYouWant.onclick = console.log("Yup.. It was clicked..");
};
Reference.
The answer to the question is not as trivial as it may seem at first sight. I had also thought of a logic of the type described in the Pogrindis' response.
But here, in the case of interaction between the main script (i.e. that of the add-on) and generic script of arbitrary documents, the pattern is different.
In summary, the interaction takes place in this way:
It is required the API page-mod.
Through the property includes of the object PageMod you create a reference to the document, specifying the URI (wildcards are allowed).
Via the contentScriptFile property it is set the URL of the .js file that will act as a vehicle between the main code and that of the document.
Here's an example that refers to the specific needs of the context in which I am. We have:
an add-on code (the main code);
a Sidebar type html document (gui1.html) loaded in the file that I
use as a simple UI (I advise against the use of Frames, since it does
not support many typical HTML features - eg the click on a link,
etc.) containing a link to a second document (gui2.html) which will then
be loaded into the browser tab (I needed this trick because the
Sidebar does not support localStorage, while it is necessary for me);
a script in the document.
We must create an exchange of information between the two elements. In my case the exchange is unidirectional, from the page script to the main one.
Here's the code (main.js):
var pageMod = require("sdk/page-mod");
pageMod.PageMod({
include: "resource://path/to/document/gui2.html",
contentScriptFile: data.url("listen.js"),
onAttach: function(worker) {
worker.port.on("gotElement", function(elementContent) {
console.log(elementContent);
});
}
});
and in the html page script:
<script type="text/javascript">
[...]
SOWIN = (navigator.userAgent.toLowerCase().indexOf("win") > -1) ? "win" : "nix";
if (SOWIN == "win") {
window.postMessage("win","*");
} else {
window.postMessage("Linux","*");
}
[...]
</script>
Finally in the JS file (listen.js) to be attached to the page script:
window.addEventListener('message', function(event) {
self.port.emit("gotElement", event.data);
}, false);
This is just a small example, but logic I would say that it is clear. The uploaded content scripts are not accessible directly from main.js (i.e. the add-on), but you can create a bidirectional communication through the exchange of messages. To achieve this we have to put ourselves in listening the event Attach of the page-mod. Then, it is passed a worker object to the listener; that worker may be used by the add-on for the exchange of messages.
Here are the references to have an exhaustive picture:
Interacting with page scripts
Communicating with other scripts
page-mod
port
Communicating using "port"
postMessage
Communicating using postMessage
Problem: Uncaught TypeError: Cannot read property 'onUpdated' of undefined
Google Chrome extension
My code:
main.js
I have a function getCookie and setcookie
var _a = getCookie("a");
if (_a != "") {
/// do something
} else {
chrome.tabs.onUpdated.addListener(function(tabId , info , tab) {
if (info.status == "complete") {
var _a = document.getElementsByName('id_loaded_page')[0].value;
setCookie("_a", value, 1);
console.log("_a: " +_a);
}
});
}
You are calling chrome.tabs from a Content Script.
By design, content scripts are not allowed access to most of the Chrome APIs.
You need to make a background page to access chrome.tabs, but in your particular case you don't even need that wrapper: you're injecting at "document_end", which should mean that all static DOM is already loaded
And if the DOM node you are looking for is dynamically added, it may not exist when "complete" fires for a tab. You will need to listen to DOM mutations.
There is page with economic calendar.
Scenario:
I am loading page in browser. For example this: http://www.dukascopy.com/swiss/english/marketwatch/calendars/eccalendar/
Look through it. And if there is interesting data for me, I click button and save all html with loaded iframe-data for parsing.
The problem is that necessary data on this page loaded with iframe. I read here that chrome denies iframe access with js-injects. But I can easy access necessary tables with "inspect element" from right-click menu. Is it possible to access it without js-injects? Just like automatic "inspect DOM element" or inner HTML?
I solved this issue in pyside (python qt webkit interface) this way:
def print_content():
res = web.page().mainFrame().childFrames()
for i in res:
s = i.documentElement().toOuterXml()
print(s)
But now I want do it from chrome(chromium) extension. Is there similar functional in modern chrome(chromium)?
For example:
chrome.web.page().mainFrame().childFrames() etc...
UPD:
Tried recomendation. Corrected manifest and add to content script:
chrome.runtime.sendMessage({greeting: "hello"}, function(response) {
var res = document.querySelectorAll("iframe");
var len = res.length;
for (var i = 0; i < len; i++) {
//alert(myStringArray[i]);
console.log(res[i].contentDocument);
//Do something
}
//console.log(res);
Getting this error:
Error in event handler for (unknown): Error: Blocked a frame with origin "dukascopy.com"; from accessing a cross-origin frame. at chrome-extension://bgoddjjeokncninlaacmjamgkohmcecb/content.js:19:23 at extensions::messaging:323:11 at Function.target.(anonymous function) (extensions::SafeBuiltins:19:14) at Event.dispatchToListener (extensions::event_bindings:386:22) at Event.dispatch_ (extensions::event_bindings:371:27) at Event.dispatch (extensions::event_bindings:392:17) at dispatchOnDisconnect
You need to inject your content script into the inner frame to access it. This is perfectly possible, it's just that the outer document's script cannot access the iframe contents.
This question covers how to do this in case of manifest-based injection.
For programmatic injection, you can pass all_frames: true in InjectDetails object for chrome.tabs.executeScript.
I'm writing a chrome extension, mostly for learning purpose which will show me the Google analytics tracking id (UA-xxxxxx-x) if Google Analytics is installed.
I understand that I need to use sendRequest to a content script in the page to retrieve information, and have successfully implemented that functionality
chrome.extension.onRequest.addListener(function(request, sender, sendResponse) {
// analytics request check GA and send back
if(request.requestDetail == "analytics") {
console.log(window._gaq);
console.log(window._gaq._getAsyncTracker()._getAccount());
// unknown response, send back nothing!
} else {
sendResponse({});
}
});
The listener is receiving the request, Google Analytics is definately installed on the webpage I'm testing, window._gaq and window._gaq._getAsyncTracker()._getAccount() are both accessible via the console and returns what I expect.
Why is window._gaq undefined when accessing via the listener? How should I access it from a chrome extension?
I've got an idea that there is a security reason for this so have also tried modifying the manifest to include the below but still no luck
"content_security_policy": "script-src 'self' 'unsafe-eval'; object-src 'self'"
I suppose a secondary question would be:
Is what I'm trying to achieve possible in the manner I am using (I have managed to get a similar result using regex matching on the contents of the page, but would like to access the objects directly)
This has nothing to do with Content Security Policy. I think you're injecting a content script into web pages and trying to access variables in the web page. Because of content scripts are executed in an isolated world, you cannot directly do this. See https://developer.chrome.com/trunk/extensions/content_scripts.html#execution-environment for more detail.
To access variables in web pages (necessary in your case), you must inject a <script> tag in the page. Retrieve Google Analytics ID in that script, and communicate with the content script (https://developer.chrome.com/trunk/extensions/content_scripts.html#host-page-communication) to have the content script send it back to your extension.
Here's a simple example (Disclaimer: Just to illustrate how to do it. I didn't test it myself.):
window.addEventListener("message", function(event) {
if (event.source != window)
return;
if (event.data.type && (event.data.type == "FROM_PAGE")) {
chrome.extension.sendMessage(...); // Send analytics ID (in event.data.googleAnalyticsId) to your extension here
}, false);
var script = document.createElement('script');
script.appendChild(document.createTextNode('window.postMessage({googleAnalyticsId: (window._gaq ? window._gaq._getAsyncTracker()._getAccount() : ""), type: "FROM_PAGE"}, "*");');
document.appendChild(script);
Also note that chrome.extension.onRequest/sendRequest has been deprecated for a long time (https://developer.chrome.com/trunk/extensions/whats_new.html#20). You should use onMessage/sendMessage instead.
Though I'm not particularly proud of this one, it works; I don't think you can access the variables on the page without injecting another script into the page.
var find_ga_account = function(){
var scripts = document.querySelectorAll('script:not([src])'),
i = 0,
match,
ua = false;
for( ; i < scripts.length; i++){
match = scripts[i].textContent.match(/_setAccount['"]+,[\s]?['"]+(.*)['"]+/)
if(match && match.length > 0){
ua = match[1];
}
}
return ua;
}
From the facebook canvas, I need to be able to access an iframe window. Normally you could do this with window.frames, but FJBS doesn't seem to allow access to the window object.
Has anyone figured out how to access window objects?
you could try this. Let me know how it works.
var myIframe = document.getElementById('myIframeId');
// could retrieve window or document depending on the browser
// (if FBJS allows it!?)
var myIframeWin = myIframe.contentWindow || myIframe.contentDocument;
if( !myIframeWin.document ) { //we've found the document
myIframeWin = myIframeWin.getParentNode(); //FBJS version of parentNode
}
Browsers handle domain security on the principle of Same Origin Policy
And the laws of cross domain communication
Also you will find an interesting read on the creationg of read-write JS APIs on this blog post http://piecesofrakesh.blogspot.com/2007/11/how-to-build-readwrite-javascript-api.html