Cannot get reference to one page from another - javascript

I am working on a web app that needs to have two parts. The one is a controller and the other is a display. Something like Google Slides in presentation mode. The controller has a button to launch the display:
<script language="JavaScript">
function OpenMain()
{
var MainPage = window.open("TheUltraSignalLite.html");
TimerIMG = MainPage.document.getElementById("TimerIMG");
TimerIMG.src = "TM-Full-Blue.jpg";
}
</Script>
The call to window.open seems to return null. I have tried Chrome, Edge, Firefox, and Opera and they all have the result. These are all local files for now, but I might put in on a web server someday. I have seen some answers that want you to turn off security, but I cannot ask everyone who uses this app to turn off security. How do I get a valid reference to the display window?
Edit 1:
Yes, window.open from the local disk does cause a CORS restriction.
I tried this where both files are in the same AWS S3 Bucket, so the CORS should not be an issue. But I still get a null on the window.open. If I put a breakpoint on the first line, then everything worked. If I split the open and the rest of the code into two functions with two buttons, it works. So it looks like I have to find a way to run the open in an async way.
Edit 2
My solution to keep it simple was to put the window.open in the OnLoad event. This opens the child window and allows it to fully render and the value of MainPage is ready to use. (I changed the MainPage to a global variable.) I still have to run it from some type of web server, rather than loacl file, but that is not a big deal.

If you are not allowed to access the new window content, then the problem you are encountering is a basic security feature of web browsers. Citing mdn:
The returned reference can be used to access properties and methods of the new window as long as it complies with Same-origin policy security requirements
To read more about Same-origin policy
If your new window respects the Same-origin policy, then you can access the content of the new window with for example:
// Open index.html from the current origin
const newWindow = window.open('index.html')
const h1 = newWindow.document.querySelector('h1')
If you want to avoid asking users for pop-up permission, then you should probably use a link instead of a pop-up.

Related

JavaScript problems when launching site in iframe

