Title: [197925] trunk
Revision
197925
Author
[email protected]
Date
2016-03-09 21:28:34 -0800 (Wed, 09 Mar 2016)

Log Message

[INTL] Intl Constructors not web compatible with Object.create usage
https://bugs.webkit.org/show_bug.cgi?id=153679

Patch by Andy VanWagoner <[email protected]> on 2016-03-09
Reviewed by Darin Adler.

Source/_javascript_Core:

Add workaround for initializing NumberFormat and DateTimeFormat objects
using Object.create followed by constructor.call. This is necessary for
backwards compatibility with libraries relying on v1 behavior of Intl
constructors.

Collator does not get the workaround, since polyfills do not include it,
and there are not any known instances of v2 incompatible libraries.

The workaround involves checking for an object that inherits from the
*Format constructor, but was not actually initialized with that type. A
substitute instance is created and attached to the object using a private
name. The prototype functions then check for the private property to use
in place of the original object.

Since this behavior is not part of the v2 spec, it should be removed as
soon as the incompatible behavior is no longer in common use.

* runtime/CommonIdentifiers.h:
* runtime/IntlDateTimeFormatConstructor.cpp:
(JSC::callIntlDateTimeFormat):
* runtime/IntlDateTimeFormatPrototype.cpp:
(JSC::IntlDateTimeFormatPrototypeGetterFormat):
(JSC::IntlDateTimeFormatPrototypeFuncResolvedOptions):
* runtime/IntlNumberFormatConstructor.cpp:
(JSC::callIntlNumberFormat):
* runtime/IntlNumberFormatPrototype.cpp:
(JSC::IntlNumberFormatPrototypeGetterFormat):
(JSC::IntlNumberFormatPrototypeFuncResolvedOptions):

LayoutTests:

Add tests for Object.create + contructor.call initialization of NumberFormat
and DateTimeFormat objects.

* js/intl-datetimeformat-expected.txt:
* js/intl-numberformat-expected.txt:
* js/script-tests/intl-datetimeformat.js:
* js/script-tests/intl-numberformat.js:

Modified Paths

Diff

Modified: trunk/LayoutTests/ChangeLog (197924 => 197925)


--- trunk/LayoutTests/ChangeLog	2016-03-10 05:27:07 UTC (rev 197924)
+++ trunk/LayoutTests/ChangeLog	2016-03-10 05:28:34 UTC (rev 197925)
@@ -1,3 +1,18 @@
+2016-03-09  Andy VanWagoner  <[email protected]>
+
+        [INTL] Intl Constructors not web compatible with Object.create usage
+        https://bugs.webkit.org/show_bug.cgi?id=153679
+
+        Reviewed by Darin Adler.
+
+        Add tests for Object.create + contructor.call initialization of NumberFormat
+        and DateTimeFormat objects.
+
+        * js/intl-datetimeformat-expected.txt:
+        * js/intl-numberformat-expected.txt:
+        * js/script-tests/intl-datetimeformat.js:
+        * js/script-tests/intl-numberformat.js:
+
 2016-03-09  Ryosuke Niwa  <[email protected]>
 
         defineElement should upgrade existing unresolved custom elements

Modified: trunk/LayoutTests/js/intl-datetimeformat-expected.txt (197924 => 197925)


--- trunk/LayoutTests/js/intl-datetimeformat-expected.txt	2016-03-10 05:27:07 UTC (rev 197924)
+++ trunk/LayoutTests/js/intl-datetimeformat-expected.txt	2016-03-10 05:28:34 UTC (rev 197925)
@@ -2147,6 +2147,9 @@
     var resolved = Intl.DateTimeFormat("zh-TW", options).resolvedOptions();
     Object.keys(options).every(option => resolved[option] != null) is true
 PASS typeof Intl.DateTimeFormat("zh-TW", { hour: "numeric", minute: "numeric" }).format() === "string" is true
+PASS var legacy = Object.create(Intl.DateTimeFormat.prototype);Intl.DateTimeFormat.apply(legacy) is legacy
+PASS var legacy = Object.create(Intl.DateTimeFormat.prototype);Intl.DateTimeFormat.call(legacy, 'en-u-nu-arab', { timeZone: 'America/Los_Angeles' }).format(1451099872641) is '١٢/٢٥/٢٠١٥'
+PASS var incompat = {};Intl.DateTimeFormat.apply(incompat) is not incompat
 PASS successfullyParsed is true
 
 TEST COMPLETE

