Cross-window Javascript: is there a right way? - javascript

Problem
I'm trying to make a method that passes objects to a similar method in a popup window. I don't have control over the code in the target method, or the object passed in. The target method currently serialises the object, using JSON.stringify where possible, or instanceof Array.
The first problem with this is a bug in IE8 (see below). The second, and more fundamental, is that primitives are not the same across windows:
w = open("http://google.com")
w.Array == Array // returns false
Overriding on the popup any classes that might be passed in, and then restoring them after the call works, but it's really brittle and a maintenance headache.
Serialising the object into JSON and then parsing it in the context of the window hits the Firefox bug below.
I'm also a bit loathe to do a deep copy of the object or parse the JSON using new w.Object, etc. because it doesn't feel like it should be that complicated.
Can anyone suggest a sensible way to deal with this, or should I just accept that objects can't be passed verbatim between windows?
IE bug
JSON.stringify doesn't work across windows in IE8. If I pass an object to the popup, which attempts to serialise it, stringify returns undefined. To see this problem, open the script console in IE8 and try:
w = open("http://google.com")
JSON.stringify(Object()) // returns "{}"
w.JSON.stringify(w.Object()) // returns "{}"
w.JSON.stringify(Object()) // returns "undefined" on IE8
JSON.stringify(w.Object()) // returns "undefined" on IE8
JSON.stringify([1, w.Object()]) // returns "[1,null]" on IE8
I tried working around this by setting w.JSON = JSON, but as the last test shows, that breaks when you have objects from both windows.
Firefox bug
It seems that calling w.Object() to create an object in Firefox in fact calls window.Object(). The same bug is hit when calling w.JSON.parse or w.eval. To see this, open Firebug's console and try:
w = open("http://google.com")
new w.Object instanceof w.Object // returns true
w.Object() instanceof w.Object // returns false on Firefox 3.5
w.Object() instanceof Object // returns true on Firefox 3.5
w.Object.call(w) instanceof Object // returns true on Firefox 3.5
w.JSON.parse("{}") instanceof w.Object // returns false on Firefox 3.5
w.JSON.parse("{}") instanceof Object // returns true on Firefox 3.5
w.eval("[]") instanceof w.Array // returns false on Firefox 3.5
w.eval("[]") instanceof Array // returns true on Firefox 3.5
w.eval.call(w, "[]") instanceof Array // returns true on Firefox 3.5
The only workaround I can see is parsing the JSON string myself.

For what it's worth, this is what I'm doing for now:
Ensure jquery-json is loaded in the popup window
Stringify the object
Call w.$.evalJSON(str), which binds primitives correctly
Pass that result to the method on the popup
Alternatively (if jquery-json is not available), you could inject the following script into the target:
<script type="text/javascript">
function parseJSON(j) {
return JSON.parse(j)
}
</script>
as that will capture the popup's JSON, and not the caller's.
Any better solutions gladly appreciated.

If you are trying to do cross-domain scripting, seems like JSONP might be worth investigating.

I can't say I understand your problem fully, but there's an interesting window.name hack that's worth checking out: http://www.sitepen.com/blog/2008/07/22/windowname-transport/ (the blog post uses dojo but, of course, it can be done with pure JS too). It's safer than JSONP, easy to implement and it seems to work on all browsers.
Basically, it allows you to store any data, of any length in the window.name variable. What's awesome is that this variable doesn't get flushed/cleared on page change/refresh, so with some clever use of iframes you get simple and secure cross-domain transport :)

To see this, open Firebug's console
and try:
Error: Permission denied for <stackoverflow> to get property Window.Object from <google>.
On any line except first one: w = open("http://google.com")
Firefox 3.5.7
Think for a moment: You ar trying to open new window with arbitrary site and send to it data available to js. Seems too insecure to allow this.

Related

Getting methods on an instance

From the following code:
let n = 1234.567;
console.log(Object.getOwnPropertyNames(Number.prototype));
console.log(Object.getPrototypeOf(n));
console.log(Object.getPrototypeOf(5));
The first console.log prints the properties, but the second and third line just print {}. Why is this so? Is there a name to directly inspect the variable or number without having to refer to the base type, such as Number.prototype ?
The second and third line only appear to print empty objects due to how the Stack Snippet console operates - the actual browser console of Chrome, Firefox, Safari, or whatever browser you wish will be much more informative. When debugging things and logging objects, if you want the most informative interface, use the actual browser console.
Both of those lines really do point to Number.prototype:
console.log(
Object.getPrototypeOf(5) === Number.prototype
);

Mysterious failure of jQuery.each() and Underscore.each() on iOS

