@@ -1731,6 +1731,28 @@ static bool str_toWellFormed(JSContext* cx, unsigned argc, Value* vp) {
17311731 return true ;
17321732}
17331733
1734+ // Clamp |value| to a string index between 0 and |length|.
1735+ static MOZ_ALWAYS_INLINE bool ToClampedStringIndex (JSContext* cx,
1736+ Handle<Value> value,
1737+ uint32_t length,
1738+ uint32_t * result) {
1739+ // Handle the common case of int32 indices first.
1740+ if (value.isInt32 ()) {
1741+ int32_t i = value.toInt32 ();
1742+ *result = std::min (uint32_t (std::max (i, 0 )), length);
1743+ return true ;
1744+ }
1745+
1746+ double d;
1747+ if (!ToInteger (cx, value, &d)) {
1748+ return false ;
1749+ }
1750+ *result = uint32_t (std::clamp (d, 0.0 , double (length)));
1751+ return true ;
1752+ }
1753+
1754+ // Return |Some(index)| if |value| is a string index between 0 and |length|.
1755+ // Otherwise return |Nothing|.
17341756static MOZ_ALWAYS_INLINE bool ToStringIndex (JSContext* cx, Handle<Value> value,
17351757 size_t length,
17361758 mozilla::Maybe<size_t >* result) {
@@ -1753,6 +1775,8 @@ static MOZ_ALWAYS_INLINE bool ToStringIndex(JSContext* cx, Handle<Value> value,
17531775 return true ;
17541776}
17551777
1778+ // Return |Some(index)| if |value| is a relative string index between 0 and
1779+ // |length|. Otherwise return |Nothing|.
17561780static MOZ_ALWAYS_INLINE bool ToRelativeStringIndex (
17571781 JSContext* cx, Handle<Value> value, size_t length,
17581782 mozilla::Maybe<size_t >* result) {
@@ -2326,8 +2350,8 @@ static MOZ_ALWAYS_INLINE bool ReportErrorIfFirstArgIsRegExp(
23262350 return true ;
23272351}
23282352
2329- // ES2018 draft rev de77aaeffce115deaf948ed30c7dbe4c60983c0c
2330- // 21 .1.3.7 String.prototype.includes ( searchString [ , position ] )
2353+ // ES2026 draft rev a562082b031d89d00ee667181ce8a6158656bd4b
2354+ // 22 .1.3.8 String.prototype.includes ( searchString [ , position ] )
23312355bool js::str_includes (JSContext* cx, unsigned argc, Value* vp) {
23322356 AutoJSMethodProfilerEntry pseudoFrame (cx, " String.prototype" , " includes" );
23332357 CallArgs args = CallArgsFromVp (argc, vp);
@@ -2349,28 +2373,15 @@ bool js::str_includes(JSContext* cx, unsigned argc, Value* vp) {
23492373 return false ;
23502374 }
23512375
2352- // Step 6 .
2353- uint32_t pos = 0 ;
2376+ // Steps 6-9 .
2377+ uint32_t start = 0 ;
23542378 if (args.hasDefined (1 )) {
2355- if (args[1 ].isInt32 ()) {
2356- int i = args[1 ].toInt32 ();
2357- pos = (i < 0 ) ? 0U : uint32_t (i);
2358- } else {
2359- double d;
2360- if (!ToInteger (cx, args[1 ], &d)) {
2361- return false ;
2362- }
2363- pos = uint32_t (std::clamp (d, 0.0 , double (UINT32_MAX)));
2379+ if (!ToClampedStringIndex (cx, args[1 ], str->length (), &start)) {
2380+ return false ;
23642381 }
23652382 }
23662383
2367- // Step 7.
2368- uint32_t textLen = str->length ();
2369-
2370- // Step 8.
2371- uint32_t start = std::min (pos, textLen);
2372-
2373- // Steps 9-10.
2384+ // Steps 10-12.
23742385 JSLinearString* text = str->ensureLinear (cx);
23752386 if (!text) {
23762387 return false ;
@@ -2396,52 +2407,40 @@ bool js::StringIncludes(JSContext* cx, HandleString string,
23962407 return true ;
23972408}
23982409
2399- /* ES6 20120927 draft 15.5.4.7. */
2410+ // ES2026 draft rev a562082b031d89d00ee667181ce8a6158656bd4b
2411+ // 22.1.3.9 String.prototype.indexOf ( searchString [ , position ] )
24002412bool js::str_indexOf (JSContext* cx, unsigned argc, Value* vp) {
24012413 AutoJSMethodProfilerEntry pseudoFrame (cx, " String.prototype" , " indexOf" );
24022414 CallArgs args = CallArgsFromVp (argc, vp);
24032415
2404- // Steps 1, 2, and 3
2416+ // Steps 1-2.
24052417 RootedString str (cx, ToStringForStringFunction (cx, " indexOf" , args.thisv ()));
24062418 if (!str) {
24072419 return false ;
24082420 }
24092421
2410- // Steps 4 and 5
2422+ // Step 3.
24112423 Rooted<JSLinearString*> searchStr (cx, ArgToLinearString (cx, args, 0 ));
24122424 if (!searchStr) {
24132425 return false ;
24142426 }
24152427
2416- // Steps 6 and 7
2417- uint32_t pos = 0 ;
2428+ // Steps 4-7.
2429+ uint32_t start = 0 ;
24182430 if (args.hasDefined (1 )) {
2419- if (args[1 ].isInt32 ()) {
2420- int i = args[1 ].toInt32 ();
2421- pos = (i < 0 ) ? 0U : uint32_t (i);
2422- } else {
2423- double d;
2424- if (!ToInteger (cx, args[1 ], &d)) {
2425- return false ;
2426- }
2427- pos = uint32_t (std::clamp (d, 0.0 , double (UINT32_MAX)));
2431+ if (!ToClampedStringIndex (cx, args[1 ], str->length (), &start)) {
2432+ return false ;
24282433 }
24292434 }
24302435
2431- // Step 8
2432- uint32_t textLen = str->length ();
2433-
2434- // Step 9
2435- uint32_t start = std::min (pos, textLen);
2436-
24372436 if (str == searchStr) {
24382437 // AngularJS often invokes "false".indexOf("false"). This check should
24392438 // be cheap enough to not hurt anything else.
24402439 args.rval ().setInt32 (start == 0 ? 0 : -1 );
24412440 return true ;
24422441 }
24432442
2444- // Steps 10 and 11
2443+ // Steps 8-10.
24452444 JSLinearString* text = str->ensureLinear (cx);
24462445 if (!text) {
24472446 return false ;
@@ -2527,8 +2526,8 @@ static int32_t LastIndexOf(const JSLinearString* text,
25272526 searchLen, start);
25282527}
25292528
2530- // ES2017 draft rev 6859bb9ccaea9c6ede81d71e5320e3833b92cb3e
2531- // 21 .1.3.9 String.prototype.lastIndexOf ( searchString [ , position ] )
2529+ // ES2026 draft rev a562082b031d89d00ee667181ce8a6158656bd4b
2530+ // 22 .1.3.11 String.prototype.lastIndexOf ( searchString [ , position ] )
25322531static bool str_lastIndexOf (JSContext* cx, unsigned argc, Value* vp) {
25332532 AutoJSMethodProfilerEntry pseudoFrame (cx, " String.prototype" , " lastIndexOf" );
25342533 CallArgs args = CallArgsFromVp (argc, vp);
@@ -2546,13 +2545,13 @@ static bool str_lastIndexOf(JSContext* cx, unsigned argc, Value* vp) {
25462545 return false ;
25472546 }
25482547
2549- // Step 6 .
2548+ // Step 7 .
25502549 size_t len = str->length ();
25512550
25522551 // Step 8.
25532552 size_t searchLen = searchStr->length ();
25542553
2555- // Steps 4-5, 7 .
2554+ // Steps 4-6 and 9 .
25562555 int start = len - searchLen; // Start searching here
25572556 if (args.hasDefined (1 )) {
25582557 if (args[1 ].isInt32 ()) {
@@ -2599,7 +2598,7 @@ static bool str_lastIndexOf(JSContext* cx, unsigned argc, Value* vp) {
25992598 return false ;
26002599 }
26012600
2602- // Step 9 .
2601+ // Step 10-12 .
26032602 args.rval ().setInt32 (LastIndexOf (text, searchStr, start));
26042603 return true ;
26052604}
@@ -2642,8 +2641,8 @@ bool js::StringLastIndexOf(JSContext* cx, HandleString string,
26422641 return true ;
26432642}
26442643
2645- // ES2018 draft rev de77aaeffce115deaf948ed30c7dbe4c60983c0c
2646- // 21 .1.3.20 String.prototype.startsWith ( searchString [ , position ] )
2644+ // ES2026 draft rev a562082b031d89d00ee667181ce8a6158656bd4b
2645+ // 22 .1.3.24 String.prototype.startsWith ( searchString [ , position ] )
26472646bool js::str_startsWith (JSContext* cx, unsigned argc, Value* vp) {
26482647 AutoJSMethodProfilerEntry pseudoFrame (cx, " String.prototype" , " startsWith" );
26492648 CallArgs args = CallArgsFromVp (argc, vp);
@@ -2667,36 +2666,26 @@ bool js::str_startsWith(JSContext* cx, unsigned argc, Value* vp) {
26672666 }
26682667
26692668 // Step 6.
2670- uint32_t pos = 0 ;
2669+ uint32_t textLen = str->length ();
2670+
2671+ // Steps 7-8.
2672+ uint32_t start = 0 ;
26712673 if (args.hasDefined (1 )) {
2672- if (args[1 ].isInt32 ()) {
2673- int i = args[1 ].toInt32 ();
2674- pos = (i < 0 ) ? 0U : uint32_t (i);
2675- } else {
2676- double d;
2677- if (!ToInteger (cx, args[1 ], &d)) {
2678- return false ;
2679- }
2680- pos = uint32_t (std::clamp (d, 0.0 , double (UINT32_MAX)));
2674+ if (!ToClampedStringIndex (cx, args[1 ], textLen, &start)) {
2675+ return false ;
26812676 }
26822677 }
26832678
2684- // Step 7.
2685- uint32_t textLen = str->length ();
2686-
2687- // Step 8.
2688- uint32_t start = std::min (pos, textLen);
2689-
26902679 // Step 9.
26912680 uint32_t searchLen = searchStr->length ();
26922681
2693- // Step 10 .
2682+ // Step 12 .
26942683 if (searchLen + start < searchLen || searchLen + start > textLen) {
26952684 args.rval ().setBoolean (false );
26962685 return true ;
26972686 }
26982687
2699- // Steps 11-12 .
2688+ // Steps 10-11 and 13-15 .
27002689 JSLinearString* text = str->ensureLinear (cx);
27012690 if (!text) {
27022691 return false ;
@@ -2727,8 +2716,8 @@ bool js::StringStartsWith(JSContext* cx, HandleString string,
27272716 return true ;
27282717}
27292718
2730- // ES2018 draft rev de77aaeffce115deaf948ed30c7dbe4c60983c0c
2731- // 21 .1.3.6 String.prototype.endsWith ( searchString [ , endPosition ] )
2719+ // ES2026 draft rev a562082b031d89d00ee667181ce8a6158656bd4b
2720+ // 22 .1.3.7 String.prototype.endsWith ( searchString [ , endPosition ] )
27322721bool js::str_endsWith (JSContext* cx, unsigned argc, Value* vp) {
27332722 AutoJSMethodProfilerEntry pseudoFrame (cx, " String.prototype" , " endsWith" );
27342723 CallArgs args = CallArgsFromVp (argc, vp);
@@ -2753,37 +2742,27 @@ bool js::str_endsWith(JSContext* cx, unsigned argc, Value* vp) {
27532742 // Step 6.
27542743 uint32_t textLen = str->length ();
27552744
2756- // Step 7 .
2757- uint32_t pos = textLen;
2745+ // Steps 7-8 .
2746+ uint32_t end = textLen;
27582747 if (args.hasDefined (1 )) {
2759- if (args[1 ].isInt32 ()) {
2760- int i = args[1 ].toInt32 ();
2761- pos = (i < 0 ) ? 0U : uint32_t (i);
2762- } else {
2763- double d;
2764- if (!ToInteger (cx, args[1 ], &d)) {
2765- return false ;
2766- }
2767- pos = uint32_t (std::clamp (d, 0.0 , double (UINT32_MAX)));
2748+ if (!ToClampedStringIndex (cx, args[1 ], textLen, &end)) {
2749+ return false ;
27682750 }
27692751 }
27702752
2771- // Step 8.
2772- uint32_t end = std::min (pos, textLen);
2773-
27742753 // Step 9.
27752754 uint32_t searchLen = searchStr->length ();
27762755
2777- // Step 11 (reordered).
2756+ // Step 12 (reordered).
27782757 if (searchLen > end) {
27792758 args.rval ().setBoolean (false );
27802759 return true ;
27812760 }
27822761
2783- // Step 10 .
2762+ // Step 11 .
27842763 uint32_t start = end - searchLen;
27852764
2786- // Steps 12-13 .
2765+ // Steps 10 and 13-15 .
27872766 JSLinearString* text = str->ensureLinear (cx);
27882767 if (!text) {
27892768 return false ;
0 commit comments