Bug 887016 - Part 10: Implement RegExp.prototype[@@search] and call it from String.prototype.search. r=till
☠☠ backed out by 0fd465ec1e2c ☠ ☠
authorTooru Fujisawa <arai_a@mac.com>
Sat, 05 Sep 2015 22:01:40 +0900
changeset 290628 f23a61067cefec53fd72dc00383092f72fe707cb
parent 290627 537d40121b6d4c80071e02aeaa95712fdfdcb107
child 290629 f373233a8c82941b6ca1dfca5fd62edb3ceae81a
push id19656
push usergwagner@mozilla.com
push dateMon, 04 Apr 2016 13:43:23 +0000
treeherderb2g-inbound@e99061fde28a [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerstill
bugs887016
milestone48.0a1
Bug 887016 - Part 10: Implement RegExp.prototype[@@search] and call it from String.prototype.search. r=till
js/src/builtin/RegExp.cpp
js/src/builtin/RegExp.js
js/src/builtin/String.js
js/src/jit/Lowering.cpp
js/src/jsapi.h
js/src/jsstr.cpp
js/src/jsstr.h
js/src/tests/ecma_6/RegExp/search-this.js
js/src/tests/ecma_6/RegExp/search-trace.js
js/src/tests/ecma_6/RegExp/search.js
js/src/tests/ecma_6/String/search.js
js/src/tests/ecma_6/Symbol/well-known.js
js/src/vm/CommonPropertyNames.h
js/src/vm/GlobalObject.cpp
js/src/vm/SelfHosting.cpp
js/xpconnect/tests/chrome/test_xrayToJS.xul
--- a/js/src/builtin/RegExp.cpp
+++ b/js/src/builtin/RegExp.cpp
@@ -640,16 +640,17 @@ const JSFunctionSpec js::regexp_methods[
 #if JS_HAS_TOSOURCE
     JS_SELF_HOSTED_FN(js_toSource_str, "RegExpToString", 0, 0),
 #endif
     JS_SELF_HOSTED_FN(js_toString_str, "RegExpToString", 0, 0),
     JS_FN("compile",        regexp_compile,     2,0),
     JS_SELF_HOSTED_FN("exec", "RegExp_prototype_Exec", 1,0),
     JS_SELF_HOSTED_FN("test", "RegExpTest" ,    1,0),
     JS_SELF_HOSTED_SYM_FN(match, "RegExpMatch", 1,0),
+    JS_SELF_HOSTED_SYM_FN(search, "RegExpSearch", 1,0),
     JS_FS_END
 };
 
 #define STATIC_PAREN_GETTER_CODE(parenNum)                                      \
     if (!res->createParen(cx, parenNum, args.rval()))                           \
         return false;                                                           \
     if (args.rval().isUndefined())                                              \
         args.rval().setString(cx->runtime()->emptyString);                      \
@@ -1080,24 +1081,22 @@ js::RegExpPrototypeOptimizableRaw(JSCont
     bool has = false;
     if (!HasOwnDataPropertyPure(cx, proto, SYMBOL_TO_JSID(cx->wellKnownSymbols().match), &has))
         return false;
     if (!has) {
         *result = false;
         return true;
     }
 
-    /*
     if (!HasOwnDataPropertyPure(cx, proto, SYMBOL_TO_JSID(cx->wellKnownSymbols().search), &has))
         return false;
     if (!has) {
         *result = false;
         return true;
     }
-    */
 
     if (!HasOwnDataPropertyPure(cx, proto, NameToId(cx->names().exec), &has))
         return false;
     if (!has) {
         *result = false;
         return true;
     }
 
--- a/js/src/builtin/RegExp.js
+++ b/js/src/builtin/RegExp.js
@@ -142,16 +142,48 @@ function RegExpMatch(string) {
             rx.lastIndex = fullUnicode ? AdvanceStringIndex(S, lastIndex) : lastIndex + 1;
         }
 
         // Step 6.e.iii.5.
         n++;
     }
 }
 