A brief summary for anyone landing here from Google: There is a bug in iOS8 (on 64-bit devices only) that intermittently causes a phantom "length" property to appear on objects that only have numeric properties. This causes functions such as $.each() and _.each() to incorrectly try to iterate your object as an array.
I have filed an issue report (really a workaround request) with jQuery (https://github.com/jquery/jquery/issues/2145), and there is a similar issue on the Underscore tracker (https://github.com/jashkenas/underscore/issues/2081).
Update: This is a confirmed webkit bug. A fix was comitted on 2015-03-27, but there is no indication as to which version of iOS will have the fix. See https://bugs.webkit.org/show_bug.cgi?id=142792. Currently iOS 8.0 - 8.3 are known to be affected.
Update 2: A workaround for the iOS bug can be found in jQuery 2.1.4+ and 1.11.3+ as well as Underscore 1.8.3+. If you're using any of these versions, then the library itself will behave properly. However, it's still up to you to ensure that your own code isn't affected.
This question can also be called: "How can an object without a length have a length?"
I'm having a twilight zone kind of issue with mobile Safari (seen on both iPhones and iPads running iOS 8). My code has a lot of intermittent failures using the "each" implementation of both jQuery ($.each()) and Underscore (_.each()).
After some investigation, I discovered that in all cases of failure, the each function was treating my object as an array. It would then try to iterate it like an array (obj[0], obj[1], etc.) and would fail.
Both jQuery and Underscore use the length property to determine if an argument is an object or an array/array-like collection. For example, Underscore uses this test:
if (length === +length) { ... this is an array
My objects had no length parameter, yet they were triggering the above if statements. I double validated that there was no length by:
Sending the value of obj.length to the server for logging prior to calling each() (confirming that length was undefined)
Calling delete obj.length prior to calling each() (this didn't change anything.)
I have finally been able to capture this behavior in the debugger with an iPhone attached to Safari on a Mac.
The following picture shows that $.isArrayLike thinks that length is 7.
However, a console trace shows that length is undefined, as expected:
At this point I believe this is a bug in iOS Safari, especially since it's intermittent. I'd love to hear from others who's seen this problem and perhaps found a way to counter it.
Update
I was asked to create a fiddle of this, but unfortunately I can't. There seems to be a timing issue (which may even differ between devices) and I can't reproduce it in a fiddle. This is the minimum set of code I was able to repro the problem with, and it requires an external .js file. With this code happens 100% of the time on my iPhone 6 running 8.1.2. If I change anything (e.g. making the JS inline, removing any of the unrelated JS code, etc), the problem goes away.
Here is the code:
index.html
<html>
<head>
<script src="//ajax.googleapis.com/ajax/libs/jquery/2.1.3/jquery.min.js"></script>
<script src="script.js"></script>
</head>
<body>
Should say 3:
<div id="res"></div>
<script>
function trigger_failure() {
var obj = { 1: '1', 2: '2', 3: '3' };
print_last(obj);
}
$(window).load(trigger_failure);
</script>
</body>
</html>
script.js
function init_menu()
{
var elemMenu = $('#menu');
elemMenu
.on('mouseenter', function() {})
.on('mouseleave', function() {});
elemMenu.find('.menu-btn').on('touchstart', function(ev) {});
$(document).on('touchstart', function(ev) { });
return;
}
function main_init()
{
$(document).ready(function() {
init_menu();
});
}
function print_last(obj)
{
var a = $($.parseHTML('<div></div>'));
var b = $($.parseHTML('<div></div>'));
b.append($.parseHTML('foo'));
$.each(obj, function(key, btnText) {
document.getElementById('res').innerHTML = ("adding " + btnText);
});
}
main_init();
This isn't an answer but rather an analysis of what's going on under the covers after much testing. I hope that, after reading this, someone on either safari mobile side or the JavaScript VM on iOS side can take a look and correct the issue.
We can confirm that the _.each() function is treating js objects {} as arrays [] because the safari browser returns the 'length' property of an object as an integer. BUT ONLY IN CERTAIN CASES.
If we use an object map where the keys are integers:
var obj = {
23:'some value',
24:'some value',
25:'some value'
}
obj.hasOwnProperty('length'); //...this comes out as 'false'
var length = obj.length; //...this returns 26!!!
Inspecting with the debugger on mobile Safari browser we clearly see that obj.length is "undefined". However stepping to next line:
var length = obj.length;
the length variable is clearly being assigned the value 26 which is an integer. The integer part is important because the bug in underscore.js occurs at these two lines in the underscore.js code:
var i, length = obj.length;
if (length === +length) { //... it treats an object as an array because
//... it has assigned the obj (which has no own
//... 'length' property) an actual length (integer)
However if we were to change the object in question just slightly and add a key-value pair where the key is a string (and is the last item in object) such as:
var obj = {
23:'some value',
24:'some value',
25:'some value',
'foo':'bar'
}
obj.hasOwnProperty('length'); //...this comes out as 'false'
var length = obj.length; //...this now returns 'undefined'
More interestingly, if we change the object again and a key-value pair such as:
var obj = {
23:'some value',
24:'some value',
25:'some value',
75:'bar'
}
obj.hasOwnProperty('length'); //...this comes out as 'false'
var length = obj.length; //...this now returns 76!
It appears that the bug (wherever it is happening: Safari/JavaScript VM) looks at the key of last item in the object and if it is an integer adds one (+1) to it and reports that as a length of the object...even though obj.hasOwnProperty('length') comes back as false.
This occurs on:
some iPads (but NOT ALL that we have) with iOS version 8.1.1, 8.1.2, 8.1.3
the iPads that it does occur on, it happens consistently...every time
only Safari browser on iOS
This does not occur on:
any iPhones we tried with iOS 8.1.3 (using both Safari and Chrome)
any iPads with iOS 7.x.x (using both Safari and Chrome)
chrome browser on iOS
any js fiddles we attempted to create using the above mentioned iPads that consistently created the error
Because we can't really prove it with a jsFiddle we did the next best thing and got a screen capture of it stepping through the debugger. We posted the video on youTube and it can be seen at this location:
https://www.youtube.com/watch?v=IR3ZzSK0zKU&feature=youtu.be
As stated above this is just an analysis of the problem in more detail. We are hoping someone with more understanding under the hood can comment on the situation.
One simple solution is to NOT USE _.each function (or the jQuery equivalent). We can confirm that using angular.js forEach function remedies this issue. However, we use underscore.js pretty extensively and _.each is used nearly everywhere we iterate through arrays/collections.
Update
This was confirmed as a bug and there is now a fix for this bug on WebKit as of 2015-03-27:
fix: http://trac.webkit.org/changeset/182058
original bug report: https://bugs.webkit.org/show_bug.cgi?id=142792
For anyone looking at this and using jQuery, just a heads' up that this has now been fixed in versions 2.1.4 and 1.11.3, which specifically only contain a hot-fix to the above issue:
http://blog.jquery.com/2015/04/28/jquery-1-11-3-and-2-1-4-released-ios-fail-safe-edition/
Upgrading to the latest version of lodash (3.10.0) fixed this problem for me.
Take note that there is a breaking change in this lodash version with _.first vs _.take.
For anyone who isn't familiar with lodash - it's a fork of underscore that is nowadays (imo) a better solution.
Really a big thanks to #OzSolomon for explaning, describing and figuring out this problem.
If I would be able to give a bounty to a question I would've done it.
I have worked a while with something that could be similar or the same problem. The company I work for have a game that uses KineticJS 4.3.2. Kinetic makes intensive use of Arrays when grouping graphic objects on a html5 canvas.
The problems that occurred was that push was missing on the array sometimes or that properties on the object that was stored in the array was missing. The problem occurred in Safari and when run from homescreen in iOS8. For some reason the problem did not occur when run in Chrome on iOS. The problem also did not occur with a debugger connected.
After a lot of testing and searching on the net we think this is a JIT optimisation bug in webkit on iOS. A colleague found the following links.
TypeError: Attempted to assign to readonly property. in Angularjs application on iOS8 Safari
https://github.com/angular/angular.js/issues/9128
http://tracker.zkoss.org/browse/ZK-2487
Currently we have made a workaround by removing dot-notation when accessing the array that did not work (.children was replaced with [ "children" ]).
I have not been able to create a jsFiddle.
The problem is the user code, not iOS8 webkit.
var obj = {
23: 'a',
24: 'b',
25: 'c'
}
The map keys above are integers, when strings should be used. It's not a valid map by the javascript standard so the behaviour is undefined, and Apple is choosing to interpret `obj' as an array of 26 elements (indexes 0 to 25 inclusive).
Use string keys and the length problem will go away:
var obj = {
'23': 'a',
'24': 'b',
'25': 'c'
}

Array-like objects and arrays not logged like arrays if the console is closed

See this jsbin where, to answer another question, I build an array-like object :
function myCollection() {
var items = [], r = {}
function myPush(value){
value += 'bar'
r[items.length]=value;
items.push(value)
}
Object.defineProperty(r, "splice", {value:[].splice});
Object.defineProperty(r, "slice", {value:[].slice});
Object.defineProperty(r, "length", {
get : function(){ return items.length}
});
Object.defineProperty(r, "myPush", {value:myPush});
return r;
}
var fooCollection = myCollection();
fooCollection.myPush('foo');
console.log(fooCollection); // logs ["foobar"]
fooCollection.myPush('Ba');
console.log(fooCollection); // logs ["foobar", "Babar"]
fooCollection.myPush('wzouing');
console.log(fooCollection.slice(-2)); //logs ["Babar", "wzouingbar"]
console.log(fooCollection[1]); // logs Babar
If you hit the Run with JS button on Chromium with the console open, you get this :
The very curious thing is that if you hit the button while the console is closed you get this (you see it after having reopened the console, of course) :
Is that a known bug ? A feature ? A grey zone ? Is there a workaround ?
*Note : sometimes, on Chrome/linux (not Chromium) I get something weirder : existing logs are changed when closing and reopening the console. They can go from the array like form to the folded form. *
I suppose this behaviour could be an intended one to save memory/performance, since resolving/printing runtime objects is a pretty expensive task.
If you really need an unwrapped info, I'd suggest using
console.dir( myStuff );
Or, alternatively, for arrays there is a new
console.table( myArray );
TIP: console.dir is is also the way to log the object contents in Internet Explorer, as opposed to it's default log() output of [object Object].
It's more like an issue at: https://bugs.webkit.org/show_bug.cgi?id=35801
Seems it's already fixed in webkit.
The very curious thing is that if you log an object while the console is closed, you don't get the expanded (array-like) view (after having reopened the console, of course).
Is that a known bug? A feature?
A feature, I'd guess. The console is optimized for performance while it is not opened, and therefore does not compute that expanded view of the object
Is there a workaround?
Have a look at How can I change the default behavior of console.log? (*Error console in safari, no add-on*). You'd have the typical lazyness problem anyway. Logging JSON strings should help.

Get full DOM stack as a string

I want to get the full DOM stack as a string. As an example, please open Chrome, press F12 and type "document.all". This object represents the full DOM. I want to convert this object as a string. In Chrome you're able to explore the object, expand sections and view their content in the web debugger console. Is there any possibility to convert document.all as a string? Or a similar solution with gives me the full DOM stack? I don't just want the innerHTML/outerHTML, I want the content of literally everything what's defined in the current DOM. Any ideas? Thanks in advance...
EDIT: Okay, I think my question is kind of confusing, sorry for that. To clarify this: I want to get every property of every object which is defined, including stuff like "document.location", "document.location.hash", "window.innerHeight", "document.body.innerHTML", and so on, by using JavaScript.
As far as I know, there's no way to get every property of every object.
In at least some browsers (Chrome, for instance), you can get most of the DOM with outerHTML on documentElement (the root element, e.g., html):
var html = document.documentElement.outerHTML;
You'd have to check whether your other target browsers do. At a minimum, they'll have innerHTML on body:
var bodyHTML = document.body.innerHTML;
But in terms of the other things, I don't believe there's any way to get every property of every object. For instance, you can find the properties on window like this:
var key;
for (key in window) {
// ...'key' is the property name, window[key] is the value...
}
...but that will only give you enumerable properties, not non-enumerable ones. And of course, you'd have to recurse into objects to get their properties (allowing for the fact that you can get to an object in more than one way — for example, window === window.window and there's also top, self, etc. to worry about; and similarly for document.all, document.forms, ...).
So unfortunately, while you can get a lot of the information you're talking about, I don't believe you can get it all.
You can implement this yourself using Object.getOwnPropertyNames, which will even include non-enumerable properties. I made a small example showing how this might be done.

How to disable specific JavaScript object

HI All,
Is there a way to disable specific JavaScript object, while all other JS objects will be available for execution? Something like a script in the top line which controls the rest of JS code on the page.
For example I want to prevent navigator.userAgent execution and leave navigator.appVersion available.
Or even more advanced way, the execution result must be defined by me. Let's say my browser is FF 3.6.8 but navigator.userAgent would reture IE 8.0
Mostly I'm interested in disabling or superseding objects results that return information about user Browser, Cookie, Resolution and OS
Thanks in Advance.
Jevgenijs
I'm not sure why you would want to do this, but you can override any properties and methods on the window object just by declaring a variable with the same name in the global scope:
var navigator = {
userAgent: "",
appVersion: navigator.appVersion,
// etc...
}
alert(window.navigator.userAgent);
//-> ""
#All: I added following to user.js file ( link text ) and it helped. Now neither my FF browser nor OS type never recognised by any website even by those showed through iframe like adsence.
user_pref("general.useragent.appName", "replacement string for appNameCode");
user_pref("general.appname.override", "replacement string for appName");
user_pref("general.useragent.override", "replacement string for userAgent");
user_pref("general.appversion.override", "replacement string for appVersion");
user_pref("general.platform.override", "replacement string for Platform");
Now it is left to override following screen.width, screen.heigh, screen.colorDepth but these objects seems unable to be overridden via user.js file.
So far only one idea:... most likely FireFox JS engine retrieves these values from OS, hence I need to know which file in Linux (Ubuntu) stores these values and temporary change it when I need. With any WIN OS it would be mush harder to do since it stores everything in damn registries. Am I right ?
Any more ideas ?
Regards

Categories

Resources