This commit is contained in:
Simon Wülker 2025-06-02 14:13:50 -05:00 committed by GitHub
commit 327c71076f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 142 additions and 55 deletions

View file

@ -99,6 +99,15 @@ pub struct Preferences {
pub dom_serviceworker_timeout_seconds: i64, pub dom_serviceworker_timeout_seconds: i64,
pub dom_servo_helpers_enabled: bool, pub dom_servo_helpers_enabled: bool,
pub dom_servoparser_async_html_tokenizer_enabled: bool, pub dom_servoparser_async_html_tokenizer_enabled: bool,
/// When enabled, Servo will create stub implementations for all DOM methods and attributes
/// that are known but not implemented. When a page tries to use one of these then a warning
/// with the method name will be logged and some appropriate default behaviour will be chosen:
/// * Getting the value of a stub attribute will return `undefined`
/// * Setting the value of a stub attribute will do nothing
/// * Calling a stub method will return `undefined`
///
/// The warning is dispatched with target `dom-stubs`. Set `RUST_LOG=error,dom-stubs=warn` to see it.
pub dom_stub_unimplemented_features: bool,
pub dom_svg_enabled: bool, pub dom_svg_enabled: bool,
pub dom_testable_crash_enabled: bool, pub dom_testable_crash_enabled: bool,
pub dom_testbinding_enabled: bool, pub dom_testbinding_enabled: bool,
@ -272,6 +281,7 @@ impl Preferences {
dom_serviceworker_timeout_seconds: 60, dom_serviceworker_timeout_seconds: 60,
dom_servo_helpers_enabled: false, dom_servo_helpers_enabled: false,
dom_servoparser_async_html_tokenizer_enabled: false, dom_servoparser_async_html_tokenizer_enabled: false,
dom_stub_unimplemented_features: false,
dom_svg_enabled: false, dom_svg_enabled: false,
dom_testable_crash_enabled: false, dom_testable_crash_enabled: false,
dom_testbinding_enabled: false, dom_testbinding_enabled: false,

View file

@ -417,10 +417,11 @@ class CGMethodCall(CGThing):
def getPerSignatureCall(signature, argConversionStartsAt=0): def getPerSignatureCall(signature, argConversionStartsAt=0):
signatureIndex = signatures.index(signature) signatureIndex = signatures.index(signature)
isUnimplemented = method.getExtendedAttribute("Unimplemented") is not None
return CGPerSignatureCall(signature[0], argsPre, signature[1], return CGPerSignatureCall(signature[0], argsPre, signature[1],
f"{nativeMethodName}{'_' * signatureIndex}", f"{nativeMethodName}{'_' * signatureIndex}",
static, descriptor, static, descriptor,
method, argConversionStartsAt) method, isUnimplemented, argConversionStartsAt)
if len(signatures) == 1: if len(signatures) == 1:
# Special case: we can just do a per-signature method call # Special case: we can just do a per-signature method call
@ -1614,11 +1615,23 @@ class PropertyDefiner:
assert attr[0] is not None assert attr[0] is not None
return attr[0] return attr[0]
@staticmethod
def hasAttr(member, name) -> bool:
attr = member.getExtendedAttribute(name)
return attr is not None
@staticmethod @staticmethod
def getControllingCondition(interfaceMember, descriptor): def getControllingCondition(interfaceMember, descriptor):
prefCondition = PropertyDefiner.getStringAttr(interfaceMember, "Pref")
isUnimplemented = PropertyDefiner.hasAttr(interfaceMember, "Unimplemented")
if isUnimplemented:
if prefCondition is not None:
raise TypeError("Cannot preference-gate unimplemented features")
prefCondition = "dom_stub_unimplemented_features"
return MemberCondition( return MemberCondition(
PropertyDefiner.getStringAttr(interfaceMember, prefCondition,
"Pref"),
PropertyDefiner.getStringAttr(interfaceMember, PropertyDefiner.getStringAttr(interfaceMember,
"Func"), "Func"),
interfaceMember.exposureSet, interfaceMember.exposureSet,
@ -2801,7 +2814,10 @@ def DomTypes(descriptors, descriptorProvider, dictionaries, callbacks, typedefs,
traits += ["crate::reflector::DomObjectWrap<Self>"] traits += ["crate::reflector::DomObjectWrap<Self>"]
if not descriptor.interface.isCallback() and not descriptor.interface.isIteratorInterface(): if not descriptor.interface.isCallback() and not descriptor.interface.isIteratorInterface():
nonConstMembers = [m for m in descriptor.interface.members if not m.isConst()] nonConstMembers = [
m for m in descriptor.interface.members
if not m.isConst() and not m.getExtendedAttribute("Unimplemented")
]
ctor = descriptor.interface.ctor() ctor = descriptor.interface.ctor()
if ( if (
nonConstMembers nonConstMembers
@ -3928,7 +3944,7 @@ def needCx(returnType, arguments, considerTypes):
class CGCallGenerator(CGThing): class CGCallGenerator(CGThing):
""" """
A class to generate an actual call to a C++ object. Assumes that the C++ A class to generate an actual call to a Rust object. Assumes that the Rust
object is stored in a variable whose name is given by the |object| argument. object is stored in a variable whose name is given by the |object| argument.
errorResult should be a string for the value to return in case of an errorResult should be a string for the value to return in case of an
@ -3936,7 +3952,7 @@ class CGCallGenerator(CGThing):
""" """
def __init__(self, errorResult, arguments, argsPre, returnType, def __init__(self, errorResult, arguments, argsPre, returnType,
extendedAttributes, descriptor, nativeMethodName, extendedAttributes, descriptor, nativeMethodName,
static, object="this", hasCEReactions=False): static, isUnimplemented: bool, object="this", hasCEReactions=False):
CGThing.__init__(self) CGThing.__init__(self)
assert errorResult is None or isinstance(errorResult, str) assert errorResult is None or isinstance(errorResult, str)
@ -3974,6 +3990,17 @@ class CGCallGenerator(CGThing):
# Build up our actual call # Build up our actual call
self.cgRoot = CGList([], "\n") self.cgRoot = CGList([], "\n")
if isUnimplemented:
# Generate a stub implementation that logs a warning and does nothing else
logMessage = (
f"log::warn!(target: \"dom-stubs\", \""
f"Attempt to use unimplemented method or attribute "
f"{descriptor.interface.identifier.name}::{nativeMethodName}"
f"\");"
)
self.cgRoot.append(CGGeneric(logMessage))
return
if rootType: if rootType:
self.cgRoot.append(CGList([ self.cgRoot.append(CGList([
CGGeneric("rooted!(in(*cx) let mut retval: "), CGGeneric("rooted!(in(*cx) let mut retval: "),
@ -4045,7 +4072,7 @@ class CGPerSignatureCall(CGThing):
# there. # there.
def __init__(self, returnType, argsPre, arguments, nativeMethodName, static, def __init__(self, returnType, argsPre, arguments, nativeMethodName, static,
descriptor, idlNode, argConversionStartsAt=0, descriptor, idlNode, isUnimplemented: bool, argConversionStartsAt=0,
getter=False, setter=False): getter=False, setter=False):
CGThing.__init__(self) CGThing.__init__(self)
self.returnType = returnType self.returnType = returnType
@ -4057,6 +4084,8 @@ class CGPerSignatureCall(CGThing):
self.argsPre = argsPre self.argsPre = argsPre
self.arguments = arguments self.arguments = arguments
self.argCount = len(arguments) self.argCount = len(arguments)
self.isUnimplemented = isUnimplemented
cgThings = [] cgThings = []
cgThings.extend([CGArgumentConverter(arguments[i], i, self.getArgs(), cgThings.extend([CGArgumentConverter(arguments[i], i, self.getArgs(),
self.getArgc(), self.descriptor, self.getArgc(), self.descriptor,
@ -4083,7 +4112,7 @@ class CGPerSignatureCall(CGThing):
errorResult, errorResult,
self.getArguments(), self.argsPre, returnType, self.getArguments(), self.argsPre, returnType,
self.extendedAttributes, descriptor, nativeMethodName, self.extendedAttributes, descriptor, nativeMethodName,
static, hasCEReactions=hasCEReactions)) static, isUnimplemented, hasCEReactions=hasCEReactions))
self.cgRoot = CGList(cgThings, "\n") self.cgRoot = CGList(cgThings, "\n")
@ -4100,6 +4129,14 @@ class CGPerSignatureCall(CGThing):
return 'infallible' not in self.extendedAttributes return 'infallible' not in self.extendedAttributes
def wrap_return_value(self): def wrap_return_value(self):
if self.isUnimplemented:
# Unimplemented methods don't actually compute a result type, so we stub
# it out here
return (
"args.rval().set(UndefinedValue());\n"
"return true;"
)
resultName = "result" resultName = "result"
# Maplike methods have `any` return values in WebIDL, but our internal bindings # Maplike methods have `any` return values in WebIDL, but our internal bindings
# use stronger types so we need to exclude them from being handled like other # use stronger types so we need to exclude them from being handled like other
@ -4170,9 +4207,10 @@ class CGGetterCall(CGPerSignatureCall):
getter. getter.
""" """
def __init__(self, argsPre, returnType, nativeMethodName, descriptor, attr): def __init__(self, argsPre, returnType, nativeMethodName, descriptor, attr):
isUnimplemented = attr.getExtendedAttribute("Unimplemented") is not None
CGPerSignatureCall.__init__(self, returnType, argsPre, [], CGPerSignatureCall.__init__(self, returnType, argsPre, [],
nativeMethodName, attr.isStatic(), descriptor, nativeMethodName, attr.isStatic(), descriptor,
attr, getter=True) attr, isUnimplemented, getter=True)
class FakeArgument(): class FakeArgument():
@ -4197,9 +4235,10 @@ class CGSetterCall(CGPerSignatureCall):
setter. setter.
""" """
def __init__(self, argsPre, argType, nativeMethodName, descriptor, attr): def __init__(self, argsPre, argType, nativeMethodName, descriptor, attr):
isUnimplemented = attr.getExtendedAttribute("Unimplemented") is not None
CGPerSignatureCall.__init__(self, None, argsPre, CGPerSignatureCall.__init__(self, None, argsPre,
[FakeArgument(argType, attr, allowTreatNonObjectAsNull=True)], [FakeArgument(argType, attr, allowTreatNonObjectAsNull=True)],
nativeMethodName, attr.isStatic(), descriptor, attr, nativeMethodName, attr.isStatic(), descriptor, attr, isUnimplemented,
setter=True) setter=True)
def wrap_return_value(self): def wrap_return_value(self):
@ -5826,8 +5865,9 @@ class CGProxySpecialOperation(CGPerSignatureCall):
# We pass len(arguments) as the final argument so that the # We pass len(arguments) as the final argument so that the
# CGPerSignatureCall won't do any argument conversion of its own. # CGPerSignatureCall won't do any argument conversion of its own.
isUnimplemented = False
CGPerSignatureCall.__init__(self, returnType, "", arguments, nativeName, CGPerSignatureCall.__init__(self, returnType, "", arguments, nativeName,
False, descriptor, operation, False, descriptor, operation, isUnimplemented,
len(arguments)) len(arguments))
if operation.isSetter(): if operation.isSetter():
@ -6687,6 +6727,11 @@ class CGInterfaceTrait(CGThing):
def members(): def members():
for m in descriptor.interface.members: for m in descriptor.interface.members:
if m.getExtendedAttribute("Unimplemented"):
# Unimplemented methods or attributes generate a stub implementation and should
# therefore not be part of the trait
continue
if (m.isMethod() if (m.isMethod()
and not m.isMaplikeOrSetlikeOrIterableMethod() and not m.isMaplikeOrSetlikeOrIterableMethod()
and (not m.isIdentifierLess() or (m.isStringifier() and not m.underlyingAttr)) and (not m.isIdentifierLess() or (m.isStringifier() and not m.underlyingAttr))

View file

@ -16,13 +16,28 @@ typedef (HTMLOrSVGImageElement or
/*VideoFrame or*/ /*VideoFrame or*/
/*CSSImageValue*/ CSSStyleValue) CanvasImageSource; /*CSSImageValue*/ CSSStyleValue) CanvasImageSource;
enum PredefinedColorSpace { "srgb", "display-p3" };
enum CanvasColorType { "unorm8", "float16" };
enum CanvasFillRule { "nonzero", "evenodd" }; enum CanvasFillRule { "nonzero", "evenodd" };
dictionary CanvasRenderingContext2DSettings {
boolean alpha = true;
boolean desynchronized = false;
PredefinedColorSpace colorSpace = "srgb";
CanvasColorType colorType = "unorm8";
boolean willReadFrequently = false;
};
enum ImageSmoothingQuality { "low", "medium", "high" };
[Exposed=Window] [Exposed=Window]
interface CanvasRenderingContext2D { interface CanvasRenderingContext2D {
// back-reference to the canvas // back-reference to the canvas
readonly attribute HTMLCanvasElement canvas; readonly attribute HTMLCanvasElement canvas;
}; };
CanvasRenderingContext2D includes CanvasSettings;
CanvasRenderingContext2D includes CanvasState; CanvasRenderingContext2D includes CanvasState;
CanvasRenderingContext2D includes CanvasTransform; CanvasRenderingContext2D includes CanvasTransform;
CanvasRenderingContext2D includes CanvasCompositing; CanvasRenderingContext2D includes CanvasCompositing;
@ -40,11 +55,17 @@ CanvasRenderingContext2D includes CanvasPathDrawingStyles;
CanvasRenderingContext2D includes CanvasTextDrawingStyles; CanvasRenderingContext2D includes CanvasTextDrawingStyles;
CanvasRenderingContext2D includes CanvasPath; CanvasRenderingContext2D includes CanvasPath;
interface mixin CanvasSettings {
// settings
[Unimplemented] CanvasRenderingContext2DSettings getContextAttributes();
};
interface mixin CanvasState { interface mixin CanvasState {
// state // state
undefined save(); // push state on state stack undefined save(); // push state on state stack
undefined restore(); // pop state stack and restore state undefined restore(); // pop state stack and restore state
undefined reset(); undefined reset(); // reset the rendering context to its default state
[Unimplemented] boolean isContextLost(); // return whether context is lost
}; };
interface mixin CanvasTransform { interface mixin CanvasTransform {
@ -66,7 +87,7 @@ interface mixin CanvasTransform {
unrestricted double d, unrestricted double d,
unrestricted double e, unrestricted double e,
unrestricted double f); unrestricted double f);
// void setTransform(optional DOMMatrixInit matrix); // [Unimplemented] undefined setTransform(optional DOMMatrixInit matrix);
undefined resetTransform(); undefined resetTransform();
}; };
@ -79,7 +100,7 @@ interface mixin CanvasCompositing {
interface mixin CanvasImageSmoothing { interface mixin CanvasImageSmoothing {
// image smoothing // image smoothing
attribute boolean imageSmoothingEnabled; // (default true) attribute boolean imageSmoothingEnabled; // (default true)
// attribute ImageSmoothingQuality imageSmoothingQuality; // (default low) [Unimplemented] attribute ImageSmoothingQuality imageSmoothingQuality; // (default low)
}; };
interface mixin CanvasFillStrokeStyles { interface mixin CanvasFillStrokeStyles {
@ -89,6 +110,7 @@ interface mixin CanvasFillStrokeStyles {
CanvasGradient createLinearGradient(double x0, double y0, double x1, double y1); CanvasGradient createLinearGradient(double x0, double y0, double x1, double y1);
[Throws] [Throws]
CanvasGradient createRadialGradient(double x0, double y0, double r0, double x1, double y1, double r1); CanvasGradient createRadialGradient(double x0, double y0, double r0, double x1, double y1, double r1);
[Unimplemented] CanvasGradient createConicGradient(double startAngle, double x, double y);
[Throws] [Throws]
CanvasPattern? createPattern(CanvasImageSource image, [LegacyNullToEmptyString] DOMString repetition); CanvasPattern? createPattern(CanvasImageSource image, [LegacyNullToEmptyString] DOMString repetition);
}; };
@ -103,7 +125,7 @@ interface mixin CanvasShadowStyles {
interface mixin CanvasFilters { interface mixin CanvasFilters {
// filters // filters
//attribute DOMString filter; // (default "none") [Unimplemented] attribute DOMString filter; // (default "none")
}; };
interface mixin CanvasRect { interface mixin CanvasRect {
@ -126,15 +148,15 @@ interface mixin CanvasDrawPath {
optional CanvasFillRule fillRule = "nonzero"); optional CanvasFillRule fillRule = "nonzero");
boolean isPointInPath(Path2D path, unrestricted double x, unrestricted double y, boolean isPointInPath(Path2D path, unrestricted double x, unrestricted double y,
optional CanvasFillRule fillRule = "nonzero"); optional CanvasFillRule fillRule = "nonzero");
//boolean isPointInStroke(unrestricted double x, unrestricted double y); [Unimplemented] boolean isPointInStroke(unrestricted double x, unrestricted double y);
//boolean isPointInStroke(Path2D path, unrestricted double x, unrestricted double y); [Unimplemented] boolean isPointInStroke(Path2D path, unrestricted double x, unrestricted double y);
}; };
interface mixin CanvasUserInterface { interface mixin CanvasUserInterface {
//void drawFocusIfNeeded(Element element); [Unimplemented] undefined drawFocusIfNeeded(Element element);
//void drawFocusIfNeeded(Path2D path, Element element); [Unimplemented] undefined drawFocusIfNeeded(Path2D path, Element element);
//void scrollPathIntoView(); [Unimplemented] undefined scrollPathIntoView();
//void scrollPathIntoView(Path2D path); [Unimplemented] undefined scrollPathIntoView(Path2D path);
}; };
interface mixin CanvasText { interface mixin CanvasText {
@ -142,8 +164,7 @@ interface mixin CanvasText {
[Pref="dom_canvas_text_enabled"] [Pref="dom_canvas_text_enabled"]
undefined fillText(DOMString text, unrestricted double x, unrestricted double y, undefined fillText(DOMString text, unrestricted double x, unrestricted double y,
optional unrestricted double maxWidth); optional unrestricted double maxWidth);
//void strokeText(DOMString text, unrestricted double x, unrestricted double y, [Unimplemented] undefined strokeText(DOMString text, unrestricted double x, unrestricted double y, optional unrestricted double maxWidth);
// optional unrestricted double maxWidth);
[Pref="dom_canvas_text_enabled"] [Pref="dom_canvas_text_enabled"]
TextMetrics measureText(DOMString text); TextMetrics measureText(DOMString text);
}; };

View file

@ -44,9 +44,8 @@ interface HTMLElement : Element {
// attribute boolean draggable; // attribute boolean draggable;
// [SameObject, PutForwards=value] readonly attribute DOMTokenList dropzone; // [SameObject, PutForwards=value] readonly attribute DOMTokenList dropzone;
// attribute HTMLMenuElement? contextMenu; // attribute HTMLMenuElement? contextMenu;
// [CEReactions] [Unimplemented, CEReactions] attribute boolean spellcheck;
// attribute boolean spellcheck; [Unimplemented] undefined forceSpellCheck();
// void forceSpellCheck();
[CEReactions] attribute [LegacyNullToEmptyString] DOMString innerText; [CEReactions] attribute [LegacyNullToEmptyString] DOMString innerText;
[CEReactions, Throws] attribute [LegacyNullToEmptyString] DOMString outerText; [CEReactions, Throws] attribute [LegacyNullToEmptyString] DOMString outerText;

View file

@ -7,30 +7,22 @@
interface HTMLLinkElement : HTMLElement { interface HTMLLinkElement : HTMLElement {
[HTMLConstructor] constructor(); [HTMLConstructor] constructor();
[CEReactions] [CEReactions] attribute USVString href;
attribute USVString href; [CEReactions] attribute DOMString? crossOrigin;
[CEReactions] [CEReactions] attribute DOMString rel;
attribute DOMString? crossOrigin;
[CEReactions]
attribute DOMString rel;
[CEReactions] attribute DOMString as; [CEReactions] attribute DOMString as;
[SameObject, PutForwards=value] readonly attribute DOMTokenList relList; [SameObject, PutForwards=value] readonly attribute DOMTokenList relList;
[CEReactions] [CEReactions] attribute DOMString media;
attribute DOMString media; [CEReactions] attribute DOMString integrity;
[CEReactions] [CEReactions] attribute DOMString hreflang;
attribute DOMString integrity; [CEReactions] attribute DOMString type;
[CEReactions] [Unimplemented, SameObject, PutForwards=value] readonly attribute DOMTokenList sizes;
attribute DOMString hreflang; [Unimplemented, CEReactions] attribute USVString imageSrcset;
[CEReactions] [Unimplemented, CEReactions] attribute DOMString imageSizes;
attribute DOMString type; [CEReactions] attribute DOMString referrerPolicy;
// [SameObject, PutForwards=value] readonly attribute DOMTokenList sizes; [Unimplemented, SameObject, PutForwards=value] readonly attribute DOMTokenList blocking;
// [CEReactions] attribute USVString imageSrcset;
// [CEReactions] attribute DOMString imageSizes;
[CEReactions]
attribute DOMString referrerPolicy;
// [SameObject, PutForwards=value] readonly attribute DOMTokenList blocking;
[CEReactions] attribute boolean disabled; [CEReactions] attribute boolean disabled;
// [CEReactions] attribute DOMString fetchPriority; [Unimplemented, CEReactions] attribute DOMString fetchPriority;
// also has obsolete members // also has obsolete members
}; };
@ -38,10 +30,7 @@ HTMLLinkElement includes LinkStyle;
// https://html.spec.whatwg.org/multipage/#HTMLLinkElement-partial // https://html.spec.whatwg.org/multipage/#HTMLLinkElement-partial
partial interface HTMLLinkElement { partial interface HTMLLinkElement {
[CEReactions] [CEReactions] attribute DOMString charset;
attribute DOMString charset; [CEReactions] attribute DOMString rev;
[CEReactions] [CEReactions] attribute DOMString target;
attribute DOMString rev;
[CEReactions]
attribute DOMString target;
}; };

View file

@ -20,5 +20,5 @@
undefined replace(USVString url); undefined replace(USVString url);
[Throws] undefined reload(); [Throws] undefined reload();
//[SameObject] readonly attribute USVString[] ancestorOrigins; [Unimplemented, SameObject] readonly attribute DOMStringList ancestorOrigins;
}; };

View file

@ -14,7 +14,7 @@ interface TextTrack : EventTarget {
readonly attribute DOMString language; readonly attribute DOMString language;
readonly attribute DOMString id; readonly attribute DOMString id;
// readonly attribute DOMString inBandMetadataTrackDispatchType; [Unimplemented] readonly attribute DOMString inBandMetadataTrackDispatchType;
attribute TextTrackMode mode; attribute TextTrackMode mode;

View file

@ -5939,6 +5939,7 @@ class IDLAttribute(IDLInterfaceMember):
or identifier == "BinaryName" or identifier == "BinaryName"
or identifier == "NonEnumerable" or identifier == "NonEnumerable"
or identifier == "BindingTemplate" or identifier == "BindingTemplate"
or identifier == "Unimplemented"
): ):
# Known attributes that we don't need to do anything with here # Known attributes that we don't need to do anything with here
pass pass
@ -7018,6 +7019,7 @@ class IDLMethod(IDLInterfaceMember, IDLScope):
or identifier == "NonEnumerable" or identifier == "NonEnumerable"
or identifier == "Unexposed" or identifier == "Unexposed"
or identifier == "WebExtensionStub" or identifier == "WebExtensionStub"
or identifier == "Unimplemented"
): ):
# Known attributes that we don't need to do anything with here # Known attributes that we don't need to do anything with here
pass pass

20
third_party/WebIDL/dom-stubs.patch vendored Normal file
View file

@ -0,0 +1,20 @@
diff --git a/third_party/WebIDL/WebIDL.py b/third_party/WebIDL/WebIDL.py
index 40e118e378..437e3640a6 100644
--- a/third_party/WebIDL/WebIDL.py
+++ b/third_party/WebIDL/WebIDL.py
@@ -5938,6 +5938,7 @@ class IDLAttribute(IDLInterfaceMember):
or identifier == "BinaryName"
or identifier == "NonEnumerable"
or identifier == "BindingTemplate"
+ or identifier == "Unimplemented"
):
# Known attributes that we don't need to do anything with here
pass
@@ -7017,6 +7018,7 @@ class IDLMethod(IDLInterfaceMember, IDLScope):
or identifier == "NonEnumerable"
or identifier == "Unexposed"
or identifier == "WebExtensionStub"
+ or identifier == "Unimplemented"
):
# Known attributes that we don't need to do anything with here
pass

View file

@ -8,6 +8,7 @@ patch < like-as-iterable.patch
patch < builtin-array.patch patch < builtin-array.patch
patch < array-type.patch patch < array-type.patch
patch < transferable.patch patch < transferable.patch
patch < dom-stubs.patch
wget https://hg.mozilla.org/mozilla-central/archive/tip.zip/dom/bindings/parser/tests/ -O tests.zip wget https://hg.mozilla.org/mozilla-central/archive/tip.zip/dom/bindings/parser/tests/ -O tests.zip
rm -r tests rm -r tests