Add module loader "resolve" hook for local file system to test the loader in JSC...
[WebKit-https.git] / Source / JavaScriptCore / jsc.cpp
index 033e696..efaeb04 100644 (file)
@@ -64,7 +64,9 @@
 #include <wtf/StringPrintStream.h>
 #include <wtf/text/StringBuilder.h>
 
-#if !OS(WINDOWS)
+#if OS(WINDOWS)
+#include <direct.h>
+#else
 #include <unistd.h>
 #endif
 
@@ -491,6 +493,7 @@ static EncodedJSValue JSC_HOST_CALL functionReturnTypeFor(ExecState*);
 static EncodedJSValue JSC_HOST_CALL functionDumpBasicBlockExecutionRanges(ExecState*);
 static EncodedJSValue JSC_HOST_CALL functionHasBasicBlockExecuted(ExecState*);
 static EncodedJSValue JSC_HOST_CALL functionEnableExceptionFuzz(ExecState*);
+static EncodedJSValue JSC_HOST_CALL functionDrainMicrotasks(ExecState*);
 #if ENABLE(WEBASSEMBLY)
 static EncodedJSValue JSC_HOST_CALL functionLoadWebAssembly(ExecState*);
 #endif
@@ -666,7 +669,9 @@ protected:
         addFunction(vm, "hasBasicBlockExecuted", functionHasBasicBlockExecuted, 2);
 
         addFunction(vm, "enableExceptionFuzz", functionEnableExceptionFuzz, 0);
-        
+
+        addFunction(vm, "drainMicrotasks", functionDrainMicrotasks, 0);
+
 #if ENABLE(WEBASSEMBLY)
         addFunction(vm, "loadWebAssembly", functionLoadWebAssembly, 1);
 #endif
@@ -694,6 +699,8 @@ protected:
         putDirect(vm, identifier, JSFunction::create(vm, this, arguments, identifier.string(), function, NoIntrinsic, function));
     }
 
+    static JSInternalPromise* moduleLoaderResolve(JSGlobalObject*, ExecState*, JSValue, JSValue);
+
     static JSInternalPromise* moduleLoaderFetch(JSGlobalObject* globalObject, ExecState* exec, JSValue key)
     {
         JSInternalPromiseDeferred* deferred = JSInternalPromiseDeferred::create(exec, globalObject);
@@ -701,23 +708,20 @@ protected:
         if (exec->hadException()) {
             JSValue exception = exec->exception();
             exec->clearException();
-            deferred->reject(exec, exception);
-            return deferred->promise();
+            return deferred->reject(exec, exception);
         }
 
         // Here, now we consider moduleKey as the fileName.
         Vector<char> utf8;
-        if (!fillBufferWithContentsOfFile(moduleKey, utf8)) {
-            deferred->reject(exec, createError(exec, makeString("Could not open file '", moduleKey, "'.")));
-            return deferred->promise();
-        }
-        deferred->resolve(exec, jsString(exec, stringFromUTF(utf8.data())));
-        return deferred->promise();
+        if (!fillBufferWithContentsOfFile(moduleKey, utf8))
+            return deferred->reject(exec, createError(exec, makeString("Could not open file '", moduleKey, "'.")));
+
+        return deferred->resolve(exec, jsString(exec, stringFromUTF(utf8.data())));
     }
 };
 
 const ClassInfo GlobalObject::s_info = { "global", &JSGlobalObject::s_info, nullptr, CREATE_METHOD_TABLE(GlobalObject) };
-const GlobalObjectMethodTable GlobalObject::s_globalObjectMethodTable = { &allowsAccessFrom, &supportsProfiling, &supportsRichSourceInfo, &shouldInterruptScript, &javaScriptRuntimeFlags, 0, &shouldInterruptScriptBeforeTimeout, nullptr, &moduleLoaderFetch, nullptr, nullptr };
+const GlobalObjectMethodTable GlobalObject::s_globalObjectMethodTable = { &allowsAccessFrom, &supportsProfiling, &supportsRichSourceInfo, &shouldInterruptScript, &javaScriptRuntimeFlags, 0, &shouldInterruptScriptBeforeTimeout, &moduleLoaderResolve, &moduleLoaderFetch, nullptr, nullptr };
 
 
 GlobalObject::GlobalObject(VM& vm, Structure* structure)
@@ -725,6 +729,129 @@ GlobalObject::GlobalObject(VM& vm, Structure* structure)
 {
 }
 
