ModuleLoader.cpp (23369B)
1 /* -*- Mode: javascript; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 2 * -*- */ 3 /* This Source Code Form is subject to the terms of the Mozilla Public 4 * License, v. 2.0. If a copy of the MPL was not distributed with this 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 7 #include "shell/ModuleLoader.h" 8 9 #include "mozilla/TextUtils.h" 10 11 #include "jsapi.h" 12 #include "NamespaceImports.h" 13 14 #include "builtin/TestingUtility.h" // js::CreateScriptPrivate 15 #include "js/Conversions.h" 16 #include "js/MapAndSet.h" 17 #include "js/Modules.h" 18 #include "js/PropertyAndElement.h" // JS_DefineProperty, JS_GetProperty 19 #include "js/SourceText.h" 20 #include "js/StableStringChars.h" 21 #include "shell/jsshell.h" 22 #include "shell/OSObject.h" 23 #include "shell/StringUtils.h" 24 #include "util/Text.h" 25 #include "vm/JSAtomUtils.h" // AtomizeString, PinAtom 26 #include "vm/JSContext.h" 27 #include "vm/StringType.h" 28 29 #include "vm/NativeObject-inl.h" 30 31 using namespace js; 32 using namespace js::shell; 33 34 static constexpr char16_t JavaScriptScheme[] = u"javascript:"; 35 36 static bool IsJavaScriptURL(Handle<JSLinearString*> path) { 37 return StringStartsWith(path, JavaScriptScheme); 38 } 39 40 static JSString* ExtractJavaScriptURLSource(JSContext* cx, 41 Handle<JSLinearString*> path) { 42 MOZ_ASSERT(IsJavaScriptURL(path)); 43 44 const size_t schemeLength = js_strlen(JavaScriptScheme); 45 return SubString(cx, path, schemeLength); 46 } 47 48 bool ModuleLoader::init(JSContext* cx, HandleString loadPath) { 49 loadPathStr = AtomizeString(cx, loadPath); 50 if (!loadPathStr || !PinAtom(cx, loadPathStr)) { 51 return false; 52 } 53 54 MOZ_ASSERT(IsAbsolutePath(loadPathStr)); 55 56 char16_t sep = PathSeparator; 57 pathSeparatorStr = AtomizeChars(cx, &sep, 1); 58 if (!pathSeparatorStr || !PinAtom(cx, pathSeparatorStr)) { 59 return false; 60 } 61 62 JSRuntime* rt = cx->runtime(); 63 JS::SetModuleLoadHook(rt, ModuleLoader::LoadImportedModule); 64 JS::SetModuleMetadataHook(rt, ModuleLoader::GetImportMetaProperties); 65 return true; 66 } 67 68 // static 69 bool ModuleLoader::LoadImportedModule(JSContext* cx, 70 JS::Handle<JSScript*> referrer, 71 JS::Handle<JSObject*> moduleRequest, 72 JS::HandleValue hostDefined, 73 JS::HandleValue payload, 74 uint32_t lineNumber, 75 JS::ColumnNumberOneOrigin columnNumber) { 76 ShellContext* scx = GetShellContext(cx); 77 return scx->moduleLoader->loadImportedModule(cx, referrer, moduleRequest, 78 payload); 79 } 80 81 // static 82 bool ModuleLoader::GetImportMetaProperties(JSContext* cx, 83 JS::HandleValue privateValue, 84 JS::HandleObject metaObject) { 85 ShellContext* scx = GetShellContext(cx); 86 return scx->moduleLoader->populateImportMeta(cx, privateValue, metaObject); 87 } 88 89 bool ModuleLoader::ImportMetaResolve(JSContext* cx, unsigned argc, Value* vp) { 90 CallArgs args = CallArgsFromVp(argc, vp); 91 RootedValue modulePrivate( 92 cx, js::GetFunctionNativeReserved(&args.callee(), ModulePrivateSlot)); 93 94 // https://html.spec.whatwg.org/#hostgetimportmetaproperties 95 // Step 4.1. Set specifier to ? ToString(specifier). 96 // 97 // https://tc39.es/ecma262/#sec-tostring 98 RootedValue v(cx, args.get(ImportMetaResolveSpecifierArg)); 99 RootedString specifier(cx, JS::ToString(cx, v)); 100 if (!specifier) { 101 return false; 102 } 103 104 // Step 4.2, 4.3 are implemented in importMetaResolve. 105 ShellContext* scx = GetShellContext(cx); 106 RootedString url(cx); 107 if (!scx->moduleLoader->importMetaResolve(cx, modulePrivate, specifier, 108 &url)) { 109 return false; 110 } 111 112 // Step 4.4. Return the serialization of url. 113 args.rval().setString(url); 114 return true; 115 } 116 117 bool ModuleLoader::loadRootModule(JSContext* cx, HandleString path) { 118 RootedValue rval(cx); 119 if (!loadAndExecute(cx, path, nullptr, &rval)) { 120 return false; 121 } 122 123 RootedObject evaluationPromise(cx, &rval.toObject()); 124 if (evaluationPromise == nullptr) { 125 return false; 126 } 127 128 return JS::ThrowOnModuleEvaluationFailure(cx, evaluationPromise); 129 } 130 131 bool ModuleLoader::registerTestModule(JSContext* cx, HandleObject moduleRequest, 132 Handle<ModuleObject*> module) { 133 Rooted<JSLinearString*> path(cx, resolve(cx, moduleRequest, nullptr)); 134 if (!path) { 135 return false; 136 } 137 138 path = normalizePath(cx, path); 139 if (!path) { 140 return false; 141 } 142 143 JS::ModuleType moduleType = 144 moduleRequest->as<ModuleRequestObject>().moduleType(); 145 146 return addModuleToRegistry(cx, moduleType, path, module); 147 } 148 149 void ModuleLoader::clearModules(JSContext* cx) { 150 Handle<GlobalObject*> global = cx->global(); 151 global->setReservedSlot(GlobalAppSlotModuleRegistry, UndefinedValue()); 152 } 153 154 bool ModuleLoader::loadAndExecute(JSContext* cx, HandleString path, 155 HandleObject moduleRequestArg, 156 MutableHandleValue rval) { 157 RootedObject module(cx, loadAndParse(cx, path, moduleRequestArg)); 158 if (!module) { 159 return false; 160 } 161 162 return loadAndExecute(cx, module, rval); 163 } 164 165 bool ModuleLoader::loadAndExecute(JSContext* cx, HandleObject module, 166 MutableHandleValue rval) { 167 MOZ_ASSERT(module); 168 169 RootedValue hostDefined(cx, ObjectValue(*module)); 170 if (!JS::LoadRequestedModules(cx, module, hostDefined, LoadResolved, 171 LoadRejected)) { 172 return false; 173 } 174 175 if (JS_IsExceptionPending(cx)) { 176 return false; 177 } 178 179 return JS::ModuleEvaluate(cx, module, rval); 180 } 181 182 /* static */ 183 bool ModuleLoader::LoadResolved(JSContext* cx, HandleValue hostDefined) { 184 RootedObject module(cx, &hostDefined.toObject()); 185 return JS::ModuleLink(cx, module); 186 } 187 188 /* static */ 189 bool ModuleLoader::LoadRejected(JSContext* cx, HandleValue hostDefined, 190 HandleValue error) { 191 JS_SetPendingException(cx, error); 192 return true; 193 } 194 195 bool ModuleLoader::loadImportedModule(JSContext* cx, 196 JS::Handle<JSScript*> referrer, 197 JS::Handle<JSObject*> moduleRequest, 198 JS::HandleValue payload) { 199 // TODO: Bug 1968904: Update HostLoadImportedModule 200 if (payload.isObject() && payload.toObject().is<PromiseObject>()) { 201 // This is a dynamic import. 202 return dynamicImport(cx, referrer, moduleRequest, payload); 203 } 204 205 Rooted<JSLinearString*> path(cx, resolve(cx, moduleRequest, referrer)); 206 if (!path) { 207 return false; 208 } 209 210 RootedObject module(cx, loadAndParse(cx, path, moduleRequest)); 211 if (!module) { 212 return false; 213 } 214 215 return JS::FinishLoadingImportedModule(cx, referrer, moduleRequest, payload, 216 module, false); 217 } 218 219 bool ModuleLoader::populateImportMeta(JSContext* cx, 220 JS::HandleValue privateValue, 221 JS::HandleObject metaObject) { 222 Rooted<JSLinearString*> path(cx); 223 if (!privateValue.isUndefined()) { 224 if (!getScriptPath(cx, privateValue, &path)) { 225 return false; 226 } 227 } 228 229 if (!path) { 230 path = NewStringCopyZ<CanGC>(cx, "(unknown)"); 231 if (!path) { 232 return false; 233 } 234 } 235 236 RootedValue pathValue(cx, StringValue(path)); 237 if (!JS_DefineProperty(cx, metaObject, "url", pathValue, JSPROP_ENUMERATE)) { 238 return false; 239 } 240 241 JSFunction* resolveFunc = js::DefineFunctionWithReserved( 242 cx, metaObject, "resolve", ImportMetaResolve, ImportMetaResolveNumArgs, 243 JSPROP_ENUMERATE); 244 if (!resolveFunc) { 245 return false; 246 } 247 248 RootedObject resolveFuncObj(cx, JS_GetFunctionObject(resolveFunc)); 249 js::SetFunctionNativeReserved(resolveFuncObj, ModulePrivateSlot, 250 privateValue); 251 252 return true; 253 } 254 255 bool ModuleLoader::importMetaResolve(JSContext* cx, 256 JS::Handle<JS::Value> referencingPrivate, 257 JS::Handle<JSString*> specifier, 258 JS::MutableHandle<JSString*> urlOut) { 259 Rooted<JSLinearString*> path(cx, resolve(cx, specifier, referencingPrivate)); 260 if (!path) { 261 return false; 262 } 263 264 urlOut.set(path); 265 return true; 266 } 267 268 bool ModuleLoader::dynamicImport(JSContext* cx, JS::HandleScript referrer, 269 JS::HandleObject moduleRequest, 270 JS::HandleValue payload) { 271 // To make this more realistic, use a promise to delay the import and make it 272 // happen asynchronously. This method packages up the arguments and creates a 273 // resolved promise, which on fullfillment calls doDynamicImport with the 274 // original arguments. 275 276 RootedValue moduleRequestValue(cx, ObjectValue(*moduleRequest)); 277 RootedObject closure(cx, JS_NewObjectWithGivenProto(cx, nullptr, nullptr)); 278 RootedValue referrerValue(cx); 279 if (referrer) { 280 referrerValue = PrivateGCThingValue(referrer); 281 } else { 282 RootedScript script(cx); 283 const char* filename; 284 uint32_t lineno; 285 uint32_t pcOffset; 286 bool mutedErrors; 287 DescribeScriptedCallerForCompilation(cx, &script, &filename, &lineno, 288 &pcOffset, &mutedErrors); 289 MOZ_ASSERT(script); 290 referrerValue = PrivateGCThingValue(script); 291 } 292 MOZ_ASSERT(!referrerValue.isUndefined()); 293 294 if (!closure || 295 !JS_DefineProperty(cx, closure, "referrer", referrerValue, 296 JSPROP_ENUMERATE) || 297 !JS_DefineProperty(cx, closure, "moduleRequest", moduleRequestValue, 298 JSPROP_ENUMERATE) || 299 !JS_DefineProperty(cx, closure, "payload", payload, JSPROP_ENUMERATE)) { 300 return false; 301 } 302 303 RootedFunction onResolved( 304 cx, NewNativeFunction(cx, DynamicImportDelayFulfilled, 1, nullptr)); 305 if (!onResolved) { 306 return false; 307 } 308 309 RootedFunction onRejected( 310 cx, NewNativeFunction(cx, DynamicImportDelayRejected, 1, nullptr)); 311 if (!onRejected) { 312 return false; 313 } 314 315 RootedObject delayPromise(cx); 316 RootedValue closureValue(cx, ObjectValue(*closure)); 317 delayPromise = PromiseObject::unforgeableResolve(cx, closureValue); 318 if (!delayPromise) { 319 return false; 320 } 321 322 return JS::AddPromiseReactions(cx, delayPromise, onResolved, onRejected); 323 } 324 325 bool ModuleLoader::DynamicImportDelayFulfilled(JSContext* cx, unsigned argc, 326 Value* vp) { 327 CallArgs args = CallArgsFromVp(argc, vp); 328 RootedObject closure(cx, &args[0].toObject()); 329 330 RootedValue referrerValue(cx); 331 RootedValue moduleRequestValue(cx); 332 RootedValue payload(cx); 333 if (!JS_GetProperty(cx, closure, "referrer", &referrerValue) || 334 !JS_GetProperty(cx, closure, "moduleRequest", &moduleRequestValue) || 335 !JS_GetProperty(cx, closure, "payload", &payload)) { 336 return false; 337 } 338 339 RootedObject moduleRequest(cx, &moduleRequestValue.toObject()); 340 RootedScript referrer(cx, static_cast<JSScript*>(referrerValue.toGCThing())); 341 342 ShellContext* scx = GetShellContext(cx); 343 return scx->moduleLoader->doDynamicImport(cx, referrer, moduleRequest, 344 payload); 345 } 346 347 bool ModuleLoader::DynamicImportDelayRejected(JSContext* cx, unsigned argc, 348 Value* vp) { 349 MOZ_CRASH("This promise should never be rejected"); 350 } 351 352 bool ModuleLoader::doDynamicImport(JSContext* cx, JS::HandleScript referrer, 353 JS::HandleObject moduleRequest, 354 JS::HandleValue payload) { 355 // Exceptions during dynamic import are handled by calling 356 // FinishLoadingImportedModule with a pending exception on the context. 357 Rooted<JSLinearString*> path(cx, resolve(cx, moduleRequest, referrer)); 358 if (!path) { 359 return JS::FinishLoadingImportedModuleFailedWithPendingException(cx, 360 payload); 361 } 362 363 RootedObject module(cx, loadAndParse(cx, path, moduleRequest)); 364 if (!module) { 365 return JS::FinishLoadingImportedModuleFailedWithPendingException(cx, 366 payload); 367 } 368 369 RootedValue hostDefined(cx, ObjectValue(*module)); 370 if (!JS::LoadRequestedModules(cx, module, hostDefined, LoadResolved, 371 LoadRejected)) { 372 return JS::FinishLoadingImportedModuleFailedWithPendingException(cx, 373 payload); 374 } 375 376 if (JS_IsExceptionPending(cx)) { 377 return JS::FinishLoadingImportedModuleFailedWithPendingException(cx, 378 payload); 379 } 380 381 return JS::FinishLoadingImportedModule(cx, nullptr, moduleRequest, payload, 382 module, false); 383 } 384 385 JSLinearString* ModuleLoader::resolve(JSContext* cx, 386 HandleObject moduleRequestArg, 387 HandleScript referrer) { 388 RootedValue referencingInfo(cx); 389 if (referrer) { 390 referencingInfo = GetScriptPrivate(referrer); 391 } 392 393 ModuleRequestObject* moduleRequest = 394 &moduleRequestArg->as<ModuleRequestObject>(); 395 if (moduleRequest->specifier()->length() == 0) { 396 JS_ReportErrorASCII(cx, "Invalid module specifier"); 397 return nullptr; 398 } 399 400 Rooted<JSLinearString*> name( 401 cx, JS_EnsureLinearString(cx, moduleRequest->specifier())); 402 if (!name) { 403 return nullptr; 404 } 405 406 return resolve(cx, name, referencingInfo); 407 } 408 409 JSLinearString* ModuleLoader::resolve(JSContext* cx, HandleString specifier, 410 HandleValue referencingInfo) { 411 Rooted<JSLinearString*> name(cx, JS_EnsureLinearString(cx, specifier)); 412 if (!name) { 413 return nullptr; 414 } 415 416 if (IsJavaScriptURL(name) || IsAbsolutePath(name)) { 417 return name; 418 } 419 420 // Treat |name| as a relative path if it starts with either "./" or "../". 421 bool isRelative = 422 StringStartsWith(name, u"./") || StringStartsWith(name, u"../") 423 #ifdef XP_WIN 424 || StringStartsWith(name, u".\\") || StringStartsWith(name, u"..\\") 425 #endif 426 ; 427 428 RootedString path(cx, loadPathStr); 429 430 if (isRelative) { 431 if (referencingInfo.isUndefined()) { 432 JS_ReportErrorASCII(cx, "No referencing module for relative import"); 433 return nullptr; 434 } 435 436 Rooted<JSLinearString*> refPath(cx); 437 if (!getScriptPath(cx, referencingInfo, &refPath)) { 438 return nullptr; 439 } 440 441 if (!refPath) { 442 JS_ReportErrorASCII(cx, "No path set for referencing module"); 443 return nullptr; 444 } 445 446 int32_t sepIndex = LastIndexOf(refPath, u'/'); 447 #ifdef XP_WIN 448 sepIndex = std::max(sepIndex, LastIndexOf(refPath, u'\\')); 449 #endif 450 if (sepIndex >= 0) { 451 path = SubString(cx, refPath, 0, sepIndex); 452 if (!path) { 453 return nullptr; 454 } 455 } 456 } 457 458 RootedString result(cx); 459 RootedString pathSep(cx, pathSeparatorStr); 460 result = JS_ConcatStrings(cx, path, pathSep); 461 if (!result) { 462 return nullptr; 463 } 464 465 result = JS_ConcatStrings(cx, result, name); 466 if (!result) { 467 return nullptr; 468 } 469 470 Rooted<JSLinearString*> linear(cx, JS_EnsureLinearString(cx, result)); 471 if (!linear) { 472 return nullptr; 473 } 474 return normalizePath(cx, linear); 475 } 476 477 JSObject* ModuleLoader::loadAndParse(JSContext* cx, HandleString pathArg, 478 JS::HandleObject moduleRequestArg) { 479 Rooted<JSLinearString*> path(cx, JS_EnsureLinearString(cx, pathArg)); 480 if (!path) { 481 return nullptr; 482 } 483 484 path = normalizePath(cx, path); 485 if (!path) { 486 return nullptr; 487 } 488 489 JS::ModuleType moduleType = JS::ModuleType::JavaScript; 490 if (moduleRequestArg) { 491 moduleType = moduleRequestArg->as<ModuleRequestObject>().moduleType(); 492 } 493 494 RootedObject module(cx); 495 if (!lookupModuleInRegistry(cx, moduleType, path, &module)) { 496 return nullptr; 497 } 498 499 if (module) { 500 return module; 501 } 502 503 UniqueChars filename = JS_EncodeStringToUTF8(cx, path); 504 if (!filename) { 505 return nullptr; 506 } 507 508 JS::CompileOptions options(cx); 509 options.setFileAndLine(filename.get(), 1); 510 511 RootedString source(cx, fetchSource(cx, path)); 512 if (!source) { 513 return nullptr; 514 } 515 516 JS::AutoStableStringChars linearChars(cx); 517 if (!linearChars.initTwoByte(cx, source)) { 518 return nullptr; 519 } 520 521 JS::SourceText<char16_t> srcBuf; 522 if (!srcBuf.initMaybeBorrowed(cx, linearChars)) { 523 return nullptr; 524 } 525 526 switch (moduleType) { 527 case JS::ModuleType::Unknown: 528 case JS::ModuleType::Bytes: 529 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, 530 JSMSG_BAD_MODULE_TYPE); 531 return nullptr; 532 case JS::ModuleType::JavaScript: { 533 module = JS::CompileModule(cx, options, srcBuf); 534 if (!module) { 535 return nullptr; 536 } 537 538 RootedObject info(cx, js::CreateScriptPrivate(cx, path)); 539 if (!info) { 540 return nullptr; 541 } 542 543 JS::SetModulePrivate(module, ObjectValue(*info)); 544 } break; 545 case JS::ModuleType::JSON: 546 module = JS::CompileJsonModule(cx, options, srcBuf); 547 if (!module) { 548 return nullptr; 549 } 550 break; 551 case JS::ModuleType::CSS: 552 // We don't support CSS modules in the shell because we don't have access 553 // to a CSS parser in standalone shell builds. 554 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, 555 JSMSG_BAD_MODULE_TYPE); 556 return nullptr; 557 } 558 559 if (!addModuleToRegistry(cx, moduleType, path, module)) { 560 return nullptr; 561 } 562 563 return module; 564 } 565 566 bool ModuleLoader::lookupModuleInRegistry(JSContext* cx, 567 JS::ModuleType moduleType, 568 HandleString path, 569 MutableHandleObject moduleOut) { 570 moduleOut.set(nullptr); 571 572 RootedObject registry(cx, getOrCreateModuleRegistry(cx, moduleType)); 573 if (!registry) { 574 return false; 575 } 576 577 RootedValue pathValue(cx, StringValue(path)); 578 RootedValue moduleValue(cx); 579 if (!JS::MapGet(cx, registry, pathValue, &moduleValue)) { 580 return false; 581 } 582 583 if (!moduleValue.isUndefined()) { 584 moduleOut.set(&moduleValue.toObject()); 585 } 586 587 return true; 588 } 589 590 bool ModuleLoader::addModuleToRegistry(JSContext* cx, JS::ModuleType moduleType, 591 HandleString path, HandleObject module) { 592 RootedObject registry(cx, getOrCreateModuleRegistry(cx, moduleType)); 593 if (!registry) { 594 return false; 595 } 596 597 RootedValue pathValue(cx, StringValue(path)); 598 RootedValue moduleValue(cx, ObjectValue(*module)); 599 return JS::MapSet(cx, registry, pathValue, moduleValue); 600 } 601 602 static ArrayObject* GetOrCreateRootRegistry(JSContext* cx) { 603 Handle<GlobalObject*> global = cx->global(); 604 RootedValue value(cx, global->getReservedSlot(GlobalAppSlotModuleRegistry)); 605 if (!value.isUndefined()) { 606 return &value.toObject().as<ArrayObject>(); 607 } 608 609 uint32_t numberOfModuleTypes = uint32_t(JS::ModuleType::Limit) + 1; 610 611 Rooted<ArrayObject*> registry( 612 cx, NewDenseFullyAllocatedArray(cx, numberOfModuleTypes, TenuredObject)); 613 if (!registry) { 614 return nullptr; 615 } 616 registry->ensureDenseInitializedLength(0, numberOfModuleTypes); 617 618 Rooted<JSObject*> innerRegistry(cx); 619 for (size_t i = 0; i < numberOfModuleTypes; ++i) { 620 innerRegistry = JS::NewMapObject(cx); 621 if (!innerRegistry) { 622 return nullptr; 623 } 624 registry->initDenseElement(i, ObjectValue(*innerRegistry)); 625 } 626 627 global->setReservedSlot(GlobalAppSlotModuleRegistry, ObjectValue(*registry)); 628 629 return registry; 630 } 631 632 JSObject* ModuleLoader::getOrCreateModuleRegistry(JSContext* cx, 633 JS::ModuleType moduleType) { 634 Rooted<ArrayObject*> rootRegistry(cx, GetOrCreateRootRegistry(cx)); 635 if (!rootRegistry) { 636 return nullptr; 637 } 638 639 uint32_t index = uint32_t(moduleType); 640 MOZ_ASSERT(rootRegistry->containsDenseElement(index)); 641 return &rootRegistry->getDenseElement(index).toObject(); 642 } 643 644 bool ModuleLoader::getScriptPath(JSContext* cx, HandleValue privateValue, 645 MutableHandle<JSLinearString*> pathOut) { 646 pathOut.set(nullptr); 647 648 RootedObject infoObj(cx, &privateValue.toObject()); 649 RootedValue pathValue(cx); 650 if (!JS_GetProperty(cx, infoObj, "path", &pathValue)) { 651 return false; 652 } 653 654 if (pathValue.isUndefined()) { 655 return true; 656 } 657 658 RootedString path(cx, pathValue.toString()); 659 pathOut.set(JS_EnsureLinearString(cx, path)); 660 return pathOut; 661 } 662 663 JSLinearString* ModuleLoader::normalizePath(JSContext* cx, 664 Handle<JSLinearString*> pathArg) { 665 Rooted<JSLinearString*> path(cx, pathArg); 666 667 if (IsJavaScriptURL(path)) { 668 return path; 669 } 670 671 #ifdef XP_WIN 672 // Replace all forward slashes with backward slashes. 673 path = ReplaceCharGlobally(cx, path, u'/', PathSeparator); 674 if (!path) { 675 return nullptr; 676 } 677 678 // Remove the drive letter, if present. 679 Rooted<JSLinearString*> drive(cx); 680 if (path->length() > 2 && mozilla::IsAsciiAlpha(CharAt(path, 0)) && 681 CharAt(path, 1) == u':' && CharAt(path, 2) == u'\\') { 682 drive = SubString(cx, path, 0, 2); 683 path = SubString(cx, path, 2); 684 if (!drive || !path) { 685 return nullptr; 686 } 687 } 688 #endif // XP_WIN 689 690 // Normalize the path by removing redundant path components. 691 Rooted<GCVector<JSLinearString*>> components(cx, cx); 692 size_t lastSep = 0; 693 while (lastSep < path->length()) { 694 int32_t i = IndexOf(path, PathSeparator, lastSep); 695 if (i < 0) { 696 i = path->length(); 697 } 698 699 Rooted<JSLinearString*> part(cx, SubString(cx, path, lastSep, i)); 700 if (!part) { 701 return nullptr; 702 } 703 704 lastSep = i + 1; 705 706 // Remove "." when preceded by a path component. 707 if (StringEquals(part, u".") && !components.empty()) { 708 continue; 709 } 710 711 if (StringEquals(part, u"..") && !components.empty()) { 712 // Replace "./.." with "..". 713 if (StringEquals(components.back(), u".")) { 714 components.back() = part; 715 continue; 716 } 717 718 // When preceded by a non-empty path component, remove ".." and the 719 // preceding component, unless the preceding component is also "..". 720 if (!StringEquals(components.back(), u"") && 721 !StringEquals(components.back(), u"..")) { 722 components.popBack(); 723 continue; 724 } 725 } 726 727 if (!components.append(part)) { 728 return nullptr; 729 } 730 } 731 732 Rooted<JSLinearString*> pathSep(cx, pathSeparatorStr); 733 RootedString normalized(cx, JoinStrings(cx, components, pathSep)); 734 if (!normalized) { 735 return nullptr; 736 } 737 738 #ifdef XP_WIN 739 if (drive) { 740 normalized = JS_ConcatStrings(cx, drive, normalized); 741 if (!normalized) { 742 return nullptr; 743 } 744 } 745 #endif 746 747 return JS_EnsureLinearString(cx, normalized); 748 } 749 750 JSString* ModuleLoader::fetchSource(JSContext* cx, 751 Handle<JSLinearString*> path) { 752 if (IsJavaScriptURL(path)) { 753 return ExtractJavaScriptURLSource(cx, path); 754 } 755 756 RootedString resolvedPath(cx, ResolvePath(cx, path, RootRelative)); 757 if (!resolvedPath) { 758 return nullptr; 759 } 760 761 return FileAsString(cx, resolvedPath); 762 }