[INTL] Intl Constructors not web compatible with Object.create usage
authorcommit-queue@webkit.org <commit-queue@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Thu, 10 Mar 2016 05:28:34 +0000 (05:28 +0000)
committercommit-queue@webkit.org <commit-queue@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Thu, 10 Mar 2016 05:28:34 +0000 (05:28 +0000)
https://bugs.webkit.org/show_bug.cgi?id=153679

Patch by Andy VanWagoner <thetalecrafter@gmail.com> on 2016-03-09
Reviewed by Darin Adler.

Source/JavaScriptCore:

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:

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@197925 268f45cc-cd09-0410-ab3c-d52691b4dbfc

LayoutTests/ChangeLog
LayoutTests/js/intl-datetimeformat-expected.txt
LayoutTests/js/intl-numberformat-expected.txt
LayoutTests/js/script-tests/intl-datetimeformat.js
LayoutTests/js/script-tests/intl-numberformat.js
Source/JavaScriptCore/ChangeLog
Source/JavaScriptCore/runtime/CommonIdentifiers.h
Source/JavaScriptCore/runtime/IntlDateTimeFormatConstructor.cpp
Source/JavaScriptCore/runtime/IntlDateTimeFormatPrototype.cpp
Source/JavaScriptCore/runtime/IntlNumberFormatConstructor.cpp
Source/JavaScriptCore/runtime/IntlNumberFormatPrototype.cpp

index 23cfe5377833bbf3e6f859950cab51f80bdd3dd0..8ed3e0cb1ed59f1b5c7973c44ba1c4f61711dad9 100644 (file)
@@ -1,3 +1,18 @@
+2016-03-09  Andy VanWagoner  <thetalecrafter@gmail.com>
+
+        [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  <rniwa@webkit.org>
 
         defineElement should upgrade existing unresolved custom elements
index f58d4a9b2c7495d5aa84d30fc369ff5975938dea..64486ea796870ede13fb02542a650b44c989fbb6 100644 (file)
@@ -2147,6 +2147,9 @@ PASS
     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
index df8cb8ea31cca93666a6a036b2f68856d51adf4c..26bdc33006ca3443d2e48588a967f410d701dad0 100644 (file)
@@ -194,6 +194,9 @@ PASS Intl.NumberFormat.prototype.resolvedOptions() is an instance of Object
 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
index 927a930bee5270a4275917524de8874b36f5e998..fbf890a90a808cba76c682e18d93d51f484ab90c 100644 (file)
@@ -497,3 +497,9 @@ for (let locale of localesSample) {
     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");
index 812794459585f6a79d55fdb89c8c4cfcd7153394..76171158eb67eb611b0884a163f2ae969022455d 100644 (file)
@@ -338,3 +338,9 @@ shouldThrow("Intl.NumberFormat.prototype.resolvedOptions.call(5)", "'TypeError:
 
 // 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");
index 3fd8ffef58771686cd5b84de66b37484b14b72b1..964053d3456e8840abf36216613a96c2b7d3ca7b 100644 (file)
@@ -1,3 +1,39 @@
+2016-03-09  Andy VanWagoner  <thetalecrafter@gmail.com>
+
+        [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  <sbarati@apple.com>
 
         Add proper JSON.stringify support for Proxy when the target is an array
index 6c04359cc9ece9835797353e1e93eb86727cc046..2695e48898ef3bd9c086547b398cd55cef8bc3d5 100644 (file)
     macro(Collator) \
     macro(DateTimeFormat) \
     macro(NumberFormat) \
+    macro(intlSubstituteValue) \
     macro(thisTimeValue) \
     macro(newTargetLocal) \
     macro(derivedConstructor) \
index e917ee25e91b682b68e80ed32667deadb6586c41..5b0e5f070d1ab5b2e9a945598f9ff3824f6e1aca 100644 (file)
@@ -117,17 +117,35 @@ static EncodedJSValue JSC_HOST_CALL callIntlDateTimeFormat(ExecState* state)
     // 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);
 }
 
index d2c54d64cbbbcf1002f6e5261d17e586ccea9372..e683515ef54930d8155725e637a238430d87616a 100644 (file)
@@ -117,6 +117,11 @@ EncodedJSValue JSC_HOST_CALL IntlDateTimeFormatPrototypeGetterFormat(ExecState*
     // 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,6 +153,11 @@ EncodedJSValue JSC_HOST_CALL IntlDateTimeFormatPrototypeFuncResolvedOptions(Exec
 {
     // 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")));
 
index 26c6d46357eeb9786c79b891a61847801d9bed15..0a11078fe564489ae320ff304cf4e9cf59546178 100644 (file)
@@ -117,17 +117,35 @@ static EncodedJSValue JSC_HOST_CALL callIntlNumberFormat(ExecState* state)
     // 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);
 }
 
index 4bb2ee4922feb0f51870a910db12e52871bddd52..7cdbeb4a29ef1b91b29277a6a186e8b4d9ee6139 100644 (file)
@@ -106,6 +106,11 @@ EncodedJSValue JSC_HOST_CALL IntlNumberFormatPrototypeGetterFormat(ExecState* st
     // 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")));
     
@@ -136,6 +141,11 @@ EncodedJSValue JSC_HOST_CALL IntlNumberFormatPrototypeFuncResolvedOptions(ExecSt
 {
     // 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")));