Implement String.codePointAt()
authorbenjamin@webkit.org <benjamin@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Wed, 22 Apr 2015 22:25:27 +0000 (22:25 +0000)
committerbenjamin@webkit.org <benjamin@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Wed, 22 Apr 2015 22:25:27 +0000 (22:25 +0000)
https://bugs.webkit.org/show_bug.cgi?id=143934

Reviewed by Darin Adler.

Source/JavaScriptCore:

This patch adds String.codePointAt() as defined by ES6.
I opted for a C++ implementation for now.

* runtime/StringPrototype.cpp:
(JSC::StringPrototype::finishCreation):
(JSC::codePointAt):
(JSC::stringProtoFuncCodePointAt):

LayoutTests:

* js/Object-getOwnPropertyNames-expected.txt:
* js/script-tests/string-code-point-at.js: Added.
(objectWithCustomToString.toString):
(objectThrowingOnToString.toString):
(objectCountingToString.toString):
(testLeadSurrogateOutOfBounds):
(testLeadSurrogateAsLastCharacter):
(testTrailSurrogateOutOfbounds):
(testAccessNullInString):
(testNormalCombinationOfSurrogates):
* js/string-code-point-at-expected.txt: Added.
* js/string-code-point-at.html: Added.

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

LayoutTests/ChangeLog
LayoutTests/js/Object-getOwnPropertyNames-expected.txt
LayoutTests/js/script-tests/Object-getOwnPropertyNames.js
LayoutTests/js/script-tests/string-code-point-at.js [new file with mode: 0644]
LayoutTests/js/string-code-point-at-expected.txt [new file with mode: 0644]
LayoutTests/js/string-code-point-at.html [new file with mode: 0644]
Source/JavaScriptCore/ChangeLog
Source/JavaScriptCore/runtime/StringPrototype.cpp

index 4317745..04a1d4d 100644 (file)
@@ -1,3 +1,23 @@
+2015-04-22  Benjamin Poulain  <benjamin@webkit.org>
+
+        Implement String.codePointAt()
+        https://bugs.webkit.org/show_bug.cgi?id=143934
+
+        Reviewed by Darin Adler.
+
+        * js/Object-getOwnPropertyNames-expected.txt:
+        * js/script-tests/string-code-point-at.js: Added.
+        (objectWithCustomToString.toString):
+        (objectThrowingOnToString.toString):
+        (objectCountingToString.toString):
+        (testLeadSurrogateOutOfBounds):
+        (testLeadSurrogateAsLastCharacter):
+        (testTrailSurrogateOutOfbounds):
+        (testAccessNullInString):
+        (testNormalCombinationOfSurrogates):
+        * js/string-code-point-at-expected.txt: Added.
+        * js/string-code-point-at.html: Added.
+
 2015-04-22  Brent Fulgham  <bfulgham@apple.com>
 
         Context menu doesn't account for selection semantics