+// ES 2016 draft Mar 25, 2016 21.2.5.9.
+function RegExpSearch(string) {
+    // Step 1.
+    var rx = this;
+
+    // Step 2.
+    if (!IsObject(rx))
+        ThrowTypeError(JSMSG_NOT_NONNULL_OBJECT, rx === null ? "null" : typeof rx);
+
+    // Step 3.
+    var S = ToString(string);
+
+    // Step 4.
+    var previousLastIndex = rx.lastIndex;
+
+    // Step 5.
+    rx.lastIndex = 0;
+
+    // Step 6.
+    var result = RegExpExec(rx, S, false);
+
+    // Step 7.
+    rx.lastIndex = previousLastIndex;
+
+    // Step 8.
+    if (result === null)
+        return -1;
+
+    // Step 9.
+    return result.index;
+}
+
 // ES6 21.2.5.2.
 // NOTE: This is not RegExpExec (21.2.5.2.1).
 function RegExp_prototype_Exec(string) {
     // Steps 1-3.
     var R = this;
     if (!IsObject(R) || !IsRegExpObject(R))
         return callFunction(CallRegExpMethodIfWrapped, R, string, "RegExp_prototype_Exec");
 
--- a/js/src/builtin/String.js
+++ b/js/src/builtin/String.js
@@ -66,16 +66,79 @@ function String_match(regexp) {
 }
 
 function String_generic_match(thisValue, regexp) {
     if (thisValue === undefined)
         ThrowTypeError(JSMSG_MISSING_FUN_ARG, 0, 'String.match');
     return callFunction(String_match, thisValue, regexp);
 }
 
+function StringProtoHasNoSearch() {
+    var ObjectProto = GetBuiltinPrototype("Object");
+    var StringProto = GetBuiltinPrototype("String");
+    if (!ObjectHasPrototype(StringProto, ObjectProto))
+        return false;
+    return !(std_search in StringProto);
+}
+
+function IsStringSearchOptimizable() {
+    var RegExpProto = GetBuiltinPrototype("RegExp");
+    // If RegExpPrototypeOptimizable succeeds, `exec` and `@@search` are
+    // guaranteed to be data properties.
+    return RegExpPrototypeOptimizable(RegExpProto) &&
+           RegExpProto.exec === RegExp_prototype_Exec &&
+           RegExpProto[std_search] === RegExpSearch;
+}
+
+// ES 2016 draft Mar 25, 2016 21.1.3.15.
+function String_search(regexp) {
+    // Step 1.
+    RequireObjectCoercible(this);
+
+    // Step 2.
+    var isPatternString = (typeof regexp === "string");
+    if (!(isPatternString && StringProtoHasNoSearch()) && regexp !== undefined && regexp !== null) {
+        // Step 2.a.
+        var searcher = regexp[std_search];
+
+        // Step 2.b.
+        if (searcher !== undefined)
+            return callContentFunction(searcher, regexp, this);
+    }
+
+    // Step 3.
+    var string = ToString(this);
+
+    // FIXME: Non-standard flags argument (bug 1108382).
+    var flags = undefined;
+    if (arguments.length > 1) {
+        if (IsMatchFlagsArgumentEnabled())
+            flags = arguments[1];
+        WarnOnceAboutFlagsArgument();
+    } else {
+        if (isPatternString && IsStringSearchOptimizable()) {
+            var flatResult = FlatStringSearch(string, regexp);
+            if (flatResult !== -2)
+                return flatResult;
+        }
+    }
+
+    // Step 4.
+    var rx = RegExpCreate(regexp, flags);
+
+    // Step 5.
+    return callContentFunction(GetMethod(rx, std_search), rx, string);
+}
+
+function String_generic_search(thisValue, regexp) {
+    if (thisValue === undefined)
+        ThrowTypeError(JSMSG_MISSING_FUN_ARG, 0, 'String.search');
+    return callFunction(String_search, thisValue, regexp);
+}
+
 /* ES6 Draft Oct 14, 2014 21.1.3.19 */
 function String_substring(start, end) {
     // Steps 1-3.
     RequireObjectCoercible(this);
     var str = ToString(this);
 
     // Step 4.
     var len = str.length;
--- a/js/src/jit/Lowering.cpp
+++ b/js/src/jit/Lowering.cpp
@@ -2184,18 +2184,17 @@ MustCloneRegExpForCall(MCall* call, uint
     // this is a native call that does not let the regex escape.
 
     JSFunction* target = call->getSingleTarget();
     if (!target || !target->isNative())
         return true;
 
     if (useIndex == MCall::IndexOfArgument(0) &&
         (target->native() == str_split ||
-         target->native() == str_replace ||
-         target->native() == str_search))
+         target->native() == str_replace))
     {
         return false;
     }
 
     return true;
 }
 
 