I have set up an Articulate Storyline course (a Flash version accessed using the page "story.html" and an HTML5 version accessed using "story_html5.html"). It works fine when run directly, however, when I try to run everything in an iframe on the company server (linking to the course files on my personal server) I get JavaScript errors:
The course uses player.GetVar("HTML5spelaren") to access a variable called HTML5spelaren, which is located on the story_html5.html page itself. When running in an iframe I get a "Permission denied to access property 'HTML5spelaren'".
Finally the course uses the JavaScript var newWin=document.window.open("report.html", "Kursintyg"); to display a course completion certificate in a new window. When running in an iframe however this results in a "Permission denied to access property 'open'".
Is there a way to rewrite the JavaScripts to get around this? I need to be able to detect if the course is running in Flash or HTML5 mode (that's what I use the variable in story_html5.html for), as well as being able to use JavaScript to open a new page from within the iframe when clicking on a link.
Page structure:
https://dl.dropboxusercontent.com/u/11131031/pagestructure.png
/Andreas
There's a way for different domains to speak to one another via javascript. You can use postMessage: https://developer.mozilla.org/en-US/docs/Web/API/Window/postMessage
In your case, in story.html or story_html5.html could use something like:
parent.postMessage(HTML5spelaren, parent_domain);
and you add an event listener in the company page:
window.addEventListener("message", receiveMessage, false);
And in receiveMessage function you retrieve the data that you need. Something like:
function receiveMessage(event){
your_variable = event.data
}
Same logic can be probably be applied to your popup.
You can post from child to parent or from parent to child.
My guess is that content you're linking to in the iFrame is on a different server/domain. If so, the error is a security feature to stop cross-site scripting (XSS) attacks.
Consider putting both the parent iFrame and the articulate content (child) on the same server. This should eliminate the problem.

node-webkit running a function from external source

I am loading a remote page with an iframe in node-webkit app.
I would like to run a function from the node.js app from the webpage.
In app.js i have
var xxx = function(){ console.log('test'); }
and in http://www.test.com/index.html i have tried:
window.xxx();
xxx();
global.xxx();
But nothing seems to work.
How can i do that?
Many thanks
If I understand correctly, you want to access a function that's defined in the remote page that you are including as an iframe in your current page, right?
There are two separate issue here, I think.
First, you need to have a handle on the iframe, then access that window's environment through the 'contentWindow' property of the iframe dom element, i.e. given an iframe with id foo, that points to a page with a function named bar, you could do this:
x = document.getElementById('foo');
x.bar();
This works fine for me with a local page, but the other issue is that you might find some difficulty with running a remote page in an iframe and still having access. If it's a page under your control, that might work, but some pages don't like to be run in iframes, so then you have to sandbox to some extent, which may interfere with your ability to access it in this way. It's been a while since I played with sandboxing, so I'm not sure, but if it's a page you control, you should be able to set it up so it's not a problem.

Can I use indexeddb across subdomains?

I'm building a Chrome extension and using the db.js wrapper to utilize the indexeddb. The problem is, I've got several subdomains and I'd like to be able to share the information across them.
When I use the Chrome Dev tools to view Resources, all of the individual subdomains have their own copy of the schema I'm creating, and each has it's own data.
The only thing I knew to try was to set the document.domain but that didn't help. I wasn't surprised.
Documentation on indexeddb is very slim it seems. I keep finding the same 2 or 3 blog posts copied word for word in several different blogs and nothing specifies that this is possible or impossible.
You can't access the same database from multiple subdomains, the access scope is limited to html origin.
html_Origin = protocol + "://" + hostname + ":" + port + "/";
As #Xan mentioned, if you can use a common origin owned by the extension itself, rather than by the content pages, that sounds like it would be by far the easiest solution. If for whatever reason you can't do that (or for readers who got here wanting to know about regular page javascript or Greasemonkey-style userscripts, rather than extensions), the answer is:
Yes, though it's a slightly awkward and takes some work:
Since you're using a number of related subdomains, (rather than completely unrelated domains), there's a technique you can use in that situation. It can be applied to IndexedDB, localStorage, SharedWorker, BroadcastChannel, etc, all of which offer shared functionality between same-origin pages, but for some reason don't respect modifications to document.domain.
(1) Pick one "main" subdomain to for the data to belong to. i.e. if your subdomains are https://a.example.com, https://b.example.com, and https://c.example.com, you might choose to have your IndexedDB database stored under the https://a.example.com subdomain.
(2) Use it normally from all the the https://a.example.com pages.
(3) On https://b.example.com and https://c.example.com, use javascript to set document.domain = "example.com";. Then also create a hidden <iframe>, and navigate it to some page on the https://a.example.com domain (It doesn't matter what page, as long as you can insert a very little snippet of javascript on there. If you're creating the site, just make an empty page specifically for this purpose. If you're writing an extension or a userscript and so don't have any control over pages on the example.com server, just pick the most lightweight page you can find and insert your script into it. Some kind of "not found" page would probably be fine).
(4) The script on the hidden iframe page need only (a) set document.domain = "example.com";, and (b) notify the parent window when this is done. After that, the parent window can access the iframe window and all its objects without restriction! So the minimal iframe page is something like:
<!doctype html>
<html>
<head>
<script>
document.domain = "example.com";
window.parent.iframeReady(); // function defined & called on parent window
</script>
</head>
<body></body>
</html>
If writing a userscript, you might not want to add externally-accessible functions such as iframeReady() to your unsafeWindow, so instead a better way to notify the main window userscript might be to use a custom event:
window.parent.dispatchEvent(new CustomEvent("iframeReady"));
Which you'd detect by adding a listener for the custom "iframeReady" event to your main page's window.
(5) Once the hidden iframe has informed its parent window that it's ready, script in the parent window can just use iframe.contentWindow.indexedDB, iframe.contentWindow.localStorage, iframe.contentWindow.BroadcastChannel, iframe.contentWindow.SharedWorker instead of window.indexedDB, window.localStorage etc. ...and all these objects will be scoped to the https://a.example.com origin - so they'll have the this same shared origin for all of your pages!
The "awkward" part of this technique is mostly that you have to wait for the iframe to load before proceeding. So you can't just blithely initialize IndexedDB in your DOMContentLoaded handler, for example. Also you might want to add some error handling to detect if the hidden iframe fails to load correctly.
Obviously, you should also make sure the hidden iframe is not removed or navigated during the lifetime of your page... OTOH I don't know what the result of that would be, but very likely bad things would happen.
And, a caveat: setting/changing document.domain can be blocked using the Feature-Policy header, in which case this technique will not be usable as described.
However, there is a significantly more-complicated generalization of this technique, that can't be blocked by Feature-Policy, and that also allows entirely unrelated domains to share data, communications, and shared workers (i.e. not just subdomains off a common superdomain). #Xan alludes to it in point (2) of his answer:
The general idea is that, just as above, you create a hidden iframe to provide the correct origin for access; but instead of then just grabbing the iframe window's properties directly, you use script inside the iframe to do all of the work, and you communicate between the iframe and your main window only using postMessage() and addEventListener("message",...).
This works because postMessage() can be used even between different-origin windows. But it's also significantly more complicated because you have to pass everything through some kind of messaging infrastructure that you create between the iframe and the main window, rather than (for example) just using the IndexedDB API directly in your main window's code.
HTML-based storage (indexedDB, localStorage) in Chrome extensions behaves in a way that might not be expected, but it's perfectly natural.
In the background page, the domain is chrome-extension://yourextensionid/, and this is shared by all extension pages and is persistent.
In the content scripts though, you're sharing the HTML storage with the domain you're operating on. This makes life difficult if you want it to share/persist things. Note that sometimes this behavior is actually helpful.
The universal solution is to keep the DB in a background script, and communicate data/requests by means of Messaging API.
This was the usual solution for localStorage use until chrome.storage came along. But since you're using a database, you don't have a ready extension-friendly replacement.

transferring localstorage to another website [duplicate]

I am attempting to share data across subdomains using Safari. I would like to use an HTML5 database (specifically localStorage as my data is nothing but key-value pairs).
However, it seems as though data stored to example.com can not be accessed from sub.example.com (or vice versa). Is there any way to share a single database in this situation?
Update 2016
This library from Zendesk worked for me.
Sample:
Hub
// Config s.t. subdomains can get, but only the root domain can set and del
CrossStorageHub.init([
{origin: /\.example.com$/, allow: ['get']},
{origin: /:\/\/(www\.)?example.com$/, allow: ['get', 'set', 'del']}
]);
Note the $ for matching the end of the string. The regular expression in the above example will match origins such as valid.example.com, but not invalid.example.com.malicious.com.
Client
var storage = new CrossStorageClient('https://store.example.com/hub.html');
storage.onConnect().then(function() {
return storage.set('newKey', 'foobar');
}).then(function() {
return storage.get('existingKey', 'newKey');
}).then(function(res) {
console.log(res.length); // 2
}).catch(function(err) {
// Handle error
});
Check https://stackoverflow.com/a/39788742/5064633
There is simple way to use cross-domain anything, just create simple page that will be included as proxy iframe hosted on domain you try to access, send PostMessage to that iframe and inside iframe you do your LocalStorage database manipulation. Here is a link to article that do this with lcoalStorage. And here is demo that send message to different page in subdomain check the source code, it use iframe and PostMessage.
EDIT: New version of sysend.js library (used by above demo) use BroadcastChannel if browser support it, but still it require Iframe. Recent version also simplify using of Cross-Origin messages, you have html of the iframe in repo, that you can use (or you can use simple html file with single script tag with the lib) and in parent you just need to call one function sysend.proxy('https://example.com'); where example.com need to have proxy.html file (you can also use your own filename and different path).
Google Chrome blocks localStoage access from an iFrame in another domain by default,unless 3rd party cookie is enabled and so does Safari on iPhone...the only solution seems to be opening the parent domain on a different domain and then sending to to the Child via window.postMessage but looks ugly and shifty on phones...
Yes. This is how:
For sharing between subdomains of a given superdomain (e.g. foo.example.com vs bar.example.com vs example.com), there's a technique you can use in that situation. It can be applied to localStorage, IndexedDB, SharedWorker, BroadcastChannel, etc, all of which offer shared functionality between same-origin pages, but for some reason don't respect any modification to document.domain that would let them use the superdomain as their origin directly.
NOTE: This technique depends on setting document.domain to allow direct communication between iframes on different subdomains. That functionality has now been deprecated. (As of April 2021 it continues to work in all major browsers however. From Chrome v109 the feature will be disabled unless an Origin-Agent-Cluster: ?0 header is also sent.)
NOTE: Be aware that this technique removes the same-origin defences that block malicious script on a subdomain from affecting the main-domain window, or visa versa, potentially broadening the attack surface for XSS attacks. There are other security implications for shared hosting as well - see the MDN document.domain page for details.
(1) Pick one "main" domain to for the data to belong to: i.e. either https://foo.example.com or https://bar.example.com or https://example.com will hold your localStorage data. Let's say you pick https://example.com.
(2) Use localStorage normally for that chosen domain's pages.
(3) On all other https://*.example.com pages (the other domains), use JavaScript to set document.domain = "example.com"; (always the superdomain). Then also create a hidden <iframe>, and navigate it to some page on the chosen https://example.com domain (It doesn't matter what page, as long as you can insert a very little snippet of JavaScript on there. If you're creating the site, just make an empty page specifically for this purpose. If you're writing an extension or a Greasemonkey-style userscript and so don't have any control over pages on the example.com server, just pick the most lightweight page you can find and insert your script into it. Some kind of "not found" page would probably be fine).
(4) The script on the hidden iframe page need only (a) set document.domain = "example.com";, and (b) notify the parent window when this is done. After that, the parent window can access the iframe window and all its objects without restriction! So the minimal iframe page is something like:
<!doctype html>
<html>
<head>
<script>
document.domain = "example.com";
window.parent.iframeReady(); // function defined & called on parent window
</script>
</head>
<body></body>
</html>
If writing a userscript, you might not want to add externally-accessible functions such as iframeReady() to your unsafeWindow, so instead a better way to notify the main window userscript might be to use a custom event:
window.parent.dispatchEvent(new CustomEvent("iframeReady"));
Which you'd detect by adding a listener for the custom "iframeReady" event to your main page's window.
(NOTE: You need to set document.domain = example.com even if the iframe's domain is already example.com: Assigning a value to document.domain implicitly sets the origin's port to null, and both ports must match for the iframe and its parent to be considered same-origin. See the note here: https://developer.mozilla.org/en-US/docs/Web/Security/Same-origin_policy#Changing_origin)
(5) Once the hidden iframe has informed its parent window that it's ready, script in the parent window can just use iframe.contentWindow.localStorage, iframe.contentWindow.indexedDB, iframe.contentWindow.BroadcastChannel, iframe.contentWindow.SharedWorker instead of window.localStorage, window.indexedDB, etc. ...and all these objects will be scoped to the chosen https://example.com origin - so they'll have the this same shared origin for all of your pages!
The most awkward part of this technique is that you have to wait for the iframe to load before proceeding. So you can't just blithely start using localStorage in your DOMContentLoaded handler, for example. Also you might want to add some error handling to detect if the hidden iframe fails to load correctly.
Obviously, you should also make sure the hidden iframe is not removed or navigated during the lifetime of your page... OTOH I don't know what the result of that would be, but very likely bad things would happen.
And, a caveat: setting/changing document.domain can be blocked using the Feature-Policy header, in which case this technique will not be usable as described.
However, there is a significantly more-complicated generalization of this technique, that can't be blocked by Feature-Policy, and that also allows entirely unrelated domains to share data, communications, and shared workers (i.e. not just subdomains off a common superdomain). #jcubic already described it in their answer, namely:
The general idea is that, just as above, you create a hidden iframe to provide the correct origin for access; but instead of then just grabbing the iframe window's properties directly, you use script inside the iframe to do all of the work, and you communicate between the iframe and your main window only using postMessage() and addEventListener("message",...).
This works because postMessage() can be used even between different-origin Windows. But it's also significantly more complicated because you have to pass everything through some kind of messaging infrastructure that you create between the iframe and the main window, rather than just using the localStorage, IndexedDB, etc. APIs directly in your main window's code.

Firefox Error: Permission Denied to get Window.Document

I have a standard 3-frame layout; "fnav" on the left, "fheader" at the top and "fcontent" below the header. All files are located locally on the hard drive.
This is the JS function that is throwing the error:
function writeHeaderFrame() {
try {
var headerFrame = window.top.frames['fheader'];
var headerTable = document.getElementById('headerTable');
if (headerFrame && headerTable) {
headerFrame.document.body.style.backgroundColor = "Black";
var headerFrameBody = headerFrame.document.documentElement.childNodes[1];
headerFrameBody.innerHTML = headerTable.innerHTML;
} else if (headerTable) {
// there is a headerTable, but no headerFrame
headerTable.style.display = 'inline' // show the headerTable
}
} catch (e) { alert('from header.js, writeHeaderFrame(): ' + e.message); }
}
Clicking on a link in fnav (or initially loading the frameset) loads content into fcontent, then a JS file in fcontent loads the "header" frame... or it is supposed to, anyway. The Javascript runs fine initially, but whenever a link is clicked I get the following error:
Permission Denied To Get Window.document
I am unable to determine why. Any and all suggestions would be appreciated.
First off, please post the code being run when you click those links, and their html.
secondly, did you have a typo there? Window.document should be window.document, should it? (lowercase w)
Edit response to changes in OP question
Without the html it's a little hard to say, but If I were taking a stab in the dark, I'd say this line:
headerFrame.document.body.style.backgroundColor = "Black";
is causing the error. It looks like headerFrame is on a different domain and you don't, for security reasons, have permission to modify the contents of that frame. Of course, some of the following lines will also have the same issue.
Also see http://userscripts.org/topics/25029 and http://www.webdeveloper.com/forum/showthread.php?t=189515 for similar cases.
Edit 2
From Mozilla Development Center
Note: Firefox 3 alters the security for windows' documents so that only the domain from which it was located can access the document. While this may break some existing sites, it's a move made by both Firefox 3 and Internet Explorer 7, and results in improved security.
(see https://developer.mozilla.org/En/DOM/Window.document)
I would guess you're trying to manipulate the window or document from a different origin. HTML5 (and all modern browsers, even IE :D ) enforce (or attempt to enforce) what is called "The Same-Origin Policy". Basically JS from one origin cannot interact with the DOM of a document or window from a different origin.
What is an origin? At a basic level you could substitute domain for origin and almost be right, but the full set of rules are
You must have the same domain
The same port (eg. code on example.com:80 cannot reference the DOM of a page a example.com:8080)
The same protocol (eg. http://example.com is a different origin from https://example.com)
lastly, redirects also matter so (http://example.com -> http://example.com/?redirect=http://evil.com with the server responding with a 3xx redirect to http://evil.com will result in a different origin)
In all liklihood firefox has merely tightened up one area where they did not apply the same origin policy in the past.
Apparently, the user in question updated his installation without changing the following setting to "false", which allows local documents to have access to all other local documents.
pref("security.fileuri.strict_origin_policy", true);
Which explains why I was unable to duplicate the error on my machine.
Many thanks to all for your assistance.
Have you tried installing Firebug and figuring out which line is throwing the error? I'm guessing that since the question is tagged Firefox you are seeing this occur in it.
It'd be most helpful if you could post a template HTML page using this Javascript.
Is the script/frame pages all on the same domain? If not, this is expected. You can't access window.document from another window if they are not on the same domain.

Categories