+static UChar pathSeparator()
+{
+#if OS(WINDOWS)
+    return '\\';
+#else
+    return '/';
+#endif
+}
+
+struct DirectoryName {
+    // In unix, it is "/". In Windows, it becomes a drive letter like "C:\"
+    String rootName;
+
+    // If the directory name is "/home/WebKit", this becomes "home/WebKit". If the directory name is "/", this becomes "".
+    String queryName;
+};
+
+static bool extractDirectoryName(const String& absolutePathToFile, DirectoryName& directoryName)
+{
+    size_t firstSeparatorPosition = absolutePathToFile.find(pathSeparator());
+    if (firstSeparatorPosition == notFound)
+        return false;
+    directoryName.rootName = absolutePathToFile.substring(0, firstSeparatorPosition + 1); // Include the separator.
+    size_t lastSeparatorPosition = absolutePathToFile.reverseFind(pathSeparator());
+    ASSERT_WITH_MESSAGE(lastSeparatorPosition != notFound, "If the separator is not found, this function already returns when performing the forward search.");
+    if (firstSeparatorPosition == lastSeparatorPosition)
+        directoryName.queryName = StringImpl::empty();
+    else
+        directoryName.queryName = absolutePathToFile.substring(firstSeparatorPosition + 1, lastSeparatorPosition); // Not include the separator.
+    return true;
+}
+
+static bool currentWorkingDirectory(DirectoryName& directoryName)
+{
+#if OS(WINDOWS)
+    // https://msdn.microsoft.com/en-us/library/sf98bd4y.aspx
+    char* buffer = nullptr;
+    if (!(buffer = _getcwd(nullptr, 0)))
+        return false;
+    String directoryString = String::fromUTF8(buffer);
+    free(buffer);
+#else
+    auto buffer = std::make_unique<char[]>(PATH_MAX);
+    if (!getcwd(buffer.get(), PATH_MAX))
+        return false;
+    String directoryString = String::fromUTF8(buffer.get());
+#endif
+    if (directoryString.isEmpty())
+        return false;
+
+    if (directoryString[directoryString.length() - 1] == pathSeparator())
+        return extractDirectoryName(directoryString, directoryName);
+    // Append the seperator to represents the file name. extractDirectoryName only accepts the absolute file name.
+    return extractDirectoryName(makeString(directoryString, pathSeparator()), directoryName);
+}
+
+static String resolvePath(const DirectoryName& directoryName, const String& query)
+{
+    Vector<String> directoryPieces;
+    directoryName.queryName.split(pathSeparator(), false, directoryPieces);
+
+    Vector<String> queryPieces;
+    query.split(pathSeparator(), true, queryPieces);
+
+    // Only first '/' is recognized as the path from the root.
+    if (!queryPieces.isEmpty() && queryPieces[0].isEmpty())
+        directoryPieces.clear();
+
+    for (const auto& query : queryPieces) {
+        if (query == String(ASCIILiteral(".."))) {
+            if (!directoryPieces.isEmpty())
+                directoryPieces.removeLast();
+        } else if (!query.isEmpty() && query != String(ASCIILiteral(".")))
+            directoryPieces.append(query);
+    }
+
+    StringBuilder builder;
+    builder.append(directoryName.rootName);
+    for (size_t i = 0; i < directoryPieces.size(); ++i) {
+        builder.append(directoryPieces[i]);
+        if (i + 1 != directoryPieces.size())
+            builder.append(pathSeparator());
+    }
+    return builder.toString();
+}
+
+JSInternalPromise* GlobalObject::moduleLoaderResolve(JSGlobalObject* globalObject, ExecState* exec, JSValue keyValue, JSValue referrerValue)
+{
+    JSInternalPromiseDeferred* deferred = JSInternalPromiseDeferred::create(exec, globalObject);
+    const Identifier key = keyValue.toPropertyKey(exec);
+    if (exec->hadException()) {
+        JSValue exception = exec->exception();
+        exec->clearException();
+        return deferred->reject(exec, exception);
+    }
+
+    if (key.isSymbol())
+        return deferred->resolve(exec, keyValue);
+
+    DirectoryName directoryName;
+    if (referrerValue.isUndefined()) {
+        if (!currentWorkingDirectory(directoryName))
+            return deferred->reject(exec, createError(exec, ASCIILiteral("Could not resolve the current working directory.")));
+    } else {
+        const Identifier referrer = referrerValue.toPropertyKey(exec);
+        if (exec->hadException()) {
+            JSValue exception = exec->exception();
+            exec->clearException();
+            return deferred->reject(exec, exception);
+        }
+        if (referrer.isSymbol()) {
+            if (!currentWorkingDirectory(directoryName))
+                return deferred->reject(exec, createError(exec, ASCIILiteral("Could not resolve the current working directory.")));
+        } else {
+            // If the referrer exists, we assume that the referrer is the correct absolute path.
+            if (!extractDirectoryName(referrer.impl(), directoryName))
+                return deferred->reject(exec, createError(exec, makeString("Could not resolve the referrer name '", String(referrer.impl()), "'.")));
+        }
+    }
+
+    return deferred->resolve(exec, jsString(exec, resolvePath(directoryName, key.impl())));
+}
+
 EncodedJSValue JSC_HOST_CALL functionPrint(ExecState* exec)
 {
     for (unsigned i = 0; i < exec->argumentCount(); ++i) {
@@ -1202,6 +1329,12 @@ EncodedJSValue JSC_HOST_CALL functionEnableExceptionFuzz(ExecState*)
     return JSValue::encode(jsUndefined());
 }
 
+EncodedJSValue JSC_HOST_CALL functionDrainMicrotasks(ExecState* exec)
+{
+    exec->vm().drainMicrotasks();
+    return JSValue::encode(jsUndefined());
+}
+
 #if ENABLE(WEBASSEMBLY)
 EncodedJSValue JSC_HOST_CALL functionLoadWebAssembly(ExecState* exec)
 {