index 0750c8a..d6f26b5 100644 (file)
@@ -48,7 +48,7 @@ PASS getSortedOwnPropertyNames(Function.prototype) is ['apply', 'bind', 'call',
 PASS getSortedOwnPropertyNames(Array) is ['from', 'isArray', 'length', 'name', 'of', 'prototype']
 PASS getSortedOwnPropertyNames(Array.prototype) is ['concat', 'constructor', 'entries', 'every', 'fill', 'filter', 'find', 'findIndex', 'forEach', 'includes', 'indexOf', 'join', 'keys', 'lastIndexOf', 'length', 'map', 'pop', 'push', 'reduce', 'reduceRight', 'reverse', 'shift', 'slice', 'some', 'sort', 'splice', 'toLocaleString', 'toString', 'unshift', 'values']
 PASS getSortedOwnPropertyNames(String) is ['fromCharCode', 'length', 'name', 'prototype']
-PASS getSortedOwnPropertyNames(String.prototype) is ['anchor', 'big', 'blink', 'bold', 'charAt', 'charCodeAt', 'concat', 'constructor', 'endsWith', 'fixed', 'fontcolor', 'fontsize', 'includes', 'indexOf', 'italics', 'lastIndexOf', 'length', 'link', 'localeCompare', 'match', 'repeat', 'replace', 'search', 'slice', 'small', 'split', 'startsWith', 'strike', 'sub', 'substr', 'substring', 'sup', 'toLocaleLowerCase', 'toLocaleUpperCase', 'toLowerCase', 'toString', 'toUpperCase', 'trim', 'trimLeft', 'trimRight', 'valueOf']
+PASS getSortedOwnPropertyNames(String.prototype) is ['anchor', 'big', 'blink', 'bold', 'charAt', 'charCodeAt', 'codePointAt', 'concat', 'constructor', 'endsWith', 'fixed', 'fontcolor', 'fontsize', 'includes', 'indexOf', 'italics', 'lastIndexOf', 'length', 'link', 'localeCompare', 'match', 'repeat', 'replace', 'search', 'slice', 'small', 'split', 'startsWith', 'strike', 'sub', 'substr', 'substring', 'sup', 'toLocaleLowerCase', 'toLocaleUpperCase', 'toLowerCase', 'toString', 'toUpperCase', 'trim', 'trimLeft', 'trimRight', 'valueOf']
 PASS getSortedOwnPropertyNames(Boolean) is ['length', 'name', 'prototype']
 PASS getSortedOwnPropertyNames(Boolean.prototype) is ['constructor', 'toString', 'valueOf']
 PASS getSortedOwnPropertyNames(Number) is ['EPSILON', 'MAX_SAFE_INTEGER', 'MAX_VALUE', 'MIN_SAFE_INTEGER', 'MIN_VALUE', 'NEGATIVE_INFINITY', 'NaN', 'POSITIVE_INFINITY', 'isFinite', 'isInteger', 'isNaN', 'isSafeInteger', 'length', 'name', 'parseFloat', 'parseInt', 'prototype']
index 53f6a61..f3113c7 100644 (file)
@@ -57,7 +57,7 @@ var expectedPropertyNamesSet = {
     "Array": "['from', 'isArray', 'length', 'name', 'of', 'prototype']",
     "Array.prototype": "['concat', 'constructor', 'entries', 'every', 'fill', 'filter', 'find', 'findIndex', 'forEach', 'includes', 'indexOf', 'join', 'keys', 'lastIndexOf', 'length', 'map', 'pop', 'push', 'reduce', 'reduceRight', 'reverse', 'shift', 'slice', 'some', 'sort', 'splice', 'toLocaleString', 'toString', 'unshift', 'values']",
     "String": "['fromCharCode', 'length', 'name', 'prototype']",
-    "String.prototype": "['anchor', 'big', 'blink', 'bold', 'charAt', 'charCodeAt', 'concat', 'constructor', 'endsWith', 'fixed', 'fontcolor', 'fontsize', 'includes', 'indexOf', 'italics', 'lastIndexOf', 'length', 'link', 'localeCompare', 'match', 'repeat', 'replace', 'search', 'slice', 'small', 'split', 'startsWith', 'strike', 'sub', 'substr', 'substring', 'sup', 'toLocaleLowerCase', 'toLocaleUpperCase', 'toLowerCase', 'toString', 'toUpperCase', 'trim', 'trimLeft', 'trimRight', 'valueOf']",
+    "String.prototype": "['anchor', 'big', 'blink', 'bold', 'charAt', 'charCodeAt', 'codePointAt', 'concat', 'constructor', 'endsWith', 'fixed', 'fontcolor', 'fontsize', 'includes', 'indexOf', 'italics', 'lastIndexOf', 'length', 'link', 'localeCompare', 'match', 'repeat', 'replace', 'search', 'slice', 'small', 'split', 'startsWith', 'strike', 'sub', 'substr', 'substring', 'sup', 'toLocaleLowerCase', 'toLocaleUpperCase', 'toLowerCase', 'toString', 'toUpperCase', 'trim', 'trimLeft', 'trimRight', 'valueOf']",
     "Boolean": "['length', 'name', 'prototype']",
     "Boolean.prototype": "['constructor', 'toString', 'valueOf']",
     "Number": "['EPSILON', 'MAX_SAFE_INTEGER', 'MAX_VALUE', 'MIN_SAFE_INTEGER', 'MIN_VALUE', 'NEGATIVE_INFINITY', 'NaN', 'POSITIVE_INFINITY', 'isFinite', 'isInteger', 'isNaN', 'isSafeInteger', 'length', 'name', 'parseFloat', 'parseInt', 'prototype']",
diff --git a/LayoutTests/js/script-tests/string-code-point-at.js b/LayoutTests/js/script-tests/string-code-point-at.js
new file mode 100644 (file)
index 0000000..369e013
--- /dev/null
@@ -0,0 +1,143 @@
+description("Test the basic behaviors of String.codePointAt");
+
+shouldBe('String.codePointAt', 'undefined');
+
+shouldBeEqualToString('typeof String.prototype.codePointAt', 'function');
+
+// Function properties.
+shouldBe('String.prototype.codePointAt.length', '1');
+shouldBeEqualToString('String.prototype.codePointAt.name', 'codePointAt')
+shouldBe('Object.getOwnPropertyDescriptor(String.prototype, "codePointAt").configurable', 'true');
+shouldBe('Object.getOwnPropertyDescriptor(String.prototype, "codePointAt").enumerable', 'false');
+shouldBe('Object.getOwnPropertyDescriptor(String.prototype, "codePointAt").writable', 'true');
+
+// The function should only be on the prototype chain, not on the object themselves.
+shouldBeFalse('"foo".hasOwnProperty("codePointAt")');
+shouldBeFalse('(new String("bar")).hasOwnProperty("codePointAt")');
+
+// Some simple cases.
+shouldBe('"".codePointAt(0)', 'undefined');
+shouldBe('"".codePointAt(1)', 'undefined');
+
+shouldBe('"Été".codePointAt(0)', '201');
+shouldBe('"Été".codePointAt(1)', '116');
+shouldBe('"Été".codePointAt(2)', '233');
+shouldBe('"Été".codePointAt(3)', 'undefined');
+
+shouldBe('"ウェブキット".codePointAt(0)', '12454');
+shouldBe('"ウェブキット".codePointAt(1)', '12455');
+shouldBe('"ウェブキット".codePointAt(2)', '12502');
+shouldBe('"ウェブキット".codePointAt(3)', '12461');
+shouldBe('"ウェブキット".codePointAt(4)', '12483');
+shouldBe('"ウェブキット".codePointAt(5)', '12488');
+shouldBe('"ウェブキット".codePointAt(6)', 'undefined');
+
+// Object coercion.
+shouldThrow('"".codePointAt.call(null, 0)');
+shouldThrow('"".codePointAt.call(undefined, 0)');
+shouldBe('"".codePointAt.call(0, 0)', '48');
+shouldBe('"".codePointAt.call(Math.PI, 0)', '51');
+shouldBe('"".codePointAt.call(Math.PI, 1)', '46');
+shouldBe('"".codePointAt.call(Math.PI, 3)', '52');
+shouldBe('"".codePointAt.call(true, 3)', '101');
+shouldBe('"".codePointAt.call(false, 3)', '115');
+shouldBe('"".codePointAt.call(new Object, 3)', '106');
+shouldThrow('"".codePointAt.call(Symbol("WebKit"), 3)');
+
+// toString.
+var objectWithCustomToString = { toString: function() { return "ø"; } };
+shouldBe('"".codePointAt.call(objectWithCustomToString, 0)', '248');
+
+var objectThrowingOnToString = { toString: function() { throw "Hehe"; } };
+shouldThrow('"".codePointAt.call(objectThrowingOnToString, 0)');
+
+var objectCountingToString = { counter: 0, toString: function() { ++this.counter; return this.counter; } };
+shouldBe('"".codePointAt.call(objectCountingToString, 0)', '49');
+shouldBe('objectCountingToString.counter', '1');
+
+// ToNumber.
+var objectWithCustomValueOf = { toString: function() { return "5"; }, valueOf: function() { return 1; } };
+shouldBe('"abcde".codePointAt(objectWithCustomValueOf)', '98');
+
+// The second object is never converted to number if the first object did not convert to string.
+var objectRecordsValueOf = { valueOfEvaluated: false, valueOf: function() { this.valueOfEvaluated = true; return 1; } }
+shouldThrow('"".codePointAt.call(null, objectRecordsValueOf)');
+shouldThrow('"".codePointAt.call(undefined, objectRecordsValueOf)');
+shouldThrow('"".codePointAt.call(Symbol("WebKit"), objectRecordsValueOf)');
+shouldThrow('"".codePointAt.call(objectThrowingOnToString, objectRecordsValueOf)');
+shouldBeFalse('objectRecordsValueOf.valueOfEvaluated');
+
+// Evaluation order.
+var evaluationOrderRecorder = {
+    methodsCalled: [],
+    toString: function() { this.methodsCalled.push("toString"); return "foobar"; },
+    valueOf: function() { this.methodsCalled.push("valueOf"); return 5; }
+}
+shouldBe('"".codePointAt.call(evaluationOrderRecorder, evaluationOrderRecorder)', '114');
+shouldBeEqualToString('evaluationOrderRecorder.methodsCalled.toString()', 'toString,valueOf');
+
+// Weird positions.
+shouldBe('"abc".codePointAt(NaN)', '97');
+shouldBe('"abc".codePointAt(-0)', '97');
+shouldBe('"abc".codePointAt(-0.0)', '97');
+shouldBe('"abc".codePointAt(-0.05)', '97');
+shouldBe('"abc".codePointAt(-0.999)', '97');
+shouldBe('"abc".codePointAt(0.4)', '97');
+shouldBe('"abc".codePointAt(0.9)', '97');
+shouldBe('"abc".codePointAt(2.9999)', '99');
+
+// Out of bound positions.
+shouldBe('"abc".codePointAt(-1)', 'undefined');
+shouldBe('"abc".codePointAt(4)', 'undefined');
+shouldBe('var str = "abc"; str.codePointAt(str.length)', 'undefined');
+shouldBe('"abc".codePointAt(4.1)', 'undefined');
+shouldBe('"abc".codePointAt(Number.POSITIVE_INFINITY)', 'undefined');
+shouldBe('"abc".codePointAt(Number.NEGATIVE_INFINITY)', 'undefined');
+
+// Non-number as positions.
+shouldBe('"abc".codePointAt(null)', '97');
+shouldBe('"abc".codePointAt(undefined)', '97');
+shouldBe('"abc".codePointAt("")', '97');
+shouldBe('"abc".codePointAt("WebKit!")', '97');
+shouldBe('"abc".codePointAt(new Object)', '97');
+shouldThrow('"abc".codePointAt(Symbol("WebKit"))');
+
+// The following are using special test functions because of limitations of WebKitTestRunner when handling strings with invalid codepoints.
+// When transfering the text of a test, WebKitTestRunner converts it to a UTF-8 C String. Not all invalid code point can be represented.
+
+// If first < 0xD800 or first > 0xDBFF or position+1 = size, return first.
+function testLeadSurrogateOutOfBounds()
+{
+    return ("\uD7FF\uDC00".codePointAt(0) === 0xd7ff && "\uD7FF\uDC00".codePointAt(1) === 0xdc00 && "\uD7FF\uDC00".codePointAt(2) === undefined
+            && "\uDC00\uDC00".codePointAt(0) === 0xdc00 && "\uDC00\uDC00".codePointAt(1) === 0xdc00 && "\uDC00\uDC00".codePointAt(2) === undefined)
+}
+shouldBeTrue("testLeadSurrogateOutOfBounds()");
+
+function testLeadSurrogateAsLastCharacter()
+{
+    return "abc\uD800".codePointAt(3) === 0xd800;
+}
+shouldBeTrue("testLeadSurrogateAsLastCharacter()");
+
+// If second < 0xDC00 or second > 0xDFFF, return first.
+function testTrailSurrogateOutOfbounds()
+{
+    return ("\uD800\uDBFF".codePointAt(0) === 0xd800 && "\uD800\uDBFF".codePointAt(1) === 0xdbff && "\uD800\uDBFF".codePointAt(2) === undefined
+            && "\uD800\uE000".codePointAt(0) === 0xd800 && "\uD800\uE000".codePointAt(1) === 0xe000 && "\uD800\uE000".codePointAt(2) === undefined)
+}
+shouldBeTrue("testTrailSurrogateOutOfbounds()");
+
+// Null in a string.
+function testAccessNullInString()
+{
+    return "a\u0000b".codePointAt(0) === 97 && "a\u0000b".codePointAt(1) === 0 && "a\u0000b".codePointAt(2) === 98 && "a\u0000b".codePointAt(3) === undefined;
+}
+shouldBeTrue("testAccessNullInString()");
+
+
+// Normal combinations of surrogates.
+function testNormalCombinationOfSurrogates()
+{
+    return "\uD800\uDC00".codePointAt(0) === 65536 && "\uD800\uDC00".codePointAt(1) === 56320 && "\uD800\uDC00".codePointAt(2) === undefined;
+}
+shouldBeTrue("testNormalCombinationOfSurrogates()");
diff --git a/LayoutTests/js/string-code-point-at-expected.txt b/LayoutTests/js/string-code-point-at-expected.txt
new file mode 100644 (file)
index 0000000..9f0b453
--- /dev/null
@@ -0,0 +1,78 @@
+Test the basic behaviors of String.codePointAt
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+
+PASS String.codePointAt is undefined
+PASS typeof String.prototype.codePointAt is "function"
+PASS String.prototype.codePointAt.length is 1
+PASS String.prototype.codePointAt.name is "codePointAt"
+PASS Object.getOwnPropertyDescriptor(String.prototype, "codePointAt").configurable is true
+PASS Object.getOwnPropertyDescriptor(String.prototype, "codePointAt").enumerable is false
+PASS Object.getOwnPropertyDescriptor(String.prototype, "codePointAt").writable is true
+PASS "foo".hasOwnProperty("codePointAt") is false
+PASS (new String("bar")).hasOwnProperty("codePointAt") is false
+PASS "".codePointAt(0) is undefined
+PASS "".codePointAt(1) is undefined
+PASS "Été".codePointAt(0) is 201
+PASS "Été".codePointAt(1) is 116
+PASS "Été".codePointAt(2) is 233
+PASS "Été".codePointAt(3) is undefined
+PASS "ウェブキット".codePointAt(0) is 12454
+PASS "ウェブキット".codePointAt(1) is 12455
+PASS "ウェブキット".codePointAt(2) is 12502
+PASS "ウェブキット".codePointAt(3) is 12461
+PASS "ウェブキット".codePointAt(4) is 12483
+PASS "ウェブキット".codePointAt(5) is 12488
+PASS "ウェブキット".codePointAt(6) is undefined
+PASS "".codePointAt.call(null, 0) threw exception TypeError: Type error.
+PASS "".codePointAt.call(undefined, 0) threw exception TypeError: Type error.
+PASS "".codePointAt.call(0, 0) is 48
+PASS "".codePointAt.call(Math.PI, 0) is 51
+PASS "".codePointAt.call(Math.PI, 1) is 46
+PASS "".codePointAt.call(Math.PI, 3) is 52
+PASS "".codePointAt.call(true, 3) is 101
+PASS "".codePointAt.call(false, 3) is 115
+PASS "".codePointAt.call(new Object, 3) is 106
+PASS "".codePointAt.call(Symbol("WebKit"), 3) threw exception TypeError: Type error.
+PASS "".codePointAt.call(objectWithCustomToString, 0) is 248
+PASS "".codePointAt.call(objectThrowingOnToString, 0) threw exception Hehe.
+PASS "".codePointAt.call(objectCountingToString, 0) is 49
+PASS objectCountingToString.counter is 1
+PASS "abcde".codePointAt(objectWithCustomValueOf) is 98
+PASS "".codePointAt.call(null, objectRecordsValueOf) threw exception TypeError: Type error.
+PASS "".codePointAt.call(undefined, objectRecordsValueOf) threw exception TypeError: Type error.
+PASS "".codePointAt.call(Symbol("WebKit"), objectRecordsValueOf) threw exception TypeError: Type error.
+PASS "".codePointAt.call(objectThrowingOnToString, objectRecordsValueOf) threw exception Hehe.
+PASS objectRecordsValueOf.valueOfEvaluated is false
+PASS "".codePointAt.call(evaluationOrderRecorder, evaluationOrderRecorder) is 114
+PASS evaluationOrderRecorder.methodsCalled.toString() is "toString,valueOf"
+PASS "abc".codePointAt(NaN) is 97
+PASS "abc".codePointAt(-0) is 97
+PASS "abc".codePointAt(-0.0) is 97
+PASS "abc".codePointAt(-0.05) is 97
+PASS "abc".codePointAt(-0.999) is 97
+PASS "abc".codePointAt(0.4) is 97
+PASS "abc".codePointAt(0.9) is 97
+PASS "abc".codePointAt(2.9999) is 99
+PASS "abc".codePointAt(-1) is undefined
+PASS "abc".codePointAt(4) is undefined
+PASS var str = "abc"; str.codePointAt(str.length) is undefined
+PASS "abc".codePointAt(4.1) is undefined
+PASS "abc".codePointAt(Number.POSITIVE_INFINITY) is undefined
+PASS "abc".codePointAt(Number.NEGATIVE_INFINITY) is undefined
+PASS "abc".codePointAt(null) is 97
+PASS "abc".codePointAt(undefined) is 97
+PASS "abc".codePointAt("") is 97
+PASS "abc".codePointAt("WebKit!") is 97
+PASS "abc".codePointAt(new Object) is 97
+PASS "abc".codePointAt(Symbol("WebKit")) threw exception TypeError: Type error.
+PASS testLeadSurrogateOutOfBounds() is true
+PASS testLeadSurrogateAsLastCharacter() is true
+PASS testTrailSurrogateOutOfbounds() is true
+PASS testAccessNullInString() is true
+PASS testNormalCombinationOfSurrogates() is true
+PASS successfullyParsed is true
+
+TEST COMPLETE
+
diff --git a/LayoutTests/js/string-code-point-at.html b/LayoutTests/js/string-code-point-at.html
new file mode 100644 (file)
index 0000000..756a422
--- /dev/null
@@ -0,0 +1,11 @@
+<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN">
+<html>
+<head>
+<meta charset="utf-8">
+<script src="../resources/js-test-pre.js"></script>
+</head>
+<body>
+<script src="script-tests/string-code-point-at.js"></script>
+<script src="../resources/js-test-post.js"></script>
+</body>
+</html>
index 32c2b88..3881da3 100644 (file)
@@ -1,3 +1,18 @@
+2015-04-22  Benjamin Poulain  <benjamin@webkit.org>
+
+        Implement String.codePointAt()
+        https://bugs.webkit.org/show_bug.cgi?id=143934
+
+        Reviewed by Darin Adler.
+
+        This patch adds String.codePointAt() as defined by ES6.
+        I opted for a C++ implementation for now.
+
+        * runtime/StringPrototype.cpp:
+        (JSC::StringPrototype::finishCreation):
+        (JSC::codePointAt):
+        (JSC::stringProtoFuncCodePointAt):
+
 2015-04-22  Mark Lam  <mark.lam@apple.com>
 
         SparseArrayEntry's write barrier owner should be the SparseArrayValueMap.
index 99dac31..fa7bef1 100644 (file)
@@ -56,6 +56,7 @@ STATIC_ASSERT_IS_TRIVIALLY_DESTRUCTIBLE(StringPrototype);
 EncodedJSValue JSC_HOST_CALL stringProtoFuncToString(ExecState*);
 EncodedJSValue JSC_HOST_CALL stringProtoFuncCharAt(ExecState*);
 EncodedJSValue JSC_HOST_CALL stringProtoFuncCharCodeAt(ExecState*);
+EncodedJSValue JSC_HOST_CALL stringProtoFuncCodePointAt(ExecState*);
 EncodedJSValue JSC_HOST_CALL stringProtoFuncConcat(ExecState*);
 EncodedJSValue JSC_HOST_CALL stringProtoFuncIndexOf(ExecState*);
 EncodedJSValue JSC_HOST_CALL stringProtoFuncLastIndexOf(ExecState*);
@@ -108,6 +109,7 @@ void StringPrototype::finishCreation(VM& vm, JSGlobalObject* globalObject, JSStr
     JSC_NATIVE_INTRINSIC_FUNCTION(vm.propertyNames->valueOf, stringProtoFuncToString, DontEnum, 0, StringPrototypeValueOfIntrinsic);
     JSC_NATIVE_INTRINSIC_FUNCTION("charAt", stringProtoFuncCharAt, DontEnum, 1, CharAtIntrinsic);
     JSC_NATIVE_INTRINSIC_FUNCTION("charCodeAt", stringProtoFuncCharCodeAt, DontEnum, 1, CharCodeAtIntrinsic);
+    JSC_NATIVE_FUNCTION("codePointAt", stringProtoFuncCodePointAt, DontEnum, 1);
     JSC_NATIVE_FUNCTION("concat", stringProtoFuncConcat, DontEnum, 1);
     JSC_NATIVE_FUNCTION("indexOf", stringProtoFuncIndexOf, DontEnum, 1);
     JSC_NATIVE_FUNCTION("lastIndexOf", stringProtoFuncLastIndexOf, DontEnum, 1);
@@ -811,6 +813,42 @@ EncodedJSValue JSC_HOST_CALL stringProtoFuncCharCodeAt(ExecState* exec)
     return JSValue::encode(jsNaN());
 }
 
+static inline UChar32 codePointAt(const String& string, unsigned position, unsigned length)
+{
+    RELEASE_ASSERT(position < length);
+    if (string.is8Bit())
+        return string.characters8()[position];
+    UChar32 character;
+    U16_NEXT(string.characters16(), position, length, character);
+    return character;
+}
+
+EncodedJSValue JSC_HOST_CALL stringProtoFuncCodePointAt(ExecState* exec)
+{
+    JSValue thisValue = exec->thisValue();
+    if (!checkObjectCoercible(thisValue))
+        return throwVMTypeError(exec);
+
+    String string = thisValue.toWTFString(exec);
+    unsigned length = string.length();
+
+    JSValue argument0 = exec->argument(0);
+    if (argument0.isUInt32()) {
+        unsigned position = argument0.asUInt32();
+        if (position < length)
+            return JSValue::encode(jsNumber(codePointAt(string, position, length)));
+        return JSValue::encode(jsUndefined());
+    }
+
+    if (UNLIKELY(exec->hadException()))
+        return JSValue::encode(jsUndefined());
+
+    double doublePosition = argument0.toInteger(exec);
+    if (doublePosition >= 0 && doublePosition < length)
+        return JSValue::encode(jsNumber(codePointAt(string, static_cast<unsigned>(doublePosition), length)));
+    return JSValue::encode(jsUndefined());
+}
+
 EncodedJSValue JSC_HOST_CALL stringProtoFuncConcat(ExecState* exec)
 {
     JSValue thisValue = exec->thisValue();