Commit a3782800 authored by Luke Zielinski's avatar Luke Zielinski Committed by Commit Bot

Roll new version of WPT tools.

The biggest change here is the increment of the manifest version, caused
by https://github.com/web-platform-tests/wpt/pull/20459, which changes
reftest computation logic.

This roll also introduces crashtests to the manifest.

The rest of the changes are relatively small (new browser support, bug
fixes etc).

Change-Id: I72dc0901e42ae81c4a5db0e8435574578afe53c8
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1987915
Commit-Queue: Luke Z <lpz@chromium.org>
Reviewed-by: default avatarRobert Ma <robertma@chromium.org>
Cr-Commit-Position: refs/heads/master@{#729824}
parent 4ba80e19
...@@ -68,7 +68,7 @@ class WPTTestAdapter(common.BaseIsolatedScriptArgsAdapter): ...@@ -68,7 +68,7 @@ class WPTTestAdapter(common.BaseIsolatedScriptArgsAdapter):
# update the manifest in cast it's stale. # update the manifest in cast it's stale.
#"--no-manifest-update", #"--no-manifest-update",
"--manifest=../../third_party/blink/web_tests/external/" "--manifest=../../third_party/blink/web_tests/external/"
"WPT_BASE_MANIFEST_6.json", "WPT_BASE_MANIFEST_7.json",
# (crbug.com/1023835) The flags below are temporary to aid debugging # (crbug.com/1023835) The flags below are temporary to aid debugging
"--log-mach=-", "--log-mach=-",
"--log-mach-verbose", "--log-mach-verbose",
......
...@@ -32,7 +32,7 @@ Local Modifications: None ...@@ -32,7 +32,7 @@ Local Modifications: None
Name: web-platform-tests - Test Suites for Web Platform specifications Name: web-platform-tests - Test Suites for Web Platform specifications
Short Name: wpt Short Name: wpt
URL: https://github.com/web-platform-tests/wpt/ URL: https://github.com/web-platform-tests/wpt/
Version: 3ee67ef03aeee31247ee1b5aca3e91ddef4fdfd8 Version: eb55d6105892b783a8bfeb3a34b251171fe2228b
License: LICENSES FOR W3C TEST SUITES (https://www.w3.org/Consortium/Legal/2008/03-bsd-license.html) License: LICENSES FOR W3C TEST SUITES (https://www.w3.org/Consortium/Legal/2008/03-bsd-license.html)
License File: wpt/wpt/LICENSE.md License File: wpt/wpt/LICENSE.md
Security Critical: no Security Critical: no
......
...@@ -9,7 +9,7 @@ cd $DIR ...@@ -9,7 +9,7 @@ cd $DIR
TARGET_DIR=$DIR/wpt TARGET_DIR=$DIR/wpt
REMOTE_REPO="https://github.com/web-platform-tests/wpt.git" REMOTE_REPO="https://github.com/web-platform-tests/wpt.git"
WPT_HEAD=3ee67ef03aeee31247ee1b5aca3e91ddef4fdfd8 WPT_HEAD=eb55d6105892b783a8bfeb3a34b251171fe2228b
function clone { function clone {
# Remove existing repo if already exists. # Remove existing repo if already exists.
......
...@@ -14,7 +14,7 @@ ...@@ -14,7 +14,7 @@
"virtualenv": false "virtualenv": false
}, },
"tc-download": { "tc-download": {
"path": "tcdownload.py", "path": "tc/download.py",
"script": "run", "script": "run",
"parser": "get_parser", "parser": "get_parser",
"parse_known": true, "parse_known": true,
...@@ -24,5 +24,27 @@ ...@@ -24,5 +24,27 @@
"requests", "requests",
"pygithub" "pygithub"
] ]
},
"tc-taskgraph": {
"path": "tc/taskgraph.py",
"script": "run",
"help": "Build the taskgraph",
"virtualenv": true,
"install": [
"requests",
"pyyaml"
]
},
"tc-decision": {
"path": "tc/decision.py",
"parser": "get_parser",
"script": "run",
"help": "Run the decision task",
"virtualenv": true,
"install": [
"requests",
"pyyaml",
"taskcluster"
]
} }
} }
...@@ -392,6 +392,7 @@ regexps = [item() for item in # type: ignore ...@@ -392,6 +392,7 @@ regexps = [item() for item in # type: ignore
rules.MissingDepsRegexp, rules.MissingDepsRegexp,
rules.SpecialPowersRegexp]] rules.SpecialPowersRegexp]]
def check_regexp_line(repo_root, path, f): def check_regexp_line(repo_root, path, f):
# type: (str, str, IO[bytes]) -> List[rules.Error] # type: (str, str, IO[bytes]) -> List[rules.Error]
errors = [] # type: List[rules.Error] errors = [] # type: List[rules.Error]
...@@ -405,6 +406,7 @@ def check_regexp_line(repo_root, path, f): ...@@ -405,6 +406,7 @@ def check_regexp_line(repo_root, path, f):
return errors return errors
def check_parsed(repo_root, path, f): def check_parsed(repo_root, path, f):
# type: (str, str, IO[bytes]) -> List[rules.Error] # type: (str, str, IO[bytes]) -> List[rules.Error]
source_file = SourceFile(repo_root, path, "/", contents=f.read()) source_file = SourceFile(repo_root, path, "/", contents=f.read())
...@@ -474,6 +476,9 @@ def check_parsed(repo_root, path, f): ...@@ -474,6 +476,9 @@ def check_parsed(repo_root, path, f):
errors.append(rules.InvalidTimeout.error(path, (timeout_value,))) errors.append(rules.InvalidTimeout.error(path, (timeout_value,)))
if source_file.testharness_nodes: if source_file.testharness_nodes:
test_type = source_file.manifest_items()[0]
if test_type not in ("testharness", "manual"):
errors.append(rules.TestharnessInOtherType.error(path, (test_type,)))
if len(source_file.testharness_nodes) > 1: if len(source_file.testharness_nodes) > 1:
errors.append(rules.MultipleTestharness.error(path)) errors.append(rules.MultipleTestharness.error(path))
...@@ -484,10 +489,6 @@ def check_parsed(repo_root, path, f): ...@@ -484,10 +489,6 @@ def check_parsed(repo_root, path, f):
if len(testharnessreport_nodes) > 1: if len(testharnessreport_nodes) > 1:
errors.append(rules.MultipleTestharnessReport.error(path)) errors.append(rules.MultipleTestharnessReport.error(path))
testharnesscss_nodes = source_file.root.findall(".//{http://www.w3.org/1999/xhtml}link[@href='/resources/testharness.css']")
if testharnesscss_nodes:
errors.append(rules.PresentTestharnessCSS.error(path))
for element in source_file.variant_nodes: for element in source_file.variant_nodes:
if "content" not in element.attrib: if "content" not in element.attrib:
errors.append(rules.VariantMissing.error(path)) errors.append(rules.VariantMissing.error(path))
...@@ -868,12 +869,12 @@ def main(**kwargs): ...@@ -868,12 +869,12 @@ def main(**kwargs):
paths = lint_paths(kwargs, repo_root) paths = lint_paths(kwargs, repo_root)
ignore_glob = kwargs.get("ignore_glob") ignore_glob = kwargs.get(str("ignore_glob")) or str()
return lint(repo_root, paths, output_format, ignore_glob) return lint(repo_root, paths, output_format, str(ignore_glob))
def lint(repo_root, paths, output_format, ignore_glob): def lint(repo_root, paths, output_format, ignore_glob=str()):
# type: (str, List[str], str, str) -> int # type: (str, List[str], str, str) -> int
error_count = defaultdict(int) # type: Dict[Text, int] error_count = defaultdict(int) # type: Dict[Text, int]
last = None last = None
......
from copy import copy
from inspect import isabstract from inspect import isabstract
from six import iteritems, with_metaclass from six import iteritems, with_metaclass
from six.moves.urllib.parse import urljoin, urlparse from six.moves.urllib.parse import urljoin, urlparse
...@@ -196,9 +195,11 @@ class TestharnessTest(URLManifestItem): ...@@ -196,9 +195,11 @@ class TestharnessTest(URLManifestItem):
return rv return rv
class RefTestBase(URLManifestItem): class RefTest(URLManifestItem):
__slots__ = ("references",) __slots__ = ("references",)
item_type = "reftest"
def __init__(self, def __init__(self,
tests_root, # type: Text tests_root, # type: Text
path, # type: Text path, # type: Text
...@@ -207,7 +208,7 @@ class RefTestBase(URLManifestItem): ...@@ -207,7 +208,7 @@ class RefTestBase(URLManifestItem):
references=None, # type: Optional[List[Tuple[Text, Text]]] references=None, # type: Optional[List[Tuple[Text, Text]]]
**extras # type: Any **extras # type: Any
): ):
super(RefTestBase, self).__init__(tests_root, path, url_base, url, **extras) super(RefTest, self).__init__(tests_root, path, url_base, url, **extras)
if references is None: if references is None:
self.references = [] # type: List[Tuple[Text, Text]] self.references = [] # type: List[Tuple[Text, Text]]
else: else:
...@@ -266,7 +267,7 @@ class RefTestBase(URLManifestItem): ...@@ -266,7 +267,7 @@ class RefTestBase(URLManifestItem):
path, # type: Text path, # type: Text
obj # type: Tuple[Text, List[Tuple[Text, Text]], Dict[Any, Any]] obj # type: Tuple[Text, List[Tuple[Text, Text]], Dict[Any, Any]]
): ):
# type: (...) -> RefTestBase # type: (...) -> RefTest
tests_root = manifest.tests_root tests_root = manifest.tests_root
assert tests_root is not None assert tests_root is not None
path = to_os_path(path) path = to_os_path(path)
...@@ -278,38 +279,6 @@ class RefTestBase(URLManifestItem): ...@@ -278,38 +279,6 @@ class RefTestBase(URLManifestItem):
references, references,
**extras) **extras)
def to_RefTest(self):
# type: () -> RefTest
if type(self) == RefTest:
assert isinstance(self, RefTest)
return self
rv = copy(self)
rv.__class__ = RefTest
assert isinstance(rv, RefTest)
return rv
def to_RefTestNode(self):
# type: () -> RefTestNode
if type(self) == RefTestNode:
assert isinstance(self, RefTestNode)
return self
rv = copy(self)
rv.__class__ = RefTestNode
assert isinstance(rv, RefTestNode)
return rv
class RefTestNode(RefTestBase):
__slots__ = ()
item_type = "reftest_node"
class RefTest(RefTestBase):
__slots__ = ()
item_type = "reftest"
class ManualTest(URLManifestItem): class ManualTest(URLManifestItem):
__slots__ = () __slots__ = ()
...@@ -329,6 +298,17 @@ class VisualTest(URLManifestItem): ...@@ -329,6 +298,17 @@ class VisualTest(URLManifestItem):
item_type = "visual" item_type = "visual"
class CrashTest(URLManifestItem):
__slots__ = ()
item_type = "crashtest"
@property
def timeout(self):
# type: () -> Optional[Text]
return None
class WebDriverSpecTest(URLManifestItem): class WebDriverSpecTest(URLManifestItem):
__slots__ = () __slots__ = ()
......
...@@ -33,8 +33,8 @@ except ImportError: ...@@ -33,8 +33,8 @@ except ImportError:
import html5lib import html5lib
from . import XMLParser from . import XMLParser
from .item import (ManifestItem, ManualTest, WebDriverSpecTest, RefTestNode, TestharnessTest, from .item import (ManifestItem, ManualTest, WebDriverSpecTest, RefTest, TestharnessTest,
SupportFile, ConformanceCheckerTest, VisualTest) SupportFile, CrashTest, ConformanceCheckerTest, VisualTest)
from .utils import ContextManagerBytesIO, cached_property from .utils import ContextManagerBytesIO, cached_property
wd_pattern = "*.py" wd_pattern = "*.py"
...@@ -339,6 +339,7 @@ class SourceFile(object): ...@@ -339,6 +339,7 @@ class SourceFile(object):
self.filename == "META.yml" or self.filename == "META.yml" or
self.filename.startswith(".") or self.filename.startswith(".") or
self.filename.endswith(".headers") or self.filename.endswith(".headers") or
self.filename.endswith(".ini") or
self.in_non_test_dir()) self.in_non_test_dir())
@property @property
...@@ -408,6 +409,11 @@ class SourceFile(object): ...@@ -408,6 +409,11 @@ class SourceFile(object):
be a reference file (not a reftest)""" be a reference file (not a reftest)"""
return "/reference/" in self.url or bool(reference_file_re.search(self.name)) return "/reference/" in self.url or bool(reference_file_re.search(self.name))
@property
def name_is_crashtest(self):
# type: () -> bool
return self.type_flag == "crash" or "crashtests" in self.dir_path.split(os.path.sep)
@property @property
def markup_type(self): def markup_type(self):
# type: () -> Optional[Text] # type: () -> Optional[Text]
...@@ -820,6 +826,15 @@ class SourceFile(object): ...@@ -820,6 +826,15 @@ class SourceFile(object):
self.rel_url self.rel_url
)] )]
elif self.name_is_crashtest:
rv = CrashTest.item_type, [
CrashTest(
self.tests_root,
self.rel_path,
self.url_base,
self.rel_url
)]
elif self.name_is_multi_global: elif self.name_is_multi_global:
globals = b"" globals = b""
script_metadata = self.script_metadata script_metadata = self.script_metadata
...@@ -909,8 +924,8 @@ class SourceFile(object): ...@@ -909,8 +924,8 @@ class SourceFile(object):
)) ))
elif self.content_is_ref_node: elif self.content_is_ref_node:
rv = RefTestNode.item_type, [ rv = RefTest.item_type, [
RefTestNode( RefTest(
self.tests_root, self.tests_root,
self.rel_path, self.rel_path,
self.url_base, self.url_base,
......
...@@ -32,6 +32,8 @@ def create_parser(): ...@@ -32,6 +32,8 @@ def create_parser():
parser = argparse.ArgumentParser() parser = argparse.ArgumentParser()
parser.add_argument( parser.add_argument(
"-p", "--path", type=abs_path, help="Path to manifest file.") "-p", "--path", type=abs_path, help="Path to manifest file.")
parser.add_argument(
"--src-root", type=abs_path, default=None, help="Path to root of sourcetree.")
parser.add_argument( parser.add_argument(
"--tests-root", type=abs_path, default=wpt_root, help="Path to root of tests.") "--tests-root", type=abs_path, default=wpt_root, help="Path to root of tests.")
parser.add_argument( parser.add_argument(
...@@ -55,25 +57,35 @@ def create_parser(): ...@@ -55,25 +57,35 @@ def create_parser():
return parser return parser
def get_path_id_map(manifest_file, test_ids): def get_path_id_map(src_root, tests_root, manifest_file, test_ids):
# type: (Manifest, Iterable[Text]) -> Dict[Text, List[Text]] # type: (Text, Text, Manifest, Iterable[Text]) -> Dict[Text, List[Text]]
test_ids = set(test_ids) test_ids = set(test_ids)
path_id_map = defaultdict(list) # type: Dict[Text, List[Text]] path_id_map = defaultdict(list) # type: Dict[Text, List[Text]]
compute_rel_path = src_root != tests_root
for item_type, path, tests in manifest_file: for item_type, path, tests in manifest_file:
for test in tests: for test in tests:
if test.id in test_ids: if test.id in test_ids:
path_id_map[path].append(test.id) if compute_rel_path:
rel_path = os.path.relpath(os.path.join(tests_root, path),
src_root)
else:
rel_path = path
path_id_map[rel_path].append(test.id)
return path_id_map return path_id_map
def run(**kwargs): def get_paths(**kwargs):
# type: (**Any) -> None # type: (**Any) -> Dict[Text, List[Text]]
tests_root = kwargs["tests_root"] tests_root = kwargs["tests_root"]
assert tests_root is not None assert tests_root is not None
path = kwargs["path"] path = kwargs["path"]
if path is None: if path is None:
path = os.path.join(kwargs["tests_root"], "MANIFEST.json") path = os.path.join(kwargs["tests_root"], "MANIFEST.json")
src_root = kwargs["src_root"]
if src_root is None:
src_root = tests_root
manifest_file = load_and_update(tests_root, manifest_file = load_and_update(tests_root,
path, path,
...@@ -82,11 +94,21 @@ def run(**kwargs): ...@@ -82,11 +94,21 @@ def run(**kwargs):
rebuild=kwargs["rebuild"], rebuild=kwargs["rebuild"],
cache_root=kwargs["cache_root"]) cache_root=kwargs["cache_root"])
path_id_map = get_path_id_map(manifest_file, kwargs["test_ids"]) return get_path_id_map(src_root, tests_root, manifest_file, kwargs["test_ids"])
if kwargs["json"]:
def write_output(path_id_map, as_json):
# type: (Dict[Text, List[Text]], bool) -> None
if as_json:
print(json.dumps(path_id_map)) print(json.dumps(path_id_map))
else: else:
for path, test_ids in sorted(iteritems(path_id_map)): for path, test_ids in sorted(iteritems(path_id_map)):
print(path) print(path)
for test_id in sorted(test_ids): for test_id in sorted(test_ids):
print(" " + test_id) print(" " + test_id)
def run(**kwargs):
# type: (**Any) -> None
path_id_map = get_paths(**kwargs)
write_output(path_id_map, as_json=kwargs["json"])
{
"tc-verify": {"path": "verify.py", "script": "run", "parser": "create_parser", "help": "Verify .taskcluster.yml file is parsable",
"virtualenv": true, "install": ["json-e", "pyyaml"]}
}
Copyright (c) 2010-2017 Benjamin Peterson Copyright (c) 2010-2019 Benjamin Peterson
Permission is hereby granted, free of charge, to any person obtaining a copy of Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in this software and associated documentation files (the "Software"), to deal in
......
# Copyright (c) 2010-2017 Benjamin Peterson # Copyright (c) 2010-2019 Benjamin Peterson
# #
# Permission is hereby granted, free of charge, to any person obtaining a copy # Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal # of this software and associated documentation files (the "Software"), to deal
...@@ -29,7 +29,7 @@ import sys ...@@ -29,7 +29,7 @@ import sys
import types import types
__author__ = "Benjamin Peterson <benjamin@python.org>" __author__ = "Benjamin Peterson <benjamin@python.org>"
__version__ = "1.11.0" __version__ = "1.13.0"
# Useful for very coarse version differentiation. # Useful for very coarse version differentiation.
...@@ -255,8 +255,10 @@ _moved_attributes = [ ...@@ -255,8 +255,10 @@ _moved_attributes = [
MovedAttribute("zip_longest", "itertools", "itertools", "izip_longest", "zip_longest"), MovedAttribute("zip_longest", "itertools", "itertools", "izip_longest", "zip_longest"),
MovedModule("builtins", "__builtin__"), MovedModule("builtins", "__builtin__"),
MovedModule("configparser", "ConfigParser"), MovedModule("configparser", "ConfigParser"),
MovedModule("collections_abc", "collections", "collections.abc" if sys.version_info >= (3, 3) else "collections"),
MovedModule("copyreg", "copy_reg"), MovedModule("copyreg", "copy_reg"),
MovedModule("dbm_gnu", "gdbm", "dbm.gnu"), MovedModule("dbm_gnu", "gdbm", "dbm.gnu"),
MovedModule("dbm_ndbm", "dbm", "dbm.ndbm"),
MovedModule("_dummy_thread", "dummy_thread", "_dummy_thread"), MovedModule("_dummy_thread", "dummy_thread", "_dummy_thread"),
MovedModule("http_cookiejar", "cookielib", "http.cookiejar"), MovedModule("http_cookiejar", "cookielib", "http.cookiejar"),
MovedModule("http_cookies", "Cookie", "http.cookies"), MovedModule("http_cookies", "Cookie", "http.cookies"),
...@@ -637,6 +639,7 @@ if PY3: ...@@ -637,6 +639,7 @@ if PY3:
import io import io
StringIO = io.StringIO StringIO = io.StringIO
BytesIO = io.BytesIO BytesIO = io.BytesIO
del io
_assertCountEqual = "assertCountEqual" _assertCountEqual = "assertCountEqual"
if sys.version_info[1] <= 1: if sys.version_info[1] <= 1:
_assertRaisesRegex = "assertRaisesRegexp" _assertRaisesRegex = "assertRaisesRegexp"
...@@ -824,7 +827,15 @@ def with_metaclass(meta, *bases): ...@@ -824,7 +827,15 @@ def with_metaclass(meta, *bases):
class metaclass(type): class metaclass(type):
def __new__(cls, name, this_bases, d): def __new__(cls, name, this_bases, d):
return meta(name, bases, d) if sys.version_info[:2] >= (3, 7):
# This version introduced PEP 560 that requires a bit
# of extra care (we mimic what is done by __build_class__).
resolved_bases = types.resolve_bases(bases)
if resolved_bases is not bases:
d['__orig_bases__'] = bases
else:
resolved_bases = bases
return meta(name, resolved_bases, d)
@classmethod @classmethod
def __prepare__(cls, name, this_bases): def __prepare__(cls, name, this_bases):
...@@ -844,13 +855,74 @@ def add_metaclass(metaclass): ...@@ -844,13 +855,74 @@ def add_metaclass(metaclass):
orig_vars.pop(slots_var) orig_vars.pop(slots_var)
orig_vars.pop('__dict__', None) orig_vars.pop('__dict__', None)
orig_vars.pop('__weakref__', None) orig_vars.pop('__weakref__', None)
if hasattr(cls, '__qualname__'):
orig_vars['__qualname__'] = cls.__qualname__
return metaclass(cls.__name__, cls.__bases__, orig_vars) return metaclass(cls.__name__, cls.__bases__, orig_vars)
return wrapper return wrapper
def ensure_binary(s, encoding='utf-8', errors='strict'):
"""Coerce **s** to six.binary_type.
For Python 2:
- `unicode` -> encoded to `str`
- `str` -> `str`
For Python 3:
- `str` -> encoded to `bytes`
- `bytes` -> `bytes`
"""
if isinstance(s, text_type):
return s.encode(encoding, errors)
elif isinstance(s, binary_type):
return s
else:
raise TypeError("not expecting type '%s'" % type(s))
def ensure_str(s, encoding='utf-8', errors='strict'):
"""Coerce *s* to `str`.
For Python 2:
- `unicode` -> encoded to `str`
- `str` -> `str`
For Python 3:
- `str` -> `str`
- `bytes` -> decoded to `str`
"""
if not isinstance(s, (text_type, binary_type)):
raise TypeError("not expecting type '%s'" % type(s))
if PY2 and isinstance(s, text_type):
s = s.encode(encoding, errors)
elif PY3 and isinstance(s, binary_type):
s = s.decode(encoding, errors)
return s
def ensure_text(s, encoding='utf-8', errors='strict'):
"""Coerce *s* to six.text_type.
For Python 2:
- `unicode` -> `unicode`
- `str` -> `unicode`
For Python 3:
- `str` -> `str`
- `bytes` -> decoded to `str`
"""
if isinstance(s, binary_type):
return s.decode(encoding, errors)
elif isinstance(s, text_type):
return s
else:
raise TypeError("not expecting type '%s'" % type(s))
def python_2_unicode_compatible(klass): def python_2_unicode_compatible(klass):
""" """
A decorator that defines __unicode__ and __str__ methods under Python 2. A class decorator that defines __unicode__ and __str__ methods under Python 2.
Under Python 3 it does nothing. Under Python 3 it does nothing.
To support Python 2 and 3 with a single code base, define a __str__ method To support Python 2 and 3 with a single code base, define a __str__ method
......
# flake8: noqa # flake8: noqa
from client import ( from .client import (
Cookies, Cookies,
Element, Element,
Find, Find,
...@@ -8,7 +8,7 @@ from client import ( ...@@ -8,7 +8,7 @@ from client import (
Session, Session,
Timeouts, Timeouts,
Window) Window)
from error import ( from .error import (
ElementNotSelectableException, ElementNotSelectableException,
ElementNotVisibleException, ElementNotVisibleException,
InvalidArgumentException, InvalidArgumentException,
......
import urlparse from . import error
from . import protocol
import error from . import transport
import protocol
import transport
from six import string_types from six import string_types
from six.moves.urllib import parse as urlparse
def command(func): def command(func):
......
import collections import collections
import json import json
from six import itervalues
class WebDriverException(Exception): class WebDriverException(Exception):
http_status = None http_status = None
...@@ -205,6 +206,6 @@ def get(error_code): ...@@ -205,6 +206,6 @@ def get(error_code):
_errors = collections.defaultdict() _errors = collections.defaultdict()
for item in locals().values(): for item in list(itervalues(locals())):
if type(item) == type and issubclass(item, WebDriverException): if type(item) == type and issubclass(item, WebDriverException):
_errors[item.status_code] = item _errors[item.status_code] = item
import httplib
import json import json
import select import select
import urlparse from six.moves.urllib import parse as urlparse
from six.moves import http_client as httplib
import error from . import error
from six import text_type from six import text_type
......
...@@ -47,6 +47,11 @@ class Browser(object): ...@@ -47,6 +47,11 @@ class Browser(object):
def __init__(self, logger): def __init__(self, logger):
self.logger = logger self.logger = logger
@abstractmethod
def download(self, dest=None, channel=None):
"""Download a package or installer for the browser"""
return NotImplemented
@abstractmethod @abstractmethod
def install(self, dest=None): def install(self, dest=None):
"""Install the browser.""" """Install the browser."""
...@@ -91,7 +96,6 @@ class Firefox(Browser): ...@@ -91,7 +96,6 @@ class Firefox(Browser):
product = "firefox" product = "firefox"
binary = "browsers/firefox/firefox" binary = "browsers/firefox/firefox"
platform_ini = "browsers/firefox/platform.ini"
requirements = "requirements_firefox.txt" requirements = "requirements_firefox.txt"
platform = { platform = {
...@@ -117,11 +121,19 @@ class Firefox(Browser): ...@@ -117,11 +121,19 @@ class Firefox(Browser):
return "%s%s" % (self.platform, bits) return "%s%s" % (self.platform, bits)
def install(self, dest=None, channel="nightly"): def _get_dest(self, dest, channel):
"""Install Firefox.""" if dest is None:
# os.getcwd() doesn't include the venv path
dest = os.path.join(os.getcwd(), "_venv")
import mozinstall dest = os.path.join(dest, "browsers", channel)
if not os.path.exists(dest):
os.makedirs(dest)
return dest
def download(self, dest=None, channel="nightly"):
product = { product = {
"nightly": "firefox-nightly-latest-ssl", "nightly": "firefox-nightly-latest-ssl",
"beta": "firefox-beta-latest-ssl", "beta": "firefox-beta-latest-ssl",
...@@ -137,21 +149,15 @@ class Firefox(Browser): ...@@ -137,21 +149,15 @@ class Firefox(Browser):
} }
os_key = (self.platform, uname[4]) os_key = (self.platform, uname[4])
if dest is None:
dest = self._get_dest(None, channel)
if channel not in product: if channel not in product:
raise ValueError("Unrecognised release channel: %s" % channel) raise ValueError("Unrecognised release channel: %s" % channel)
if os_key not in os_builds: if os_key not in os_builds:
raise ValueError("Unsupported platform: %s %s" % os_key) raise ValueError("Unsupported platform: %s %s" % os_key)
if dest is None:
# os.getcwd() doesn't include the venv path
dest = os.path.join(os.getcwd(), "_venv")
dest = os.path.join(dest, "browsers", channel)
if not os.path.exists(dest):
os.makedirs(dest)
url = "https://download.mozilla.org/?product=%s&os=%s&lang=en-US" % (product[channel], url = "https://download.mozilla.org/?product=%s&os=%s&lang=en-US" % (product[channel],
os_builds[os_key]) os_builds[os_key])
self.logger.info("Downloading Firefox from %s" % url) self.logger.info("Downloading Firefox from %s" % url)
...@@ -176,6 +182,18 @@ class Firefox(Browser): ...@@ -176,6 +182,18 @@ class Firefox(Browser):
with open(installer_path, "wb") as f: with open(installer_path, "wb") as f:
f.write(resp.content) f.write(resp.content)
return installer_path
def install(self, dest=None, channel="nightly"):
"""Install Firefox."""
import mozinstall
dest = self._get_dest(dest, channel)
filename = os.path.basename(dest)
installer_path = self.download(dest, channel)
try: try:
mozinstall.install(installer_path, dest) mozinstall.install(installer_path, dest)
except mozinstall.mozinstall.InstallError: except mozinstall.mozinstall.InstallError:
...@@ -423,7 +441,7 @@ class FirefoxAndroid(Browser): ...@@ -423,7 +441,7 @@ class FirefoxAndroid(Browser):
product = "firefox_android" product = "firefox_android"
requirements = "requirements_firefox.txt" requirements = "requirements_firefox.txt"
def install(self, dest=None, channel=None): def download(self, dest=None, channel=None):
if dest is None: if dest is None:
dest = os.pwd dest = os.pwd
...@@ -453,6 +471,9 @@ class FirefoxAndroid(Browser): ...@@ -453,6 +471,9 @@ class FirefoxAndroid(Browser):
return apk_path return apk_path
def install(self, dest=None, channel=None):
return self.download(dest, channel)
def install_prefs(self, binary, dest=None, channel=None): def install_prefs(self, binary, dest=None, channel=None):
fx_browser = Firefox(self.logger) fx_browser = Firefox(self.logger)
return fx_browser.install_prefs(binary, dest, channel) return fx_browser.install_prefs(binary, dest, channel)
...@@ -479,6 +500,9 @@ class Chrome(Browser): ...@@ -479,6 +500,9 @@ class Chrome(Browser):
product = "chrome" product = "chrome"
requirements = "requirements_chrome.txt" requirements = "requirements_chrome.txt"
def download(self, dest=None, channel=None):
raise NotImplementedError
def install(self, dest=None, channel=None): def install(self, dest=None, channel=None):
raise NotImplementedError raise NotImplementedError
...@@ -634,6 +658,9 @@ class ChromeAndroidBase(Browser): ...@@ -634,6 +658,9 @@ class ChromeAndroidBase(Browser):
super(ChromeAndroidBase, self).__init__(logger) super(ChromeAndroidBase, self).__init__(logger)
self.device_serial = None self.device_serial = None
def download(self, dest=None, channel=None):
raise NotImplementedError
def install(self, dest=None, channel=None): def install(self, dest=None, channel=None):
raise NotImplementedError raise NotImplementedError
...@@ -685,6 +712,20 @@ class ChromeAndroid(ChromeAndroidBase): ...@@ -685,6 +712,20 @@ class ChromeAndroid(ChromeAndroidBase):
return "com.android.chrome" return "com.android.chrome"
#TODO(aluo): This is largely copied from the AndroidWebView implementation.
# Tests are not running for weblayer yet (crbug/1019521), this
# initial implementation will help to reproduce and debug any issues.
class AndroidWeblayer(ChromeAndroidBase):
"""Weblayer-specific interface for Android."""
product = "android_weblayer"
# TODO(aluo): replace this with weblayer version after tests are working.
requirements = "requirements_android_webview.txt"
def find_binary(self, venv_path=None, channel=None):
return "org.chromium.weblayer.shell"
class AndroidWebview(ChromeAndroidBase): class AndroidWebview(ChromeAndroidBase):
"""Webview-specific interface for Android. """Webview-specific interface for Android.
...@@ -725,6 +766,9 @@ class ChromeiOS(Browser): ...@@ -725,6 +766,9 @@ class ChromeiOS(Browser):
product = "chrome_ios" product = "chrome_ios"
requirements = "requirements_chrome_ios.txt" requirements = "requirements_chrome_ios.txt"
def download(self, dest=None, channel=None):
raise NotImplementedError
def install(self, dest=None, channel=None): def install(self, dest=None, channel=None):
raise NotImplementedError raise NotImplementedError
...@@ -758,6 +802,9 @@ class Opera(Browser): ...@@ -758,6 +802,9 @@ class Opera(Browser):
self.logger.warning("Unable to find the browser binary.") self.logger.warning("Unable to find the browser binary.")
return None return None
def download(self, dest=None, channel=None):
raise NotImplementedError
def install(self, dest=None, channel=None): def install(self, dest=None, channel=None):
raise NotImplementedError raise NotImplementedError
...@@ -827,6 +874,9 @@ class EdgeChromium(Browser): ...@@ -827,6 +874,9 @@ class EdgeChromium(Browser):
edgedriver_name = "msedgedriver" edgedriver_name = "msedgedriver"
requirements = "requirements_edge_chromium.txt" requirements = "requirements_edge_chromium.txt"
def download(self, dest=None, channel=None):
raise NotImplementedError
def install(self, dest=None, channel=None): def install(self, dest=None, channel=None):
raise NotImplementedError raise NotImplementedError
...@@ -923,6 +973,9 @@ class Edge(Browser): ...@@ -923,6 +973,9 @@ class Edge(Browser):
product = "edge" product = "edge"
requirements = "requirements_edge.txt" requirements = "requirements_edge.txt"
def download(self, dest=None, channel=None):
raise NotImplementedError
def install(self, dest=None, channel=None): def install(self, dest=None, channel=None):
raise NotImplementedError raise NotImplementedError
...@@ -954,6 +1007,9 @@ class InternetExplorer(Browser): ...@@ -954,6 +1007,9 @@ class InternetExplorer(Browser):
product = "ie" product = "ie"
requirements = "requirements_ie.txt" requirements = "requirements_ie.txt"
def download(self, dest=None, channel=None):
raise NotImplementedError
def install(self, dest=None, channel=None): def install(self, dest=None, channel=None):
raise NotImplementedError raise NotImplementedError
...@@ -979,6 +1035,9 @@ class Safari(Browser): ...@@ -979,6 +1035,9 @@ class Safari(Browser):
product = "safari" product = "safari"
requirements = "requirements_safari.txt" requirements = "requirements_safari.txt"
def download(self, dest=None, channel=None):
raise NotImplementedError
def install(self, dest=None, channel=None): def install(self, dest=None, channel=None):
raise NotImplementedError raise NotImplementedError
...@@ -1038,17 +1097,33 @@ class Servo(Browser): ...@@ -1038,17 +1097,33 @@ class Servo(Browser):
return (platform, extension, decompress) return (platform, extension, decompress)
def install(self, dest=None, channel="nightly"): def _get(self, channel="nightly"):
"""Install latest Browser Engine."""
if channel != "nightly": if channel != "nightly":
raise ValueError("Only nightly versions of Servo are available") raise ValueError("Only nightly versions of Servo are available")
platform, extension, _ = self.platform_components()
url = "https://download.servo.org/nightly/%s/servo-latest%s" % (platform, extension)
return get(url)
def download(self, dest=None, channel="nightly"):
if dest is None: if dest is None:
dest = os.pwd dest = os.pwd
platform, extension, decompress = self.platform_components() resp = self._get(dest, channel)
url = "https://download.servo.org/nightly/%s/servo-latest%s" % (platform, extension) _, extension, _ = self.platform_components()
decompress(get(url).raw, dest=dest) with open(os.path.join(dest, "servo-latest%s" % (extension,)), "w") as f:
f.write(resp.content)
def install(self, dest=None, channel="nightly"):
"""Install latest Browser Engine."""
if dest is None:
dest = os.pwd
_, _, decompress = self.platform_components()
resp = self._get(channel)
decompress(resp.raw, dest=dest)
path = find_executable("servo", os.path.join(dest, "servo")) path = find_executable("servo", os.path.join(dest, "servo"))
st = os.stat(path) st = os.stat(path)
os.chmod(path, st.st_mode | stat.S_IEXEC) os.chmod(path, st.st_mode | stat.S_IEXEC)
...@@ -1084,6 +1159,9 @@ class Sauce(Browser): ...@@ -1084,6 +1159,9 @@ class Sauce(Browser):
product = "sauce" product = "sauce"
requirements = "requirements_sauce.txt" requirements = "requirements_sauce.txt"
def download(self, dest=None, channel=None):
raise NotImplementedError
def install(self, dest=None, channel=None): def install(self, dest=None, channel=None):
raise NotImplementedError raise NotImplementedError
...@@ -1106,6 +1184,9 @@ class WebKit(Browser): ...@@ -1106,6 +1184,9 @@ class WebKit(Browser):
product = "webkit" product = "webkit"
requirements = "requirements_webkit.txt" requirements = "requirements_webkit.txt"
def download(self, dest=None, channel=None):
raise NotImplementedError
def install(self, dest=None, channel=None): def install(self, dest=None, channel=None):
raise NotImplementedError raise NotImplementedError
...@@ -1169,6 +1250,9 @@ class Epiphany(Browser): ...@@ -1169,6 +1250,9 @@ class Epiphany(Browser):
product = "epiphany" product = "epiphany"
requirements = "requirements_epiphany.txt" requirements = "requirements_epiphany.txt"
def download(self, dest=None, channel=None):
raise NotImplementedError
def install(self, dest=None, channel=None): def install(self, dest=None, channel=None):
raise NotImplementedError raise NotImplementedError
......
...@@ -42,6 +42,8 @@ def get_parser(): ...@@ -42,6 +42,8 @@ def get_parser():
'the latest available development release. For WebDriver installs, ' 'the latest available development release. For WebDriver installs, '
'we attempt to select an appropriate, compatible, version for the ' 'we attempt to select an appropriate, compatible, version for the '
'latest browser release on the selected channel.') 'latest browser release on the selected channel.')
parser.add_argument('--download-only', action="store_true",
help="Download the selected component but don't install it")
parser.add_argument('-d', '--destination', parser.add_argument('-d', '--destination',
help='filesystem directory to place the component') help='filesystem directory to place the component')
return parser return parser
...@@ -73,21 +75,22 @@ def run(venv, **kwargs): ...@@ -73,21 +75,22 @@ def run(venv, **kwargs):
raise argparse.ArgumentError(None, raise argparse.ArgumentError(None,
"No --destination argument, and no default for the environment") "No --destination argument, and no default for the environment")
install(browser, kwargs["component"], destination, channel) install(browser, kwargs["component"], destination, channel,
download_only=kwargs["download_only"])
def install(name, component, destination, channel="nightly", logger=None): def install(name, component, destination, channel="nightly", logger=None, download_only=False):
if logger is None: if logger is None:
import logging import logging
logger = logging.getLogger("install") logger = logging.getLogger("install")
if component == 'webdriver': prefix = "download" if download_only else "install"
method = 'install_webdriver' suffix = "_webdriver" if component == 'webdriver' else ""
else:
method = 'install' method = prefix + suffix
subclass = getattr(browser, name.title()) subclass = getattr(browser, name.title())
sys.stdout.write('Now installing %s %s...\n' % (name, component)) sys.stdout.write('Now installing %s %s...\n' % (name, component))
path = getattr(subclass(logger), method)(dest=destination, channel=channel) path = getattr(subclass(logger), method)(dest=destination, channel=channel)
if path: if path:
sys.stdout.write('Binary installed as %s\n' % (path,)) sys.stdout.write('Binary %s as %s\n' % ("downloaded" if download_only else "installed", path,))
...@@ -3,5 +3,4 @@ tools/docker/ ...@@ -3,5 +3,4 @@ tools/docker/
tools/lint/ tools/lint/
tools/manifest/ tools/manifest/
tools/serve/ tools/serve/
tools/taskcluster/
tools/wpt/ tools/wpt/
...@@ -106,7 +106,7 @@ otherwise install OpenSSL and ensure that it's on your $PATH.""") ...@@ -106,7 +106,7 @@ otherwise install OpenSSL and ensure that it's on your $PATH.""")
def check_environ(product): def check_environ(product):
if product not in ("android_webview", "chrome", "chrome_android", "firefox", "firefox_android", "servo"): if product not in ("android_weblayer", "android_webview", "chrome", "chrome_android", "firefox", "firefox_android", "servo"):
config_builder = serve.build_config(os.path.join(wpt_root, "config.json")) config_builder = serve.build_config(os.path.join(wpt_root, "config.json"))
# Override the ports to avoid looking for free ports # Override the ports to avoid looking for free ports
config_builder.ssl = {"type": "none"} config_builder.ssl = {"type": "none"}
...@@ -399,6 +399,31 @@ class ChromeiOS(BrowserSetup): ...@@ -399,6 +399,31 @@ class ChromeiOS(BrowserSetup):
raise WptrunError("Unable to locate or install chromedriver binary") raise WptrunError("Unable to locate or install chromedriver binary")
class AndroidWeblayer(BrowserSetup):
name = "android_weblayer"
browser_cls = browser.AndroidWeblayer
def setup_kwargs(self, kwargs):
if kwargs.get("device_serial"):
self.browser.device_serial = kwargs["device_serial"]
if kwargs["webdriver_binary"] is None:
webdriver_binary = self.browser.find_webdriver()
if webdriver_binary is None:
install = self.prompt_install("chromedriver")
if install:
logger.info("Downloading chromedriver")
webdriver_binary = self.browser.install_webdriver(dest=self.venv.bin_path)
else:
logger.info("Using webdriver binary %s" % webdriver_binary)
if webdriver_binary:
kwargs["webdriver_binary"] = webdriver_binary
else:
raise WptrunError("Unable to locate or install chromedriver binary")
class AndroidWebview(BrowserSetup): class AndroidWebview(BrowserSetup):
name = "android_webview" name = "android_webview"
browser_cls = browser.AndroidWebview browser_cls = browser.AndroidWebview
...@@ -476,8 +501,8 @@ class EdgeChromium(BrowserSetup): ...@@ -476,8 +501,8 @@ class EdgeChromium(BrowserSetup):
kwargs["webdriver_binary"] = webdriver_binary kwargs["webdriver_binary"] = webdriver_binary
else: else:
raise WptrunError("Unable to locate or install msedgedriver binary") raise WptrunError("Unable to locate or install msedgedriver binary")
if browser_channel == "dev": if browser_channel in ("dev", "canary"):
logger.info("Automatically turning on experimental features for Edge Dev") logger.info("Automatically turning on experimental features for Edge Dev/Canary")
kwargs["binary_args"].append("--enable-experimental-web-platform-features") kwargs["binary_args"].append("--enable-experimental-web-platform-features")
...@@ -638,6 +663,7 @@ class Epiphany(BrowserSetup): ...@@ -638,6 +663,7 @@ class Epiphany(BrowserSetup):
product_setup = { product_setup = {
"android_weblayer": AndroidWeblayer,
"android_webview": AndroidWebview, "android_webview": AndroidWebview,
"firefox": Firefox, "firefox": Firefox,
"firefox_android": FirefoxAndroid, "firefox_android": FirefoxAndroid,
......
import cgi try:
import html
except ImportError:
import cgi as html
import json import json
import os import os
import sys import sys
...@@ -76,7 +79,7 @@ class DirectoryHandler(object): ...@@ -76,7 +79,7 @@ class DirectoryHandler(object):
<ul> <ul>
%(items)s %(items)s
</ul> </ul>
""" % {"path": cgi.escape(url_path), """ % {"path": html.escape(url_path),
"items": "\n".join(self.list_items(url_path, path))} # noqa: E122 "items": "\n".join(self.list_items(url_path, path))} # noqa: E122
def list_items(self, base_path, path): def list_items(self, base_path, path):
...@@ -93,14 +96,14 @@ class DirectoryHandler(object): ...@@ -93,14 +96,14 @@ class DirectoryHandler(object):
yield ("""<li class="dir"><a href="%(link)s">%(name)s</a></li>""" % yield ("""<li class="dir"><a href="%(link)s">%(name)s</a></li>""" %
{"link": link, "name": ".."}) {"link": link, "name": ".."})
for item in sorted(os.listdir(path)): for item in sorted(os.listdir(path)):
link = cgi.escape(quote(item)) link = html.escape(quote(item))
if os.path.isdir(os.path.join(path, item)): if os.path.isdir(os.path.join(path, item)):
link += "/" link += "/"
class_ = "dir" class_ = "dir"
else: else:
class_ = "file" class_ = "file"
yield ("""<li class="%(class)s"><a href="%(link)s">%(name)s</a></li>""" % yield ("""<li class="%(class)s"><a href="%(link)s">%(name)s</a></li>""" %
{"link": link, "name": cgi.escape(item), "class": class_}) {"link": link, "name": html.escape(item), "class": class_})
def wrap_pipeline(path, request, response): def wrap_pipeline(path, request, response):
......
from cgi import escape try:
from html import escape
except ImportError:
from cgi import escape
from collections import deque from collections import deque
import base64 import base64
import gzip as gzip_module import gzip as gzip_module
......
...@@ -28,7 +28,7 @@ MANIFEST_NAME = 'MANIFEST.json' ...@@ -28,7 +28,7 @@ MANIFEST_NAME = 'MANIFEST.json'
# The filename used for the base manifest includes the version as a # The filename used for the base manifest includes the version as a
# workaround for trouble landing huge changes to the base manifest when # workaround for trouble landing huge changes to the base manifest when
# the version changes. See https://crbug.com/876717. # the version changes. See https://crbug.com/876717.
BASE_MANIFEST_NAME = 'WPT_BASE_MANIFEST_6.json' BASE_MANIFEST_NAME = 'WPT_BASE_MANIFEST_7.json'
# TODO(robertma): Use the official wpt.manifest module. # TODO(robertma): Use the official wpt.manifest module.
......
...@@ -15,9 +15,12 @@ external/wpt/infrastructure/reftest/reftest_mismatch_fail.html [ Failure ] ...@@ -15,9 +15,12 @@ external/wpt/infrastructure/reftest/reftest_mismatch_fail.html [ Failure ]
external/wpt/infrastructure/reftest/reftest_ref_timeout.html [ Timeout ] external/wpt/infrastructure/reftest/reftest_ref_timeout.html [ Timeout ]
external/wpt/infrastructure/reftest/reftest_timeout.html [ Timeout ] external/wpt/infrastructure/reftest/reftest_timeout.html [ Timeout ]
external/wpt/infrastructure/expected-fail/timeout.html [ Timeout ] external/wpt/infrastructure/expected-fail/timeout.html [ Timeout ]
external/wpt/infrastructure/reftest/legacy/reftest_and_fail_0-ref.html [ Failure ]
external/wpt/infrastructure/reftest/legacy/reftest_cycle_fail_0-ref.html [ Failure ]
crbug.com/997202 external/wpt/infrastructure/reftest/reftest_fuzzy_1.html [ Failure ] crbug.com/997202 external/wpt/infrastructure/reftest/reftest_fuzzy_1.html [ Failure ]
crbug.com/997202 external/wpt/infrastructure/reftest/legacy/reftest_fuzzy_chain_ini.html [ Failure ] crbug.com/997202 external/wpt/infrastructure/reftest/legacy/reftest_fuzzy_chain_ini.html [ Failure ]
crbug.com/997202 external/wpt/infrastructure/reftest/legacy/fuzzy-ref-2.html [ Failure ]
crbug.com/997202 external/wpt/infrastructure/reftest/reftest_fuzzy_ini_full.html [ Failure ] crbug.com/997202 external/wpt/infrastructure/reftest/reftest_fuzzy_ini_full.html [ Failure ]
crbug.com/997202 external/wpt/infrastructure/reftest/reftest_fuzzy_ini_ref_only.html [ Failure ] crbug.com/997202 external/wpt/infrastructure/reftest/reftest_fuzzy_ini_ref_only.html [ Failure ]
crbug.com/997202 external/wpt/infrastructure/reftest/reftest_fuzzy_ini_short.html [ Failure ] crbug.com/997202 external/wpt/infrastructure/reftest/reftest_fuzzy_ini_short.html [ Failure ]
...@@ -4849,6 +4852,10 @@ crbug.com/626703 virtual/omt-worker-fetch/external/wpt/xhr/setrequestheader-head ...@@ -4849,6 +4852,10 @@ crbug.com/626703 virtual/omt-worker-fetch/external/wpt/xhr/setrequestheader-head
crbug.com/626703 [ Win10 ] external/wpt/preload/delaying-onload-link-preload-after-discovery.html [ Timeout ] crbug.com/626703 [ Win10 ] external/wpt/preload/delaying-onload-link-preload-after-discovery.html [ Timeout ]
crbug.com/626703 [ Win ] external/wpt/css/css-writing-modes/box-offsets-rel-pos-vlr-005.xht [ Failure ] crbug.com/626703 [ Win ] external/wpt/css/css-writing-modes/box-offsets-rel-pos-vlr-005.xht [ Failure ]
crbug.com/626703 [ Win ] external/wpt/css/css-writing-modes/box-offsets-rel-pos-vrl-004.xht [ Failure ] crbug.com/626703 [ Win ] external/wpt/css/css-writing-modes/box-offsets-rel-pos-vrl-004.xht [ Failure ]
crbug.com/626703 external/wpt/css/CSS2/floats/float-nowrap-3-ref.html [ Crash ]
crbug.com/626703 external/wpt/css/CSS2/floats/float-nowrap-4.html [ Crash ]
crbug.com/626703 external/wpt/html/dom/elements/global-attributes/dir_auto-N-EN-ref.html [ Failure ]
crbug.com/626703 virtual/web-components-v0-disabled/external/wpt/html/dom/elements/global-attributes/dir_auto-N-EN-ref.html [ Failure ]
crbug.com/964181 external/wpt/css/css-text/overflow-wrap/overflow-wrap-anywhere-inline-002.html [ Failure ] crbug.com/964181 external/wpt/css/css-text/overflow-wrap/overflow-wrap-anywhere-inline-002.html [ Failure ]
crbug.com/964181 external/wpt/css/css-text/overflow-wrap/overflow-wrap-anywhere-inline-003.html [ Failure ] crbug.com/964181 external/wpt/css/css-text/overflow-wrap/overflow-wrap-anywhere-inline-003.html [ Failure ]
......
This diff is collapsed.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment