Confused regarding postMessage between sites - javascript

I've uploaded an HTML containing the following section:
<script>
window.onload = function(){
window.addEventListener('message', receiver, false);
}
function receiver(event) {
if (event.origin == 'http://documentA.com') {
if (event.data == 'Hello B') {
//event.source.postMessage('Hello A, how are you?', event.origin);
alert("Recognized.");
} else {
//alert(event.data);
alert("Unrecognized!");
}
}
}
</script>
The document is accessible and I can view it in the browser. Then, I open a console window using FireBug and type in the following call (as described at Wikipedia).
window.postMessage("12345", "http://server:port/Index4.htm");
As a result I get undefined and frankly I have no clue if it's a good thing or a bad thing. Probably bad, especially since I don't get to see any alerts. What to do?

At first:
You have to post the message to the iframe. Try to use:
document.getElementById("yourIFrameID").contentWindow.postMessage()
I did the following in Opera Dragonfly, Firebug and IE9 and it works with no problems:
//Add iframe: <iframe id="teest" src="http://www.dict.cc/?s=annahme">
var frame = document.getElementById("teest")
Switch to iframe context:
//Add event listener
window.addEventListener("message", function(ev){ console.log(ev.data); }, false);
Back in the top-context, send some stuff:
frame.contentWindow.postMessage("foo", "http://www.dict.cc")
//Works. console: "foo"
frame.contentWindow.postMessage("foo", "http://www.dict.cc:80")
//Works. console: "foo"
frame.contentWindow.postMessage("foo", "http://www.dict.cc/?s=annahme")
//Works. console: "foo"
frame.contentWindow.postMessage("foo", "http://dict.cc/?s=annahme")
//Does NOT work. console: Nothing Happened
So, I see difficulties if you forget to add the right subdomain to the second parameter or if you visit a "https" page but have "http" in your string.
Try to remove all if's in your event handler and print out what you get before using if (or even better inspect it with Firebug or Dragonfly)

Related

Issues with Cross Document Messaging between IFrame & Parent

I have an application running inside an iframe on a "foreign" page (different domain etc.). To allow some basic communication between the iframe & the parent, I load some script of mine on the parent page and use postMessage to do some cross document messaging.
Most of the time this communication works as intended, but sometimes I see some errors reported to my error tracking tool and can't figure out why they happen.
Here's some exemplary code:
PluginOnParent.js
// ...
window.addEventListener('message', function(e) {
// Check message origin etc...
if (e.data.type === 'iFrameRequest') {
e.source.postMessage({
type: 'parentResponse',
responseData: someInterestingData
}, e.origin);
}
// ...
}, false);
// ...
AppInsideIFrame.js
// ...
var timeoutId;
try {
if (window.self === window.top) {
// We're not inside an IFrame, don't do anything...
return;
}
} catch (e) {
// Browsers can block access to window.top due to same origin policy.
// See http://stackoverflow.com/a/326076
// If this happens, we are inside an IFrame...
}
function messageHandler(e) {
if (e.data && (e.data.type === 'parentResponse')) {
window.clearTimeout(timeoutId);
window.removeEventListener('message', messageHandler);
// Do some stuff with the sent data
}
}
timeoutId = window.setTimeout(function() {
errorTracking.report('Communication with parent page failed');
window.removeEventListener('message', messageHandler);
}, 500);
window.addEventListener('message', messageHandler, false);
window.parent.postMessage({ type: 'iFrameRequest' }, '*');
// ...
What happens here, when the timeout hits and the error is reported?
Some more info & thoughts of mine:
I have no control over the parent page myself
It doesn't seem to be a general "configuration" issue (CORS etc.) since the error happens on the same page where it works most of the time
We don't support IE < 10 and other "legacy" browser versions at all, so those are no issue here
My error reporting tool reports a multitude of different browsers amongst which are the latest versions of them (FF 49, Chrome 43 on Android 5, Chrome 53 on Win and Android 6, Mobile Safari 10, ...)
Therefore it doesn't seem like it's an issue related to specific browsers or versions.
The timeout of 500 ms is just some magic number I chose which I thought would be completely safe...
The problem appears to be in your PluginOnParent.js, where you are sending your response. Instead of using "e.origin" (which upon inspection in the developer tools was returning "null") -- try using the literal '*', as it states in the following documentation on postMessage usage (in the description for targetOrigin):
https://developer.mozilla.org/en-US/docs/Web/API/Window/postMessage
Also, as a bonus, I just tested this across two different domains and it works as well. I placed Parent.html on one domains web server, and changed the iframe's src to be child.html on a completely different domain, and they communicated together just fine.
Parent.html
<html>
<head>
<script type="text/javascript">
function parentInitialize() {
window.addEventListener('message', function (e) {
// Check message origin etc...
if (e.data.type === 'iFrameRequest') {
var obj = {
type: 'parentResponse',
responseData: 'some response'
};
e.source.postMessage(obj, '*');
}
// ...
})
}
</script>
</head>
<body style="background-color: rgb(72, 222, 218);" onload="javascript: parentInitialize();">
<iframe src="child.html" style="width: 500px; height:350px;"></iframe>
</body>
</html>
Child.html
<html>
<head>
<script type="text/javascript">
function childInitialize() {
// ...
var timeoutId;
try {
if (window.self === window.top) {
// We're not inside an IFrame, don't do anything...
return;
}
} catch (e) {
// Browsers can block access to window.top due to same origin policy.
// See http://stackoverflow.com/a/326076
// If this happens, we are inside an IFrame...
}
function messageHandler(e) {
if (e.data && (e.data.type === 'parentResponse')) {
window.clearTimeout(timeoutId);
window.removeEventListener('message', messageHandler);
// Do some stuff with the sent data
var obj = document.getElementById("status");
obj.value = e.data.responseData;
}
}
timeoutId = window.setTimeout(function () {
var obj = document.getElementById("status");
obj.value = 'Communication with parent page failed';
window.removeEventListener('message', messageHandler);
}, 500);
window.addEventListener('message', messageHandler, false);
window.parent.postMessage({ type: 'iFrameRequest' }, '*');
// ...
}
</script>
</head>
<body style="background-color: rgb(0, 148, 255);" onload="javascript: childInitialize();">
<textarea type="text" style="width:400px; height:250px;" id="status" />
</body>
</html>
Hope that helps!
Most of the time this communication works as intended, but sometimes I
see some errors reported to my error tracking tool and can't figure
out why they happen.
What happens here, when the timeout hits and the error is reported?
I have no control over the parent page myself
Not certain what the function errorTracking.report does when called, though does not appear that an actual error relating to message event occurs?
The timeout of 500 ms is just some magic number I chose which I
thought would be completely safe...
With duration set at 500, setTimeout could be called before a message event fires at window.
timeoutId = window.setTimeout(function() {
errorTracking.report('Communication with parent page failed');
window.removeEventListener('message', messageHandler);
}, 500);
Adjust the duration of setTimeout to a greater duration.
Alternatively substitute onerror handler or window.addEventListener for setTimeout
Notes
When a syntax(?) error occurs in a script, loaded from a
different origin, the details of the syntax error are not reported to
prevent leaking information (see bug 363897). Instead the error
reported is simply "Script error." This behavior can be overriden in
some browsers using the crossorigin attribute on and having
the server send the appropriate CORS HTTP response headers. A
workaround is to isolate "Script error." and handle it knowing that
the error detail is only viewable in the browser console and not
accessible via JavaScript.
For example
// handle errors
onerror = function messageErrorHandlerAtAppInsideIFrame(e) {
console.error("Error at messageErrorIndex", e)
}
to handle any actual errors during communicating between different contexts, or origins.
Use postMessage at load event of iframe to communicate with message handlers at parent window.
http://plnkr.co/edit/M85MDHF1kPPwTE2E0UGt?p=preview

Chrome API's onBeforeRedirect not working properly?

I am developing a Google Chrome extension, and I need to detect redirects so I can perform a certain action (the action is irrelevant, this question simply pertains to the redirect). Thus far I have this code in my background.js:
chrome.webRequest.onBeforeRedirect.addListener(function (url, tabId) {
console.log("This is a redirect");
chrome.tabs.sendMessage(tabId, {"message": url}, function(response){});
}, {urls: ["<all_urls>"]});
However, neither the console.log or sendMessage method is getting called. I tried going to wikipedia.com, google.net, and several other sites that I know redirect the user. Why isn't the extension picking this up?
(And yes, I have put "webRequest" in my permissions under my manifest.json file.)
Thanks in advance, please let me know if you need any other code.
EDIT: Thanks to #ze it's working now, but now it's working a little too well. In other words, it's starting to fire now multiple times per page, and when I begin to type the url of a redirect site into the chrome search bar, it also gives me the redirect message. How do I only get it to fire once, when I actually push enter to navigate to the redirect site (and not while i'm still typing)? Here's the new code:
chrome.webRequest.onBeforeRedirect.addListener(function (details) {
if(details.frameId === 0){
alert("This is a redirect.");
// chrome.tabs.sendMessage(details.tabId, {"message": details.url}, function(response){});
}
}, {urls: ["<all_urls>"]});
From what I can see your callback for onBeforeRedirect has two arguments while here says that the callback should have one argument: details. Then you access url and tabId using details.url (or details.redirectUrl if you need the new url) and details.tabId.
Also, frames inside a tab may redirect as well so the event might fire more than once. If the code has to run only when the main frame redirects you should add a check like:
if (details.frameId == 0){
//Your code here
}
Also, I am not sure why you have {urls: ["<all_urls>"]} in the arguments of the addListener function. The only argument is the callback.
So, I would write something like:
chrome.webRequest.onBeforeRedirect.addListener(function (details) {
if(details.frameId == 0){
console.log("This is a redirect");
chrome.tabs.sendMessage(details.tabId, {"message": details.url}, function(response){});
}
});
Let me know if this worked out.

Communicating between Chrome DevTools and content script in extension

(I have already read this and it didn't work, and I've done a lot of searching and experimentation to no avail.)
I am writing a Chrome extension (BigConsole) with the goal of building a better Console tab for the Chrome developer tools. This means I would like to execute user-input code in the context of the page with access to the DOM and other variables on the page. To do this, the communication is structured as follows:
devtools creates a panel where the user writes code
When the user wants to execute code from the panel, the panel sends a message to a background script with the code
The background script receives the message/code from panel and passes it on to the content script which is injected into the page
The content script receives the message/code from the background script and injects a script element into the page which then runs the code
The result of the script on the page is then posted back to the content script with window.postMessage
The content script listens for the message/result from the page and passes it on to the background script
The background script receives the message/result from the content script and passes it on to the panel
The panel receives the message/result from the background script and inserts it into the log of results
Whew.
Right now, when the user tries to run the code, nothing happens. I put a bunch of console.log()s into the code but nothing appears in the console. My main question is, what have I done wrong here with the message passing that results in nothing happening? Alternatively, I would love to be told that I am making this way too complicated and there is a better way of doing things. Simplified code below...
panel.js:
window.onload = function() {
var port = chrome.runtime.connect({name: "Eval in context"});
// Add the eval'd response to the console when the background page sends it back
port.onMessage.addListener(function (msg) {
addToConsole(msg, false);
});
document.getElementById('run').addEventListener('click', function() {
var s = document.getElementById('console').value;
try {
// Ask the background page to ask the content script to inject a script
// into the DOM that can finally eval `s` in the right context.
port.postMessage(s);
// Outputting `s` to the log in the panel works here,
// but console.log() does nothing, and I can't observe any
// results of port.postMessage
}
catch(e) {}
});
};
background.js:
chrome.runtime.onConnect.addListener(function (port) {
// Listen for message from the panel and pass it on to the content
port.onMessage.addListener(function (message) {
// Request a tab for sending needed information
chrome.tabs.query({'active': true,'currentWindow': true}, function (tabs) {
// Send message to content script
if (tab) {
chrome.tabs.sendMessage(tabs[0].id, message);
}
});
});
// Post back to Devtools from content
chrome.runtime.onMessage.addListener(function (message, sender) {
port.postMessage(message);
});
});
content.js:
// Listen for the content to eval from the panel via the background page
chrome.runtime.onMessage.addListener(function (message, sender) {
executeScriptInPageContext(message);
});
function executeScriptInPageContext(m) { alert(m); }
As pointed out by Alex, here's a typo in your code which prevents it from working.
Drop your current code and use chrome.devtools.inspectedWindow.eval to directly run the code and parse the results. This simplifies your complicated logic to:
devtools creates a panel where the user writes code
devtools runs code
devtools handles result
PS. There is a way to manipulate the existing console, but I recommend against using it, unless it's for personal use. Two different ways to do this are shown in this answer.

