Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Fixed (or at least returned to 3.4.1 state), the sublest regression e…

…ver,

related to Y.cloning Attribute driven objects with Node values (e.g. Anim),
on IE.

Fundamentally, Y.cloning nodes is broken in IE and has been since 3.3.0.

If an object holds multiple references to a Node instance, the first
reference encountered while cloning, will be a damaged clone. Subsequent
references encountered will not clone (and hence be OK).

After the Attribute refactor, the value stored in State was the first
reference encountered, resulting in permanent damage.

Before the refactor, a transient event based reference was the first reference
encountered, and hence was less likely to cause real-world problems.

* Added unit test cases to cover cloning Attribute based objects, with node values
* Tested on IE6, 7, 8, 9.
* Ran some core unit tests (App, Plugin, Widget, Attribute, Base, Node) through Yeti on IE8, Chrome, Safari, FF.
* Spot tested a couple of examples (Widget/App/Attribute).

Wanted to get this in before testfest tomorrow.

Fixes #2531929

Longer term fix for Y.clone(node) on IE is still required.
  • Loading branch information...
commit 0b4bad3bc665dbd4cfc6d3c2b65a85b7c6781767 1 parent 31689fe
@sdesai sdesai authored
Showing with 123 additions and 1 deletion.
  1. +9 −1 src/attribute/js/Attribute.js
  2. +114 −0 src/attribute/tests/attribute.html
View
10 src/attribute/js/Attribute.js
@@ -47,8 +47,16 @@
* @uses AttributeCore
* @uses AttributeEvents
* @uses AttributeExtras
- */
+ */
var Attribute = function() {
+
+ // Fix #2531929
+ // Complete hack, to make sure the first clone of a node value in IE doesn't doesn't hurt state - maintains 3.4.1 behavior.
+ // Too late in the release cycle to do anything about the core problem.
+ // The root issue is that cloning a Y.Node instance results in an object which barfs in IE, when you access it's properties (since 3.3.0).
+ this._ATTR_E_FACADE = null;
+ this._yuievt = null;
+
Y.AttributeCore.apply(this, arguments);
Y.AttributeEvents.apply(this, arguments);
Y.AttributeExtras.apply(this, arguments);
View
114 src/attribute/tests/attribute.html
@@ -700,6 +700,61 @@
Y.Assert.isTrue(valueChangeCalled, "after(valueChange) not called");
node.remove(true);
+ },
+
+ testAugmentedCloneWithNodeValue : function() {
+ function FooBar(userVals) {
+ Y.Attribute.call(this, null, userVals);
+ };
+
+ FooBar.ATTRS = {
+ node : {
+ value:null
+ }
+ };
+
+ // Straightup augment, no wrapper functions
+ Y.mix(FooBar, Y.Attribute, false, null, 1);
+
+ var n = Y.Node.create('<div id="foobar"></div>');
+
+ var o1 = new FooBar();
+ o1.set("node", n);
+
+ var o2 = Y.clone(o1);
+
+ Y.Assert.areEqual("foobar", o2.get("node").get("id"), "Expected foobar as the id of the cloned node");
+ Y.Assert.isObject(o2.get("node")._node.style, "Unable to access the cloned HTMLElement");
+
+ n.destroy();
+ },
+
+ testAugmentedCloneWithNodeValueConstructor : function() {
+ function FooBar(userVals) {
+ Y.Attribute.call(this, null, userVals);
+ };
+
+ FooBar.ATTRS = {
+ node : {
+ value:null
+ }
+ };
+
+ // Straightup augment, no wrapper functions
+ Y.mix(FooBar, Y.Attribute, false, null, 1);
+
+ var n = Y.Node.create('<div id="foobar2"></div>');
+
+ var o1 = new FooBar({
+ node: n
+ });
+
+ var o2 = Y.clone(o1);
+
+ Y.Assert.areEqual("foobar2", o2.get("node").get("id"), "Expected foobar2 as the id of the cloned node");
+ Y.Assert.isObject(o2.get("node")._node.style, "Unable to access the cloned HTMLElement");
+
+ n.destroy();
}
};
@@ -1348,7 +1403,66 @@
q.A.value = "modified value";
Y.Assert.areNotEqual(Y.dump(AttrHost.ATTRS), Y.dump(q));
+ },
+
+ testCloneWithNodeValue : function() {
+
+ function FooBar(userVals) {
+ FooBar.superclass.constructor.apply(this, arguments);
+ };
+
+ FooBar.NAME = "foobar";
+
+ FooBar.ATTRS = {
+ node : {
+ value:null
+ }
+ };
+
+ Y.extend(FooBar, Y.Base);
+
+ var n = Y.Node.create('<div id="foobar3"></div>');
+
+ var o1 = new FooBar();
+ o1.set("node", n);
+
+ var o2 = Y.clone(o1);
+
+ Y.Assert.areEqual("foobar3", o2.get("node").get("id"), "Expected foobar3 as the id of the cloned node");
+ Y.Assert.isObject(o2.get("node")._node.style, "Unable to access properties of the cloned HTMLElement");
+
+ n.destroy();
+ },
+
+ testCloneWithNodeValueConstructor : function() {
+
+ function FooBar(userVals) {
+ FooBar.superclass.constructor.apply(this, arguments);
+ };
+
+ FooBar.NAME = "foobar";
+
+ FooBar.ATTRS = {
+ node : {
+ value:null
+ }
+ };
+
+ Y.extend(FooBar, Y.Base);
+
+ var n = Y.Node.create('<div id="foobar4"></div>');
+
+ var o1 = new FooBar({
+ node: n
+ });
+ var o2 = Y.clone(o1);
+
+ Y.Assert.areEqual("foobar4", o2.get("node").get("id"), "Expected foobar4 as the id of the cloned node");
+ Y.Assert.isObject(o2.get("node")._node.style, "Unable to access properties of the cloned HTMLElement");
+
+ n.destroy();
}
+
};
basicTemplate = Y.merge(basicTemplate, sharedEventTests);

4 comments on commit 0b4bad3

@ericf
Owner

@sdesai I was talking to @rgrove the other week about this. It seems like we need to protect against cloning certain classes of objects, like HTMLElements, to avoid having objects which are broken.

@sdesai

Right. We do that for certain types already (for example we don't attempt to clone RegExp objects or Function objects, or recurse into HTMLElement properties, for obvious reasons).

In fact I'd say we may want to make clone "YUI" aware (at least in a certain mode), and not clone heavier objects like Anim, DD, Base-based objects etc. It's rarely (20%) the user's intention to do this. It usually happens inadvertently when one of these heavier objects hangs off something more basic [ like a multi-level object hash ] which they're cloning.

Also, bigger picture - we just want to do, and promise, less deep cloning.

@sdesai

Couple more clarifications which I forgot to add:

  1. In IE's case the root issue is Y.Object(HTMLElement) results in a "bad" object.

  2. The other clone cleanup required (based on my debugging) seems to be that if we have 2 references to the same object o1, in an object o, say o.foo = o1; o.bar = o1, when o is cloned, we end up replacing the first reference (foo) with a clone of o1, but bar remains the uncloned o1. I'm not sure if this is intentional. It seems like we'd want to use the cached clone, but that's just off the top of my head.

Please sign in to comment.
Something went wrong with that request. Please try again.