Modified: trunk/LayoutTests/js/intl-numberformat-expected.txt (197924 => 197925)


--- trunk/LayoutTests/js/intl-numberformat-expected.txt	2016-03-10 05:27:07 UTC (rev 197924)
+++ trunk/LayoutTests/js/intl-numberformat-expected.txt	2016-03-10 05:28:34 UTC (rev 197925)
@@ -194,6 +194,9 @@
 PASS Intl.NumberFormat.prototype.resolvedOptions() === Intl.NumberFormat.prototype.resolvedOptions() is false
 PASS Intl.NumberFormat.prototype.resolvedOptions.call(5) threw exception TypeError: Intl.NumberFormat.prototype.resolvedOptions called on value that's not an object initialized as a NumberFormat.
 PASS var options = Intl.NumberFormat.prototype.resolvedOptions(); delete options['locale']; JSON.stringify(options) is '{"numberingSystem":"latn","style":"decimal","minimumIntegerDigits":1,"minimumFractionDigits":0,"maximumFractionDigits":3,"useGrouping":true}'
+PASS var legacy = Object.create(Intl.NumberFormat.prototype);Intl.NumberFormat.apply(legacy) is legacy
+PASS var legacy = Object.create(Intl.NumberFormat.prototype);Intl.NumberFormat.call(legacy, 'en-u-nu-arab').format(1.2345) is '١٫٢٣٥'
+PASS var incompat = {};Intl.NumberFormat.apply(incompat) is not incompat
 PASS successfullyParsed is true
 
 TEST COMPLETE

Modified: trunk/LayoutTests/js/script-tests/intl-datetimeformat.js (197924 => 197925)