Catch X-Frame-Options Error in javascript

Is there any way to catch an error when loading an iframe from another domain. Here is an example in jsfiddle. http://jsfiddle.net/2Udzu/ . I need to show a message if I receive an error.
Here is what I would like to do, but it doesn't work:
$('iframe')[0].onerror = function(e) {
alert('There was an error loading the iFrame');
}
Anyone have any ideas?
The onerror is applicable only for script errors. Frame content error checking must be done using any other method. Here's one example.
<script>
function chkFrame(fr) {
if (!fr.contentDocument.location) alert('Cross domain');
}
</script>
<iframe src="http://www.google.com/" onload="chkFrame(this)"></iframe>
Due to cross domain restriction, there's no way to detect whether a page is successfully loaded or if the page can't be loaded due to client errors (HTTP 4xx errors) and server errors (HTTP 5xx errors).
If both the parent site and the iframe-url is accessible by you, a way to know that the page is fully loaded (without "sameorigin" issues) is sending a message (postMessage) from the child to the parent like this;
Parent site (containing the iframe)
//Listen for message
window.addEventListener("message", function(event) {
if (event.data === "loading_success") {
//Yay
}
});
//Check whether message has come through or not
iframe_element.onload = function () {
//iframe loaded...
setTimeout(function() {
if (!iframeLoaded) {
//iframe loaded but no message from the site - URL not allowed
alert("Failure!");
}
}, 500);
};
Child site (URL from the iframe)
parent.postMessage("loading_success", "https://the_origin_site.url/");
You could get the_origin_site.url by using a server-side language like PHP if you want the possibility for multiple origins
The accepted answer only works if the domain you're trying to put in an iframe is the same as the one you're requesting from - this solution works for cross-domain where you have access to the scripts on both domains.
I am using following code to detect whether a x-frame-option error occured or another with jquery
$(iframe).load(function (e) {
try
{
// try access to check
console.log(this.contentWindow.document);
// Access possible ...
}
catch (e)
{
// Could not access. Read out error type
console.log(e);
var messageLC = e.message.toLowerCase();
if (messageLC.indexOf("x-frame-options") > -1 || messageLC.indexOf('blocked a frame with origin') > -1 || messageLC.indexOf('accessing a cross-origin') > -1)
{
// show Error Msg with cause of cross-origin access denied
}
else
{
// Shoe Error Msg with other cause
}
}
});

Emit message to add-on from content script onbeforeunload?

I have a content script which times how long a user views a page. To do this, I inject a content script into each page, start a timer and then emit a message back to the add-on when the onbeforeunload event is triggered.
The message never seems to get passed to the background script however.
Given that my main.js looks like this:
var pageMod = require('page-mod'),
self = require("self");
pageMod.PageMod({
include: "http://*",
contentScriptFile: [self.data.url('jquery.min.js'),
self.data.url('content.js')],
onAttach: function(worker) {
worker.port.on('pageView', function(request) {
console.log("Request received");
});
}
});
I can send a message to main.js using the following code no problem.
self.port.emit('pageView', { visitTime: time });
I run into a problem when I try to do it as the user leaves the page however. The message is never received when I do it like this:
$(window).bind('onbeforeunload', function(e) {
self.port.emit('pageView', { visitTime: time });
// This should prevent the user from seeing a dialog.
return undefined;
});
I've tried listening for beforeunload too, that doesn't work either. What could be the problem?
The window object that content scripts access in Firefox browser add-ons is a proxy object and can be a little temperamental. Using window.addEventListener will work.
window.addEventListener('beforeunload', function(e) {
# Do stuff then return undefined so no dialog pops up.
return undefined
});
The onbeforeUnload event is not synchronous, so the browser garbage collects the page before it is finished. Use a synchronous AJAX request:
function Data()
{
var client = new XMLHttpRequest();
client.open("GET", "/request", false); // third paramater indicates sync xhr
client.setRequestHeader("Content-Type", "text/plain;charset=UTF-8");
client.send({ visitTime: time });
client.onreadystatechange = emitter;
}
function emitter()
{
self.port.emit('pageView', { visitTime: time });
}
or return a string as an alternative.

Categories