--- a/js/src/jsapi.h
+++ b/js/src/jsapi.h
@@ -4861,16 +4861,17 @@ GetSymbolFor(JSContext* cx, HandleString
  */
 JS_PUBLIC_API(JSString*)
 GetSymbolDescription(HandleSymbol symbol);
 
 /* Well-known symbols. */
 #define JS_FOR_EACH_WELL_KNOWN_SYMBOL(macro) \
     macro(iterator) \
     macro(match) \
+    macro(search) \
     macro(species) \
     macro(toPrimitive) \
     macro(unscopables)
 
 enum class SymbolCode : uint32_t {
     // There is one SymbolCode for each well-known symbol.
 #define JS_DEFINE_SYMBOL_ENUM(name) name,
     JS_FOR_EACH_WELL_KNOWN_SYMBOL(JS_DEFINE_SYMBOL_ENUM)  // SymbolCode::iterator, etc.
--- a/js/src/jsstr.cpp
+++ b/js/src/jsstr.cpp
@@ -2290,61 +2290,16 @@ AdvanceStringIndex(HandleLinearString in
     /* Step 10. */
     if (!unicode::IsTrailSurrogate(second))
         return index + 1;
 
     /* Step 11. */
     return index + 2;
 }
 
-bool
-js::str_search(JSContext* cx, unsigned argc, Value* vp)
-{
-    CallArgs args = CallArgsFromVp(argc, vp);
-    RootedString str(cx, ThisToStringForStringProto(cx, args));
-    if (!str)
-        return false;
-
-    StringRegExpGuard g(cx);
-    if (!g.init(cx, args, true))
-        return false;
-    if (const FlatMatch* fm = g.tryFlatMatch(cx, str, 1, args.length())) {
-        args.rval().setInt32(fm->match());
-        return true;
-    }
-
-    if (cx->isExceptionPending())  /* from tryFlatMatch */
-        return false;
-
-    if (!g.normalizeRegExp(cx, false, 1, args))
-        return false;
-
-    RootedLinearString linearStr(cx, str->ensureLinear(cx));
-    if (!linearStr)
-        return false;
-
-    RegExpStatics* res = cx->global()->getRegExpStatics(cx);
-    if (!res)
-        return false;
-
-    /* Per ECMAv5 15.5.4.12 (5) The last index property is ignored and left unchanged. */
-    ScopedMatchPairs matches(&cx->tempLifoAlloc());
-    RegExpShared& re = g.regExp();
-    bool sticky = re.sticky();
-    RegExpRunStatus status = re.execute(cx, linearStr, 0, sticky, &matches, nullptr);
-    if (status == RegExpRunStatus_Error)
-        return false;
-
-    if (status == RegExpRunStatus_Success)
-        res->updateLazily(cx, linearStr, &re, 0, sticky);
-
-    args.rval().setInt32(status == RegExpRunStatus_Success_NotFound ? -1 : matches[0].start);
-    return true;
-}
-
 // Utility for building a rope (lazy concatenation) of strings.
 class RopeBuilder {
     JSContext* cx;
     RootedString res;
 
     RopeBuilder(const RopeBuilder& other) = delete;
     void operator=(const RopeBuilder& other) = delete;
 
@@ -3988,17 +3943,17 @@ static const JSFunctionSpec string_metho
 #endif
     JS_SELF_HOSTED_FN("repeat", "String_repeat",      1,0),
 #if EXPOSE_INTL_API
     JS_FN("normalize",         str_normalize,         0,JSFUN_GENERIC_NATIVE),
 #endif
 
     /* Perl-ish methods (search is actually Python-esque). */
     JS_SELF_HOSTED_FN("match", "String_match",        1,0),
-    JS_FN("search",            str_search,            1,JSFUN_GENERIC_NATIVE),
+    JS_SELF_HOSTED_FN("search", "String_search",      1,0),
     JS_INLINABLE_FN("replace", str_replace,           2,JSFUN_GENERIC_NATIVE, StringReplace),
     JS_INLINABLE_FN("split",   str_split,             2,JSFUN_GENERIC_NATIVE, StringSplit),
     JS_SELF_HOSTED_FN("substr", "String_substr",      2,0),
 
     /* Python-esque sequence methods. */
     JS_FN("concat",            str_concat,            1,JSFUN_GENERIC_NATIVE),
     JS_SELF_HOSTED_FN("slice", "String_slice",        2,0),
 
@@ -4143,16 +4098,17 @@ static const JSFunctionSpec string_stati
 
     JS_SELF_HOSTED_FN("fromCodePoint",   "String_static_fromCodePoint", 1,0),
     JS_SELF_HOSTED_FN("raw",             "String_static_raw",           2,JSFUN_HAS_REST),
     JS_SELF_HOSTED_FN("substring",       "String_static_substring",     3,0),
     JS_SELF_HOSTED_FN("substr",          "String_static_substr",        3,0),
     JS_SELF_HOSTED_FN("slice",           "String_static_slice",         3,0),
 
     JS_SELF_HOSTED_FN("match",           "String_generic_match",        2,0),
+    JS_SELF_HOSTED_FN("search",          "String_generic_search",       2,0),
 
     // This must be at the end because of bug 853075: functions listed after
     // self-hosted methods aren't available in self-hosted code.
 #if EXPOSE_INTL_API
     JS_SELF_HOSTED_FN("localeCompare",   "String_static_localeCompare", 2,0),
 #endif
     JS_FS_END
 };
@@ -5299,8 +5255,39 @@ js::FlatStringMatch(JSContext* cx, unsig
 
     if (!isFlat) {
         args.rval().setUndefined();
         return true;
     }
 
     return BuildFlatMatchArray(cx, str, pattern, match, args.rval());
 }
+
+bool
+js::FlatStringSearch(JSContext* cx, unsigned argc, Value* vp)
+{
+    CallArgs args = CallArgsFromVp(argc, vp);
+    MOZ_ASSERT(args.length() == 2);
+    MOZ_ASSERT(args[0].isString());
+    MOZ_ASSERT(args[1].isString());
+#ifdef DEBUG
+    bool isOptimizable = false;
+    if (!CallIsStringOptimizable(cx, "IsStringSearchOptimizable", &isOptimizable))
+        return false;
+    MOZ_ASSERT(isOptimizable);
+#endif
+
+    RootedString str(cx,args[0].toString());
+    RootedString pattern(cx, args[1].toString());
+
+    bool isFlat = false;
+    int32_t match = 0;
+    if (!FlatStringMatchHelper(cx, str, pattern, &isFlat, &match))
+        return false;
+
+    if (!isFlat) {
+        args.rval().setInt32(-2);
+        return true;
+    }
+
+    args.rval().setInt32(match);
+    return true;
+}
--- a/js/src/jsstr.h
+++ b/js/src/jsstr.h
@@ -426,19 +426,16 @@ FileEscapedString(FILE* fp, const char* 
 {
     Fprinter out(fp);
     bool res = EscapedStringPrinter(out, chars, length, quote);
     out.finish();
     return res;
 }
 
 bool
-str_search(JSContext* cx, unsigned argc, Value* vp);
-
-bool
 str_split(JSContext* cx, unsigned argc, Value* vp);
 
 JSObject*
 str_split_string(JSContext* cx, HandleObjectGroup group, HandleString str, HandleString sep);
 
 JSString *
 str_flat_replace_string(JSContext *cx, HandleString string, HandleString pattern,
                         HandleString replacement);
@@ -448,11 +445,14 @@ str_replace_string_raw(JSContext* cx, Ha
                        HandleString replacement);
 
 extern bool
 StringConstructor(JSContext* cx, unsigned argc, Value* vp);
 
 extern bool
 FlatStringMatch(JSContext* cx, unsigned argc, Value* vp);
 
+extern bool
+FlatStringSearch(JSContext* cx, unsigned argc, Value* vp);
+
 } /* namespace js */
 
 #endif /* jsstr_h */
new file mode 100644
--- /dev/null
+++ b/js/src/tests/ecma_6/RegExp/search-this.js
@@ -0,0 +1,12 @@
+var BUGNUMBER = 887016;
+var summary = "RegExp.prototype[@@search] should check this value.";
+
+print(BUGNUMBER + ": " + summary);
+
+for (var v of [null, 1, true, undefined, "", Symbol.iterator]) {
+  assertThrowsInstanceOf(() => RegExp.prototype[Symbol.search].call(v),
+                         TypeError);
+}
+
+if (typeof reportCompare === "function")
+    reportCompare(true, true);
new file mode 100644
--- /dev/null
+++ b/js/src/tests/ecma_6/RegExp/search-trace.js
@@ -0,0 +1,76 @@
+var BUGNUMBER = 887016;
+var summary = "Trace RegExp.prototype[@@search] behavior.";
+
+print(BUGNUMBER + ": " + summary);
+
+var n;
+var log;
+var target;
+
+var execResult;
+var lastIndexResult;
+var lastIndexExpected;
+
+function P(index) {
+  return new Proxy({ index }, {
+    get(that, name) {
+      log += "get:result[" + name + "],";
+      return that[name];
+    }
+  });
+}
+
+var myRegExp = {
+  get lastIndex() {
+    log += "get:lastIndex,";
+    return lastIndexResult[n];
+  },
+  set lastIndex(v) {
+    log += "set:lastIndex,";
+    assertEq(v, lastIndexExpected[n]);
+  },
+  get exec() {
+    log += "get:exec,";
+    return function(S) {
+      log += "call:exec,";
+      assertEq(S, target);
+      return execResult[n++];
+    };
+  },
+};
+
+function reset() {
+  n = 0;
+  log = "";
+  target = "abcAbcABC";
+}
+
+// Trace hit.
+reset();
+execResult        = [     P(16) ];
+lastIndexResult   = [ 10, ,     ];
+lastIndexExpected = [ 0,  10    ];
+var ret = RegExp.prototype[Symbol.search].call(myRegExp, target);
+assertEq(ret, 16);
+assertEq(log,
+         "get:lastIndex," +
+         "set:lastIndex," +
+         "get:exec,call:exec," +
+         "set:lastIndex," +
+         "get:result[index],");
+
+// Trace not hit.
+reset();
+execResult        = [     null ];
+lastIndexResult   = [ 10, ,    ];
+lastIndexExpected = [ 0,  10   ];
+ret = RegExp.prototype[Symbol.search].call(myRegExp, target);
+assertEq(ret, -1);
+assertEq(log,
+         "get:lastIndex," +
+         "set:lastIndex," +
+         "get:exec,call:exec," +
+         "set:lastIndex,");
+
+if (typeof reportCompare === "function")
+    reportCompare(true, true);
new file mode 100644
--- /dev/null
+++ b/js/src/tests/ecma_6/RegExp/search.js
@@ -0,0 +1,26 @@
+var BUGNUMBER = 887016;
+var summary = "Implement RegExp.prototype[@@search].";
+
+print(BUGNUMBER + ": " + summary);
+
+assertEq(RegExp.prototype[Symbol.search].name, "[Symbol.search]");
+assertEq(RegExp.prototype[Symbol.search].length, 1);
+var desc = Object.getOwnPropertyDescriptor(RegExp.prototype, Symbol.search);
+assertEq(desc.configurable, true);
+assertEq(desc.enumerable, false);
+assertEq(desc.writable, true);
+
+var re = /B/;
+var v = re[Symbol.search]("abcAbcABC");
+assertEq(v, 7);
+
+re = /B/i;
+v = re[Symbol.search]("abcAbcABCD");
+assertEq(v, 1);
+
+re = /d/;
+v = re[Symbol.search]("abcAbcABCD");
+assertEq(v, -1);
+
+if (typeof reportCompare === "function")
+    reportCompare(true, true);
new file mode 100644
--- /dev/null
+++ b/js/src/tests/ecma_6/String/search.js
@@ -0,0 +1,27 @@
+var BUGNUMBER = 887016;
+var summary = "Call RegExp.prototype[@@search] from String.prototype.search.";
+
+print(BUGNUMBER + ": " + summary);
+
+var called = 0;
+var myRegExp = {
+  [Symbol.search](S) {
+    assertEq(S, "abcAbcABC");
+    called++;
+    return 42;
+  }
+};
+assertEq("abcAbcABC".search(myRegExp), 42);
+assertEq(called, 1);
+
+called = 0;
+RegExp.prototype[Symbol.search] = function(S) {
+  assertEq(S, "abcAbcABC");
+  called++;
+  return 43;
+};
+assertEq("abcAbcABC".search("abc"), 43);
+assertEq(called, 1);
+
+if (typeof reportCompare === "function")
+    reportCompare(true, true);
--- a/js/src/tests/ecma_6/Symbol/well-known.js
+++ b/js/src/tests/ecma_6/Symbol/well-known.js
@@ -1,14 +1,15 @@
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/licenses/publicdomain/ */
 
 var names = [
     "iterator",
     "match",
+    "search",
     "species",
     "toPrimitive",
     "unscopables"
 ];
 
 for (var name of names) {
     // Well-known symbols exist.
     assertEq(typeof Symbol[name], "symbol");
--- a/js/src/vm/CommonPropertyNames.h
+++ b/js/src/vm/CommonPropertyNames.h
@@ -303,23 +303,25 @@
     macro(number, number, "number") \
     macro(boolean, boolean, "boolean") \
     macro(null, null, "null") \
     macro(symbol, symbol, "symbol") \
     /* Well-known atom names must be continuous and ordered, matching \
      * enum JS::SymbolCode in jsapi.h. */ \
     macro(iterator, iterator, "iterator") \
     macro(match, match, "match") \
+    macro(search, search, "search") \
     macro(species, species, "species") \
     macro(toPrimitive, toPrimitive, "toPrimitive") \
     macro(unscopables, unscopables, "unscopables") \
     /* Same goes for the descriptions of the well-known symbols. */ \
     macro(Symbol_hasInstance, Symbol_hasInstance, "Symbol.hasInstance") \
     macro(Symbol_isConcatSpreadable, Symbol_isConcatSpreadable, "Symbol.isConcatSpreadable") \
     macro(Symbol_iterator, Symbol_iterator, "Symbol.iterator") \
     macro(Symbol_match,    Symbol_match,    "Symbol.match") \
+    macro(Symbol_search,   Symbol_search,   "Symbol.search") \
     macro(Symbol_species,  Symbol_species,  "Symbol.species") \
     macro(Symbol_toPrimitive, Symbol_toPrimitive, "Symbol.toPrimitive") \
     macro(Symbol_unscopables, Symbol_unscopables, "Symbol.unscopables") \
     /* Function names for properties named by symbols. */ \
     macro(Symbol_iterator_fun, Symbol_iterator_fun, "[Symbol.iterator]") \
 
 #endif /* vm_CommonPropertyNames_h */
--- a/js/src/vm/GlobalObject.cpp
+++ b/js/src/vm/GlobalObject.cpp
@@ -456,16 +456,24 @@ GlobalObject::initSelfHostingBuiltins(JS
     RootedValue std_match(cx);
     std_match.setSymbol(cx->wellKnownSymbols().get(JS::SymbolCode::match));
     if (!JS_DefineProperty(cx, global, "std_match", std_match,
                            JSPROP_PERMANENT | JSPROP_READONLY))
     {
         return false;
     }
 
+    RootedValue std_search(cx);
+    std_search.setSymbol(cx->wellKnownSymbols().get(JS::SymbolCode::search));
+    if (!JS_DefineProperty(cx, global, "std_search", std_search,
+                           JSPROP_PERMANENT | JSPROP_READONLY))
+    {
+        return false;
+    }
+
     RootedValue std_species(cx);
     std_species.setSymbol(cx->wellKnownSymbols().get(JS::SymbolCode::species));
     if (!JS_DefineProperty(cx, global, "std_species", std_species,
                            JSPROP_PERMANENT | JSPROP_READONLY))
     {
         return false;
     }
 
--- a/js/src/vm/SelfHosting.cpp
+++ b/js/src/vm/SelfHosting.cpp
@@ -2416,16 +2416,17 @@ static const JSFunctionSpec intrinsic_fu
                     RegExpTester),
     JS_FN("RegExpCreate", intrinsic_RegExpCreate, 2,0),
     JS_INLINABLE_FN("RegExpPrototypeOptimizable", RegExpPrototypeOptimizable, 1,0,
                     RegExpPrototypeOptimizable),
     JS_INLINABLE_FN("RegExpInstanceOptimizable", RegExpInstanceOptimizable, 1,0,
                     RegExpInstanceOptimizable),
 
     JS_FN("FlatStringMatch", FlatStringMatch, 2,0),
+    JS_FN("FlatStringSearch", FlatStringSearch, 2,0),
 
     // See builtin/RegExp.h for descriptions of the regexp_* functions.
     JS_FN("regexp_exec_no_statics", regexp_exec_no_statics, 2,0),
     JS_FN("regexp_test_no_statics", regexp_test_no_statics, 2,0),
     JS_FN("regexp_construct", regexp_construct_self_hosting, 2,0),
 
     JS_FN("IsMatchFlagsArgumentEnabled", IsMatchFlagsArgumentEnabled, 0,0),
     JS_FN("WarnOnceAboutFlagsArgument", WarnOnceAboutFlagsArgument, 0,0),
--- a/js/xpconnect/tests/chrome/test_xrayToJS.xul
+++ b/js/xpconnect/tests/chrome/test_xrayToJS.xul
@@ -222,17 +222,17 @@ https://bugzilla.mozilla.org/show_bug.cg
 
   gPrototypeProperties['Function'] =
     ["constructor", "toSource", "toString", "apply", "call", "bind",
      "isGenerator", "length", "name", "arguments", "caller"];
   gConstructorProperties['Function'] = constructorProps([])
 
   gPrototypeProperties['RegExp'] =
     ["constructor", "toSource", "toString", "compile", "exec", "test",
-     Symbol.match,
+     Symbol.match, Symbol.search,
      "flags", "global", "ignoreCase", "multiline", "source", "sticky", "unicode",
      "lastIndex"];
   gConstructorProperties['RegExp'] =
     constructorProps(["input", "lastMatch", "lastParen",
                       "leftContext", "rightContext", "$1", "$2", "$3", "$4",
                       "$5", "$6", "$7", "$8", "$9", "$_", "$&", "$+",
                       "$`", "$'"])