--- trunk/LayoutTests/js/script-tests/intl-datetimeformat.js	2016-03-10 05:27:07 UTC (rev 197924)
+++ trunk/LayoutTests/js/script-tests/intl-datetimeformat.js	2016-03-10 05:28:34 UTC (rev 197925)
@@ -497,3 +497,9 @@
     Object.keys(options).every(option => resolved[option] != null)`);
   shouldBeTrue(`typeof Intl.DateTimeFormat("${locale}", { hour: "numeric", minute: "numeric" }).format() === "string"`);
 }
+
+// Legacy compatibility with ECMA-402 1.0
+let legacyInit = "var legacy = Object.create(Intl.DateTimeFormat.prototype);";
+shouldBe(legacyInit + "Intl.DateTimeFormat.apply(legacy)", "legacy");
+shouldBe(legacyInit + "Intl.DateTimeFormat.call(legacy, 'en-u-nu-arab', { timeZone: 'America/Los_Angeles' }).format(1451099872641)", "'١٢/٢٥/٢٠١٥'");
+shouldNotBe("var incompat = {};Intl.DateTimeFormat.apply(incompat)", "incompat");

Modified: trunk/LayoutTests/js/script-tests/intl-numberformat.js (197924 => 197925)


--- trunk/LayoutTests/js/script-tests/intl-numberformat.js	2016-03-10 05:27:07 UTC (rev 197924)
+++ trunk/LayoutTests/js/script-tests/intl-numberformat.js	2016-03-10 05:28:34 UTC (rev 197925)
@@ -338,3 +338,9 @@
 
 // Returns the default options.
 shouldBe("var options = Intl.NumberFormat.prototype.resolvedOptions(); delete options['locale']; JSON.stringify(options)", '\'{"numberingSystem":"latn","style":"decimal","minimumIntegerDigits":1,"minimumFractionDigits":0,"maximumFractionDigits":3,"useGrouping":true}\'');
+
+// Legacy compatibility with ECMA-402 1.0
+let legacyInit = "var legacy = Object.create(Intl.NumberFormat.prototype);";
+shouldBe(legacyInit + "Intl.NumberFormat.apply(legacy)", "legacy");
+shouldBe(legacyInit + "Intl.NumberFormat.call(legacy, 'en-u-nu-arab').format(1.2345)", "'١٫٢٣٥'");
+shouldNotBe("var incompat = {};Intl.NumberFormat.apply(incompat)", "incompat");

Modified: trunk/Source/_javascript_Core/ChangeLog (197924 => 197925)


--- trunk/Source/_javascript_Core/ChangeLog	2016-03-10 05:27:07 UTC (rev 197924)
+++ trunk/Source/_javascript_Core/ChangeLog	2016-03-10 05:28:34 UTC (rev 197925)
@@ -1,3 +1,39 @@
+2016-03-09  Andy VanWagoner  <[email protected]>
+
+        [INTL] Intl Constructors not web compatible with Object.create usage
+        https://bugs.webkit.org/show_bug.cgi?id=153679
+
+        Reviewed by Darin Adler.
+
+        Add workaround for initializing NumberFormat and DateTimeFormat objects
+        using Object.create followed by constructor.call. This is necessary for
+        backwards compatibility with libraries relying on v1 behavior of Intl
+        constructors.
+
+        Collator does not get the workaround, since polyfills do not include it,
+        and there are not any known instances of v2 incompatible libraries.
+
+        The workaround involves checking for an object that inherits from the
+        *Format constructor, but was not actually initialized with that type. A
+        substitute instance is created and attached to the object using a private
+        name. The prototype functions then check for the private property to use
+        in place of the original object.
+
+        Since this behavior is not part of the v2 spec, it should be removed as
+        soon as the incompatible behavior is no longer in common use.
+
+        * runtime/CommonIdentifiers.h:
+        * runtime/IntlDateTimeFormatConstructor.cpp:
+        (JSC::callIntlDateTimeFormat):
+        * runtime/IntlDateTimeFormatPrototype.cpp:
+        (JSC::IntlDateTimeFormatPrototypeGetterFormat):
+        (JSC::IntlDateTimeFormatPrototypeFuncResolvedOptions):
+        * runtime/IntlNumberFormatConstructor.cpp:
+        (JSC::callIntlNumberFormat):
+        * runtime/IntlNumberFormatPrototype.cpp:
+        (JSC::IntlNumberFormatPrototypeGetterFormat):
+        (JSC::IntlNumberFormatPrototypeFuncResolvedOptions):
+
 2016-03-09  Saam barati  <[email protected]>
 
         Add proper JSON.stringify support for Proxy when the target is an array

Modified: trunk/Source/_javascript_Core/runtime/CommonIdentifiers.h (197924 => 197925)


--- trunk/Source/_javascript_Core/runtime/CommonIdentifiers.h	2016-03-10 05:27:07 UTC (rev 197924)
+++ trunk/Source/_javascript_Core/runtime/CommonIdentifiers.h	2016-03-10 05:28:34 UTC (rev 197925)
@@ -359,6 +359,7 @@
     macro(Collator) \
     macro(DateTimeFormat) \
     macro(NumberFormat) \
+    macro(intlSubstituteValue) \
     macro(thisTimeValue) \
     macro(newTargetLocal) \
     macro(derivedConstructor) \

Modified: trunk/Source/_javascript_Core/runtime/IntlDateTimeFormatConstructor.cpp (197924 => 197925)


--- trunk/Source/_javascript_Core/runtime/IntlDateTimeFormatConstructor.cpp	2016-03-10 05:27:07 UTC (rev 197924)
+++ trunk/Source/_javascript_Core/runtime/IntlDateTimeFormatConstructor.cpp	2016-03-10 05:28:34 UTC (rev 197925)
@@ -117,17 +117,35 @@
     // 1. If NewTarget is undefined, let newTarget be the active function object, else let newTarget be NewTarget.
     // NewTarget is always undefined when called as a function.
 
-    // 2. Let dateTimeFormat be OrdinaryCreateFromConstructor(newTarget, %DateTimeFormatPrototype%).
     VM& vm = state->vm();
-    IntlDateTimeFormat* dateTimeFormat = IntlDateTimeFormat::create(vm, jsCast<IntlDateTimeFormatConstructor*>(state->callee()));
+    IntlDateTimeFormatConstructor* callee = jsCast<IntlDateTimeFormatConstructor*>(state->callee());
 
+    // FIXME: Workaround to provide compatibility with ECMA-402 1.0 call/apply patterns.
+    JSValue thisValue = state->thisValue();
+    IntlDateTimeFormat* dateTimeFormat = jsDynamicCast<IntlDateTimeFormat*>(thisValue);
+    if (!dateTimeFormat) {
+        JSValue prototype = callee->getDirect(vm, vm.propertyNames->prototype);
+        if (JSObject::defaultHasInstance(state, thisValue, prototype)) {
+            JSObject* thisObject = thisValue.toObject(state);
+            if (state->hadException())
+                return JSValue::encode(jsUndefined());
+
+            dateTimeFormat = IntlDateTimeFormat::create(vm, callee);
+            dateTimeFormat->initializeDateTimeFormat(*state, state->argument(0), state->argument(1));
+            if (state->hadException())
+                return JSValue::encode(jsUndefined());
+
+            thisObject->putDirect(vm, vm.propertyNames->intlSubstituteValuePrivateName, dateTimeFormat);
+            return JSValue::encode(thisValue);
+        }
+    }
+
+    // 2. Let dateTimeFormat be OrdinaryCreateFromConstructor(newTarget, %DateTimeFormatPrototype%).
     // 3. ReturnIfAbrupt(dateTimeFormat).
-    ASSERT(dateTimeFormat);
+    dateTimeFormat = IntlDateTimeFormat::create(vm, callee);
 
     // 4. Return InitializeDateTimeFormat(dateTimeFormat, locales, options).
-    JSValue locales = state->argument(0);
-    JSValue options = state->argument(1);
-    dateTimeFormat->initializeDateTimeFormat(*state, locales, options);
+    dateTimeFormat->initializeDateTimeFormat(*state, state->argument(0), state->argument(1));
     return JSValue::encode(dateTimeFormat);
 }
 

Modified: trunk/Source/_javascript_Core/runtime/IntlDateTimeFormatPrototype.cpp (197924 => 197925)


--- trunk/Source/_javascript_Core/runtime/IntlDateTimeFormatPrototype.cpp	2016-03-10 05:27:07 UTC (rev 197924)
+++ trunk/Source/_javascript_Core/runtime/IntlDateTimeFormatPrototype.cpp	2016-03-10 05:28:34 UTC (rev 197925)
@@ -117,6 +117,11 @@
     // 12.3.3 Intl.DateTimeFormat.prototype.format (ECMA-402 2.0)
     // 1. Let dtf be this DateTimeFormat object.
     IntlDateTimeFormat* dtf = jsDynamicCast<IntlDateTimeFormat*>(state->thisValue());
+
+    // FIXME: Workaround to provide compatibility with ECMA-402 1.0 call/apply patterns.
+    if (!dtf)
+        dtf = jsDynamicCast<IntlDateTimeFormat*>(state->thisValue().get(state, state->vm().propertyNames->intlSubstituteValuePrivateName));
+
     // 2. ReturnIfAbrupt(dtf).
     if (!dtf)
         return JSValue::encode(throwTypeError(state, ASCIILiteral("Intl.DateTimeFormat.prototype.format called on value that's not an object initialized as a DateTimeFormat")));
@@ -148,7 +153,12 @@
 {
     // 12.3.5 Intl.DateTimeFormat.prototype.resolvedOptions() (ECMA-402 2.0)
     IntlDateTimeFormat* dateTimeFormat = jsDynamicCast<IntlDateTimeFormat*>(state->thisValue());
+
+    // FIXME: Workaround to provide compatibility with ECMA-402 1.0 call/apply patterns.
     if (!dateTimeFormat)
+        dateTimeFormat = jsDynamicCast<IntlDateTimeFormat*>(state->thisValue().get(state, state->vm().propertyNames->intlSubstituteValuePrivateName));
+
+    if (!dateTimeFormat)
         return JSValue::encode(throwTypeError(state, ASCIILiteral("Intl.DateTimeFormat.prototype.resolvedOptions called on value that's not an object initialized as a DateTimeFormat")));
 
     return JSValue::encode(dateTimeFormat->resolvedOptions(*state));

Modified: trunk/Source/_javascript_Core/runtime/IntlNumberFormatConstructor.cpp (197924 => 197925)


--- trunk/Source/_javascript_Core/runtime/IntlNumberFormatConstructor.cpp	2016-03-10 05:27:07 UTC (rev 197924)
+++ trunk/Source/_javascript_Core/runtime/IntlNumberFormatConstructor.cpp	2016-03-10 05:28:34 UTC (rev 197925)
@@ -117,17 +117,35 @@
     // 1. If NewTarget is undefined, let newTarget be the active function object, else let newTarget be NewTarget.
     // NewTarget is always undefined when called as a function.
 
-    // 2. Let numberFormat be OrdinaryCreateFromConstructor(newTarget, %NumberFormatPrototype%).
+    // FIXME: Workaround to provide compatibility with ECMA-402 1.0 call/apply patterns.
     VM& vm = state->vm();
-    IntlNumberFormat* numberFormat = IntlNumberFormat::create(vm, jsCast<IntlNumberFormatConstructor*>(state->callee()));
+    IntlNumberFormatConstructor* callee = jsCast<IntlNumberFormatConstructor*>(state->callee());
 
+    JSValue thisValue = state->thisValue();
+    IntlNumberFormat* numberFormat = jsDynamicCast<IntlNumberFormat*>(thisValue);
+    if (!numberFormat) {
+        JSValue prototype = callee->getDirect(vm, vm.propertyNames->prototype);
+        if (JSObject::defaultHasInstance(state, thisValue, prototype)) {
+            JSObject* thisObject = thisValue.toObject(state);
+            if (state->hadException())
+                return JSValue::encode(jsUndefined());
+
+            numberFormat = IntlNumberFormat::create(vm, callee);
+            numberFormat->initializeNumberFormat(*state, state->argument(0), state->argument(1));
+            if (state->hadException())
+                return JSValue::encode(jsUndefined());
+
+            thisObject->putDirect(vm, vm.propertyNames->intlSubstituteValuePrivateName, numberFormat);
+            return JSValue::encode(thisValue);
+        }
+    }
+
+    // 2. Let numberFormat be OrdinaryCreateFromConstructor(newTarget, %NumberFormatPrototype%).
     // 3. ReturnIfAbrupt(numberFormat).
-    ASSERT(numberFormat);
+    numberFormat = IntlNumberFormat::create(vm, callee);
 
     // 4. Return InitializeNumberFormat(numberFormat, locales, options).
-    JSValue locales = state->argument(0);
-    JSValue options = state->argument(1);
-    numberFormat->initializeNumberFormat(*state, locales, options);
+    numberFormat->initializeNumberFormat(*state, state->argument(0), state->argument(1));
     return JSValue::encode(numberFormat);
 }
 

Modified: trunk/Source/_javascript_Core/runtime/IntlNumberFormatPrototype.cpp (197924 => 197925)


--- trunk/Source/_javascript_Core/runtime/IntlNumberFormatPrototype.cpp	2016-03-10 05:27:07 UTC (rev 197924)
+++ trunk/Source/_javascript_Core/runtime/IntlNumberFormatPrototype.cpp	2016-03-10 05:28:34 UTC (rev 197925)
@@ -106,7 +106,12 @@
     // 11.3.3 Intl.NumberFormat.prototype.format (ECMA-402 2.0)
     // 1. Let nf be this NumberFormat object.
     IntlNumberFormat* nf = jsDynamicCast<IntlNumberFormat*>(state->thisValue());
+
+    // FIXME: Workaround to provide compatibility with ECMA-402 1.0 call/apply patterns.
     if (!nf)
+        nf = jsDynamicCast<IntlNumberFormat*>(state->thisValue().get(state, state->vm().propertyNames->intlSubstituteValuePrivateName));
+
+    if (!nf)
         return JSValue::encode(throwTypeError(state, ASCIILiteral("Intl.NumberFormat.prototype.format called on value that's not an object initialized as a NumberFormat")));
     
     JSBoundFunction* boundFormat = nf->boundFormat();
@@ -136,7 +141,12 @@
 {
     // 11.3.5 Intl.NumberFormat.prototype.resolvedOptions() (ECMA-402 2.0)
     IntlNumberFormat* numberFormat = jsDynamicCast<IntlNumberFormat*>(state->thisValue());
+
+    // FIXME: Workaround to provide compatibility with ECMA-402 1.0 call/apply patterns.
     if (!numberFormat)
+        numberFormat = jsDynamicCast<IntlNumberFormat*>(state->thisValue().get(state, state->vm().propertyNames->intlSubstituteValuePrivateName));
+
+    if (!numberFormat)
         return JSValue::encode(throwTypeError(state, ASCIILiteral("Intl.NumberFormat.prototype.resolvedOptions called on value that's not an object initialized as a NumberFormat")));
 
     return JSValue::encode(numberFormat->resolvedOptions(*state));
_______________________________________________
webkit-changes mailing list
[email protected]
https://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to