diff --git a/tools/serve/serve.py b/tools/serve/serve.py
index e8c42129702ae1..30a9c6afbb4d05 100644
--- a/tools/serve/serve.py
+++ b/tools/serve/serve.py
@@ -24,6 +24,7 @@
from localpaths import repo_root # type: ignore
from manifest.sourcefile import read_script_metadata, js_meta_re, parse_variants # type: ignore
+from manifest.test262 import parse as test262_parse # type: ignore
from wptserve import server as wptserve, handlers
from wptserve import stash
from wptserve import config
@@ -327,6 +328,81 @@ class ExtensionHandler(HtmlWrapperHandler):
"""
+class Test262WindowHandler(HtmlWrapperHandler):
+ path_replace = [(".test262.html", ".js", ".test262-test.html")]
+ wrapper = """
+
+
Test
+
+
+%(meta)s
+%(script)s
+
+"""
+
+
+class Test262WindowTestBaseHandler(HtmlWrapperHandler):
+ # For SHAB
+ headers = [('Cross-Origin-Opener-Policy', 'same-origin'),
+ ('Cross-Origin-Embedder-Policy', 'require-corp')]
+
+ # Define a common HTML structure (testharness setup, etc.) that can be
+ # extended by subclasses. This avoids duplicating boilerplate. For example,
+ # Test262WindowModuleTestHandler reuses this `pre_wrapper` but appends a
+ # module script.
+ pre_wrapper = """
+
+Test
+
+
+
+
+%(meta)s
+%(script)s"""
+ wrapper = pre_wrapper + """
+
+"""
+
+ def _get_metadata(self, request):
+ path = self._get_filesystem_path(request)
+ with open(path, encoding='utf-8') as f:
+ test_record = test262_parse(logging.getLogger(), f.read(), path)
+ yield from (('script', "/third_party/test262/harness/%s" % filename)
+ for filename in (test_record.includes or []))
+
+ expected_error = (test_record.negative or {}).get('type', None)
+ if expected_error is not None:
+ yield ('negative', expected_error)
+
+ def _meta_replacement(self, key: str, value: str) -> Optional[str]:
+ if key == 'negative':
+ return """""" % value
+ return None
+
+
+class Test262WindowTestHandler(Test262WindowTestBaseHandler):
+ path_replace = [(".test262-test.html", ".js")]
+
+
+class Test262WindowModuleHandler(Test262WindowHandler):
+ path_replace = [(".test262-module.html", ".js", ".test262-module-test.html")]
+
+class Test262WindowModuleTestHandler(Test262WindowTestBaseHandler):
+ path_replace = [(".test262-module-test.html", ".js")]
+ wrapper = Test262WindowTestBaseHandler.pre_wrapper + """"""
+
+
+class Test262StrictWindowHandler(Test262WindowHandler):
+ path_replace = [(".test262.strict.html", ".js", ".test262-test.strict.html")]
+
+class Test262StrictWindowTestHandler(Test262WindowTestBaseHandler):
+ path_replace = [(".test262-test.strict.html", ".js", ".test262.strict.js")]
+
+
class WindowModulesHandler(HtmlWrapperHandler):
global_type = "window-module"
path_replace = [(".any.window-module.html", ".any.js")]
@@ -574,6 +650,30 @@ class ShadowRealmInAudioWorkletHandler(HtmlWrapperHandler):
"""
+class Test262StrictHandler(WrapperHandler):
+ path_replace = [(".test262.strict.js", ".js")]
+ headers = [('Content-Type', 'text/javascript')]
+ wrapper = """
+"use strict";
+%(script)s
+"""
+
+ def _meta_replacement(self, key, value):
+ return None
+
+ def _get_script(self, request):
+ """
+ Reads the entire content of the associated JavaScript file to be
+ prepended with "use strict".
+ """
+ path = self._get_filesystem_path(request)
+ try:
+ with open(path, encoding='utf-8') as f:
+ yield f.read()
+ except OSError:
+ raise HTTPException(404)
+
+
class BaseWorkerHandler(WrapperHandler):
headers = [("Content-Type", "text/javascript")]
@@ -787,6 +887,13 @@ def add_mount_point(self, url_base, path):
("GET", "*.worker.html", WorkersHandler),
("GET", "*.worker-module.html", WorkerModulesHandler),
("GET", "*.window.html", WindowHandler),
+ ("GET", "*.test262.html", Test262WindowHandler),
+ ("GET", "*.test262-test.html", Test262WindowTestHandler),
+ ("GET", "*.test262-module.html", Test262WindowModuleHandler),
+ ("GET", "*.test262-module-test.html", Test262WindowModuleTestHandler),
+ ("GET", "*.test262.strict.html", Test262StrictWindowHandler),
+ ("GET", "*.test262-test.strict.html", Test262StrictWindowTestHandler),
+ ("GET", "*.test262.strict.js", Test262StrictHandler),
("GET", "*.extension.html", ExtensionHandler),
("GET", "*.any.html", AnyHtmlHandler),
("GET", "*.any.sharedworker.html", SharedWorkersHandler),
diff --git a/tools/serve/test_serve.py b/tools/serve/test_serve.py
index 9dcda584de2ea4..dd47429a97ed24 100644
--- a/tools/serve/test_serve.py
+++ b/tools/serve/test_serve.py
@@ -1,15 +1,29 @@
# mypy: allow-untyped-defs
+import builtins
+import io
import logging
import os
import pickle
import platform
+from unittest.mock import MagicMock, patch
+from typing import Generator, List, Tuple, Type
import pytest
import localpaths # type: ignore
from . import serve
-from .serve import ConfigBuilder, inject_script
+from .serve import (
+ ConfigBuilder,
+ Test262WindowHandler,
+ Test262WindowTestHandler,
+ Test262WindowModuleHandler,
+ Test262WindowModuleTestHandler,
+ Test262StrictWindowHandler,
+ Test262StrictWindowTestHandler,
+ Test262StrictHandler,
+ WrapperHandler,
+ inject_script)
logger = logging.getLogger()
@@ -154,3 +168,188 @@ def test_inject_script_parse_error():
# On a parse error, the script should not be injected and the original content should be
# returned.
assert INJECT_SCRIPT_MARKER not in inject_script(html.replace(INJECT_SCRIPT_MARKER, b""), INJECT_SCRIPT_MARKER)
+
+
+@pytest.fixture
+def test262_handlers() -> Generator[Tuple[str, str], None, None]:
+ tests_root = os.path.abspath(os.path.join(os.path.dirname(__file__), "tests", "testdata"))
+ url_base = "/"
+
+ mock_file_contents = {
+ os.path.normpath(os.path.join(tests_root, "test262", "basic.js")): """/*---\ndescription: A basic test
+includes: [assert.js, sta.js]
+---*/
+assert.sameValue(1, 1);
+""",
+ os.path.normpath(os.path.join(tests_root, "test262", "negative.js")): """/*---\ndescription: A negative test
+negative:
+ phase: runtime
+ type: TypeError
+---*/
+throw new TypeError();
+""",
+ os.path.normpath(os.path.join(tests_root, "test262", "module.js")): """/*---\ndescription: A module test
+flags: [module]
+---*/
+import {} from 'some-module';
+""",
+ os.path.normpath(os.path.join(tests_root, "test262", "teststrict.js")): """/*---\ndescription: A strict mode test
+flags: [onlyStrict]
+includes: [propertyHelper.js]
+---*/
+console.log('hello');
+"""
+ }
+
+ # Store original functions to be called if our mock doesn't handle the file
+ original_open = builtins.open
+ original_exists = os.path.exists
+ original_isdir = os.path.isdir
+
+ def custom_open(file, mode='r', *args, **kwargs):
+ normalized_file = os.path.normpath(file)
+ if normalized_file in mock_file_contents:
+ if 'b' in mode:
+ return io.BytesIO(mock_file_contents[normalized_file].encode('utf-8'))
+ else:
+ return io.StringIO(mock_file_contents[normalized_file])
+ return original_open(file, mode, *args, **kwargs)
+
+ def custom_exists(path):
+ normalized_path = os.path.normpath(path)
+ return normalized_path in mock_file_contents or original_exists(path)
+
+ def custom_isdir(path):
+ normalized_path = os.path.normpath(path)
+ expected_dir = os.path.normpath(os.path.join(tests_root, "test262"))
+ return normalized_path == expected_dir or original_isdir(path)
+
+ with patch('builtins.open', side_effect=custom_open), \
+ patch('os.path.exists', side_effect=custom_exists), \
+ patch('os.path.isdir', side_effect=custom_isdir):
+ yield tests_root, url_base
+
+
+def _create_mock_request(path: str) -> MagicMock:
+ mock_request = MagicMock()
+ mock_request.url_parts.path = path
+ mock_request.url_parts.query = ""
+ return mock_request
+
+
+def _test_handler_path_replace(handler_cls: Type[WrapperHandler],
+ tests_root: str,
+ url_base: str,
+ expected: List[Tuple[str, str]]) -> None:
+ handler = handler_cls(base_path=tests_root, url_base=url_base)
+ assert handler.path_replace == expected
+
+def _test_handler_wrapper_content(handler_cls: Type[WrapperHandler],
+ tests_root: str,
+ url_base: str,
+ request_path: str,
+ expected_content: List[str]) -> None:
+ handler = handler_cls(base_path=tests_root, url_base=url_base)
+ mock_request = _create_mock_request(request_path)
+ mock_response = MagicMock()
+ handler.handle_request(mock_request, mock_response) # type: ignore[no-untyped-call]
+ content = mock_response.content
+ for item in expected_content:
+ assert item in content
+
+def _test_handler_get_metadata(handler_cls: Type[WrapperHandler],
+ tests_root: str,
+ url_base: str,
+ request_path: str,
+ expected_metadata: List[Tuple[str, str]]) -> None:
+ handler = handler_cls(tests_root, url_base)
+ mock_request = _create_mock_request(request_path)
+ metadata = list(handler._get_metadata(mock_request)) # type: ignore[no-untyped-call]
+ for item in expected_metadata:
+ assert item in metadata
+ assert len(expected_metadata) == len(metadata), f"{expected_metadata} != {metadata}"
+
+Test262WindowHandler.__test__ = False # type: ignore[attr-defined]
+Test262WindowTestHandler.__test__ = False # type: ignore[attr-defined]
+Test262WindowModuleHandler.__test__ = False # type: ignore[attr-defined]
+Test262WindowModuleTestHandler.__test__ = False # type: ignore[attr-defined]
+Test262StrictWindowHandler.__test__ = False # type: ignore[attr-defined]
+Test262StrictWindowTestHandler.__test__ = False # type: ignore[attr-defined]
+Test262StrictHandler.__test__ = False # type: ignore[attr-defined]
+
+@pytest.mark.parametrize("handler_cls, expected", [
+ (Test262WindowHandler, [(".test262.html", ".js", ".test262-test.html")]),
+ (Test262WindowTestHandler, [(".test262-test.html", ".js")]),
+ (Test262WindowModuleHandler, [(".test262-module.html", ".js", ".test262-module-test.html")]),
+ (Test262WindowModuleTestHandler, [(".test262-module-test.html", ".js")]),
+ (Test262StrictWindowHandler, [(".test262.strict.html", ".js", ".test262-test.strict.html")]),
+ (Test262StrictWindowTestHandler, [(".test262-test.strict.html", ".js", ".test262.strict.js")]),
+])
+def test_path_replace(test262_handlers, handler_cls, expected):
+ tests_root, url_base = test262_handlers
+ _test_handler_path_replace(handler_cls, tests_root, url_base, expected)
+
+
+@pytest.mark.parametrize("handler_cls, request_path, expected_metadata", [
+ (
+ Test262WindowTestHandler,
+ "/test262/basic.test262-test.html",
+ [('script', '/third_party/test262/harness/assert.js'), ('script', '/third_party/test262/harness/sta.js')]
+ ),
+ (
+ Test262WindowTestHandler,
+ "/test262/negative.test262-test.html",
+ [('negative', 'TypeError')]
+ ),
+ (
+ Test262StrictWindowTestHandler,
+ "/test262/teststrict.test262-test.strict.html",
+ [('script', '/third_party/test262/harness/propertyHelper.js')]
+ ),
+])
+def test_get_metadata(test262_handlers, handler_cls, request_path, expected_metadata):
+ tests_root, url_base = test262_handlers
+ _test_handler_get_metadata(handler_cls, tests_root, url_base, request_path, expected_metadata)
+
+
+@pytest.mark.parametrize("handler_cls, request_path, expected_substrings", [
+ # Test262WindowHandler: Should contain the iframe pointing to the test
+ (
+ Test262WindowHandler,
+ "/test262/basic.test262.html",
+ ['']
+ ),
+ # Test262WindowTestHandler: Should contain script tags
+ (
+ Test262WindowTestHandler,
+ "/test262/basic.test262-test.html",
+ ['', '', '']
+ ),
+ # Test262WindowModuleTestHandler: Should contain module import
+ (
+ Test262WindowModuleTestHandler,
+ "/test262/module.test262-module-test.html",
+ ['"]
+ ),
+ # Strict HTML Case: points to the .strict.js variant
+ (
+ Test262StrictWindowTestHandler,
+ "/test262/teststrict.test262-test.strict.html",
+ ['src="/test262/teststrict.test262.strict.js"']
+ ),
+ # Strict JS Case: The handler that serves the actual script
+ (
+ Test262StrictHandler,
+ "/test262/teststrict.test262.strict.js",
+ ['"use strict";', "console.log('hello');"]
+ ),
+])
+def test_wrapper_content(test262_handlers, handler_cls, request_path, expected_substrings):
+ tests_root, url_base = test262_handlers
+ _test_handler_wrapper_content(handler_cls, tests_root, url_base, request_path, expected_substrings)