diff --git a/test/fixtures/wpt/README.md b/test/fixtures/wpt/README.md index db10cdaf87fa86..5289916a15834d 100644 --- a/test/fixtures/wpt/README.md +++ b/test/fixtures/wpt/README.md @@ -12,9 +12,9 @@ Last update: - console: https://github.com/web-platform-tests/wpt/tree/9786a4b131/console - encoding: https://github.com/web-platform-tests/wpt/tree/5059d2c777/encoding -- url: https://github.com/web-platform-tests/wpt/tree/43feb7f612/url -- resources: https://github.com/web-platform-tests/wpt/tree/e1fddfbf80/resources -- interfaces: https://github.com/web-platform-tests/wpt/tree/8ada332aea/interfaces +- url: https://github.com/web-platform-tests/wpt/tree/c79fe5612a/url +- resources: https://github.com/web-platform-tests/wpt/tree/1ab7a4ebcf/resources +- interfaces: https://github.com/web-platform-tests/wpt/tree/876f188904/interfaces - html/webappapis/microtask-queuing: https://github.com/web-platform-tests/wpt/tree/0c3bed38df/html/webappapis/microtask-queuing - html/webappapis/timers: https://github.com/web-platform-tests/wpt/tree/ddfe9c089b/html/webappapis/timers - hr-time: https://github.com/web-platform-tests/wpt/tree/a5d1774ecf/hr-time diff --git a/test/fixtures/wpt/interfaces/console.idl b/test/fixtures/wpt/interfaces/console.idl index 3cd3c06b54b022..7cd73a61764fc1 100644 --- a/test/fixtures/wpt/interfaces/console.idl +++ b/test/fixtures/wpt/interfaces/console.idl @@ -1,34 +1,34 @@ // GENERATED CONTENT - DO NOT EDIT -// Content was automatically extracted by Reffy into reffy-reports -// (https://github.com/tidoust/reffy-reports) +// Content was automatically extracted by Reffy into webref +// (https://github.com/w3c/webref) // Source: Console Standard (https://console.spec.whatwg.org/) [Exposed=(Window,Worker,Worklet)] namespace console { // but see namespace object requirements below // Logging - void assert(optional boolean condition = false, any... data); - void clear(); - void debug(any... data); - void error(any... data); - void info(any... data); - void log(any... data); - void table(optional any tabularData, optional sequence properties); - void trace(any... data); - void warn(any... data); - void dir(optional any item, optional object? options); - void dirxml(any... data); + undefined assert(optional boolean condition = false, any... data); + undefined clear(); + undefined debug(any... data); + undefined error(any... data); + undefined info(any... data); + undefined log(any... data); + undefined table(optional any tabularData, optional sequence properties); + undefined trace(any... data); + undefined warn(any... data); + undefined dir(optional any item, optional object? options); + undefined dirxml(any... data); // Counting - void count(optional DOMString label = "default"); - void countReset(optional DOMString label = "default"); + undefined count(optional DOMString label = "default"); + undefined countReset(optional DOMString label = "default"); // Grouping - void group(any... data); - void groupCollapsed(any... data); - void groupEnd(); + undefined group(any... data); + undefined groupCollapsed(any... data); + undefined groupEnd(); // Timing - void time(optional DOMString label = "default"); - void timeLog(optional DOMString label = "default", any... data); - void timeEnd(optional DOMString label = "default"); + undefined time(optional DOMString label = "default"); + undefined timeLog(optional DOMString label = "default", any... data); + undefined timeEnd(optional DOMString label = "default"); }; diff --git a/test/fixtures/wpt/interfaces/dom.idl b/test/fixtures/wpt/interfaces/dom.idl index 102c23123819ba..ffc5b063219d4d 100644 --- a/test/fixtures/wpt/interfaces/dom.idl +++ b/test/fixtures/wpt/interfaces/dom.idl @@ -1,6 +1,6 @@ // GENERATED CONTENT - DO NOT EDIT -// Content was automatically extracted by Reffy into reffy-reports -// (https://github.com/tidoust/reffy-reports) +// Content was automatically extracted by Reffy into webref +// (https://github.com/w3c/webref) // Source: DOM Standard (https://dom.spec.whatwg.org/) [Exposed=(Window,Worker,AudioWorklet)] @@ -19,21 +19,21 @@ interface Event { const unsigned short BUBBLING_PHASE = 3; readonly attribute unsigned short eventPhase; - void stopPropagation(); + undefined stopPropagation(); attribute boolean cancelBubble; // historical alias of .stopPropagation - void stopImmediatePropagation(); + undefined stopImmediatePropagation(); readonly attribute boolean bubbles; readonly attribute boolean cancelable; attribute boolean returnValue; // historical - void preventDefault(); + undefined preventDefault(); readonly attribute boolean defaultPrevented; readonly attribute boolean composed; [LegacyUnforgeable] readonly attribute boolean isTrusted; readonly attribute DOMHighResTimeStamp timeStamp; - void initEvent(DOMString type, optional boolean bubbles = false, optional boolean cancelable = false); // historical + undefined initEvent(DOMString type, optional boolean bubbles = false, optional boolean cancelable = false); // historical }; dictionary EventInit { @@ -52,7 +52,7 @@ interface CustomEvent : Event { readonly attribute any detail; - void initCustomEvent(DOMString type, optional boolean bubbles = false, optional boolean cancelable = false, optional any detail = null); // historical + undefined initCustomEvent(DOMString type, optional boolean bubbles = false, optional boolean cancelable = false, optional any detail = null); // historical }; dictionary CustomEventInit : EventInit { @@ -63,13 +63,13 @@ dictionary CustomEventInit : EventInit { interface EventTarget { constructor(); - void addEventListener(DOMString type, EventListener? callback, optional (AddEventListenerOptions or boolean) options = {}); - void removeEventListener(DOMString type, EventListener? callback, optional (EventListenerOptions or boolean) options = {}); + undefined addEventListener(DOMString type, EventListener? callback, optional (AddEventListenerOptions or boolean) options = {}); + undefined removeEventListener(DOMString type, EventListener? callback, optional (EventListenerOptions or boolean) options = {}); boolean dispatchEvent(Event event); }; callback interface EventListener { - void handleEvent(Event event); + undefined handleEvent(Event event); }; dictionary EventListenerOptions { @@ -87,7 +87,7 @@ interface AbortController { [SameObject] readonly attribute AbortSignal signal; - void abort(); + undefined abort(); }; [Exposed=(Window,Worker)] @@ -113,9 +113,9 @@ interface mixin ParentNode { readonly attribute Element? lastElementChild; readonly attribute unsigned long childElementCount; - [CEReactions, Unscopable] void prepend((Node or DOMString)... nodes); - [CEReactions, Unscopable] void append((Node or DOMString)... nodes); - [CEReactions, Unscopable] void replaceChildren((Node or DOMString)... nodes); + [CEReactions, Unscopable] undefined prepend((Node or DOMString)... nodes); + [CEReactions, Unscopable] undefined append((Node or DOMString)... nodes); + [CEReactions, Unscopable] undefined replaceChildren((Node or DOMString)... nodes); Element? querySelector(DOMString selectors); [NewObject] NodeList querySelectorAll(DOMString selectors); @@ -132,10 +132,10 @@ Element includes NonDocumentTypeChildNode; CharacterData includes NonDocumentTypeChildNode; interface mixin ChildNode { - [CEReactions, Unscopable] void before((Node or DOMString)... nodes); - [CEReactions, Unscopable] void after((Node or DOMString)... nodes); - [CEReactions, Unscopable] void replaceWith((Node or DOMString)... nodes); - [CEReactions, Unscopable] void remove(); + [CEReactions, Unscopable] undefined before((Node or DOMString)... nodes); + [CEReactions, Unscopable] undefined after((Node or DOMString)... nodes); + [CEReactions, Unscopable] undefined replaceWith((Node or DOMString)... nodes); + [CEReactions, Unscopable] undefined remove(); }; DocumentType includes ChildNode; Element includes ChildNode; @@ -165,12 +165,12 @@ interface HTMLCollection { interface MutationObserver { constructor(MutationCallback callback); - void observe(Node target, optional MutationObserverInit options = {}); - void disconnect(); + undefined observe(Node target, optional MutationObserverInit options = {}); + undefined disconnect(); sequence takeRecords(); }; -callback MutationCallback = void (sequence mutations, MutationObserver observer); +callback MutationCallback = undefined (sequence mutations, MutationObserver observer); dictionary MutationObserverInit { boolean childList = false; @@ -228,7 +228,7 @@ interface Node : EventTarget { [CEReactions] attribute DOMString? nodeValue; [CEReactions] attribute DOMString? textContent; - [CEReactions] void normalize(); + [CEReactions] undefined normalize(); [CEReactions, NewObject] Node cloneNode(optional boolean deep = false); boolean isEqualNode(Node? otherNode); @@ -353,10 +353,10 @@ interface Element : Node { sequence getAttributeNames(); DOMString? getAttribute(DOMString qualifiedName); DOMString? getAttributeNS(DOMString? namespace, DOMString localName); - [CEReactions] void setAttribute(DOMString qualifiedName, DOMString value); - [CEReactions] void setAttributeNS(DOMString? namespace, DOMString qualifiedName, DOMString value); - [CEReactions] void removeAttribute(DOMString qualifiedName); - [CEReactions] void removeAttributeNS(DOMString? namespace, DOMString localName); + [CEReactions] undefined setAttribute(DOMString qualifiedName, DOMString value); + [CEReactions] undefined setAttributeNS(DOMString? namespace, DOMString qualifiedName, DOMString value); + [CEReactions] undefined removeAttribute(DOMString qualifiedName); + [CEReactions] undefined removeAttributeNS(DOMString? namespace, DOMString localName); [CEReactions] boolean toggleAttribute(DOMString qualifiedName, optional boolean force); boolean hasAttribute(DOMString qualifiedName); boolean hasAttributeNS(DOMString? namespace, DOMString localName); @@ -379,7 +379,7 @@ interface Element : Node { HTMLCollection getElementsByClassName(DOMString classNames); [CEReactions] Element? insertAdjacentElement(DOMString where, Element element); // historical - void insertAdjacentText(DOMString where, DOMString data); // historical + undefined insertAdjacentText(DOMString where, DOMString data); // historical }; dictionary ShadowRootInit { @@ -417,10 +417,10 @@ interface CharacterData : Node { attribute [LegacyNullToEmptyString] DOMString data; readonly attribute unsigned long length; DOMString substringData(unsigned long offset, unsigned long count); - void appendData(DOMString data); - void insertData(unsigned long offset, DOMString data); - void deleteData(unsigned long offset, unsigned long count); - void replaceData(unsigned long offset, unsigned long count, DOMString data); + undefined appendData(DOMString data); + undefined insertData(unsigned long offset, DOMString data); + undefined deleteData(unsigned long offset, unsigned long count); + undefined replaceData(unsigned long offset, unsigned long count, DOMString data); }; [Exposed=Window] @@ -470,15 +470,15 @@ interface Range : AbstractRange { readonly attribute Node commonAncestorContainer; - void setStart(Node node, unsigned long offset); - void setEnd(Node node, unsigned long offset); - void setStartBefore(Node node); - void setStartAfter(Node node); - void setEndBefore(Node node); - void setEndAfter(Node node); - void collapse(optional boolean toStart = false); - void selectNode(Node node); - void selectNodeContents(Node node); + undefined setStart(Node node, unsigned long offset); + undefined setEnd(Node node, unsigned long offset); + undefined setStartBefore(Node node); + undefined setStartAfter(Node node); + undefined setEndBefore(Node node); + undefined setEndAfter(Node node); + undefined collapse(optional boolean toStart = false); + undefined selectNode(Node node); + undefined selectNodeContents(Node node); const unsigned short START_TO_START = 0; const unsigned short START_TO_END = 1; @@ -486,14 +486,14 @@ interface Range : AbstractRange { const unsigned short END_TO_START = 3; short compareBoundaryPoints(unsigned short how, Range sourceRange); - [CEReactions] void deleteContents(); + [CEReactions] undefined deleteContents(); [CEReactions, NewObject] DocumentFragment extractContents(); [CEReactions, NewObject] DocumentFragment cloneContents(); - [CEReactions] void insertNode(Node node); - [CEReactions] void surroundContents(Node newParent); + [CEReactions] undefined insertNode(Node node); + [CEReactions] undefined surroundContents(Node newParent); [NewObject] Range cloneRange(); - void detach(); + undefined detach(); boolean isPointInRange(Node node, unsigned long offset); short comparePoint(Node node, unsigned long offset); @@ -514,7 +514,7 @@ interface NodeIterator { Node? nextNode(); Node? previousNode(); - void detach(); + undefined detach(); }; [Exposed=Window] @@ -562,8 +562,8 @@ interface DOMTokenList { readonly attribute unsigned long length; getter DOMString? item(unsigned long index); boolean contains(DOMString token); - [CEReactions] void add(DOMString... tokens); - [CEReactions] void remove(DOMString... tokens); + [CEReactions] undefined add(DOMString... tokens); + [CEReactions] undefined remove(DOMString... tokens); [CEReactions] boolean toggle(DOMString token, optional boolean force); [CEReactions] boolean replace(DOMString token, DOMString newToken); boolean supports(DOMString token); diff --git a/test/fixtures/wpt/interfaces/encoding.idl b/test/fixtures/wpt/interfaces/encoding.idl index d00624fa1ed7ed..bae48f11993e5f 100644 --- a/test/fixtures/wpt/interfaces/encoding.idl +++ b/test/fixtures/wpt/interfaces/encoding.idl @@ -1,6 +1,6 @@ // GENERATED CONTENT - DO NOT EDIT -// Content was automatically extracted by Reffy into reffy-reports -// (https://github.com/tidoust/reffy-reports) +// Content was automatically extracted by Reffy into webref +// (https://github.com/w3c/webref) // Source: Encoding Standard (https://encoding.spec.whatwg.org/) interface mixin TextDecoderCommon { diff --git a/test/fixtures/wpt/interfaces/hr-time.idl b/test/fixtures/wpt/interfaces/hr-time.idl index 8179c66f44de27..93537d64ad18d7 100644 --- a/test/fixtures/wpt/interfaces/hr-time.idl +++ b/test/fixtures/wpt/interfaces/hr-time.idl @@ -1,6 +1,6 @@ // GENERATED CONTENT - DO NOT EDIT -// Content was automatically extracted by Reffy into reffy-reports -// (https://github.com/tidoust/reffy-reports) +// Content was automatically extracted by Reffy into webref +// (https://github.com/w3c/webref) // Source: High Resolution Time (https://w3c.github.io/hr-time/) typedef double DOMHighResTimeStamp; diff --git a/test/fixtures/wpt/interfaces/html.idl b/test/fixtures/wpt/interfaces/html.idl index 7b2a96ce6760a0..a1f9ec99afe57e 100644 --- a/test/fixtures/wpt/interfaces/html.idl +++ b/test/fixtures/wpt/interfaces/html.idl @@ -1,6 +1,6 @@ // GENERATED CONTENT - DO NOT EDIT -// Content was automatically extracted by Reffy into reffy-reports -// (https://github.com/tidoust/reffy-reports) +// Content was automatically extracted by Reffy into webref +// (https://github.com/w3c/webref) // Source: HTML Standard (https://html.spec.whatwg.org/multipage/) [Exposed=Window, @@ -29,9 +29,9 @@ interface RadioNodeList : NodeList { interface HTMLOptionsCollection : HTMLCollection { // inherits item(), namedItem() [CEReactions] attribute unsigned long length; // shadows inherited length - [CEReactions] setter void (unsigned long index, HTMLOptionElement? option); - [CEReactions] void add((HTMLOptionElement or HTMLOptGroupElement) element, optional (HTMLElement or long)? before = null); - [CEReactions] void remove(long index); + [CEReactions] setter undefined (unsigned long index, HTMLOptionElement? option); + [CEReactions] undefined add((HTMLOptionElement or HTMLOptGroupElement) element, optional (HTMLElement or long)? before = null); + [CEReactions] undefined remove(long index); attribute long selectedIndex; }; @@ -73,9 +73,9 @@ partial interface Document { // dynamic markup insertion [CEReactions] Document open(optional DOMString unused1, optional DOMString unused2); // both arguments are ignored WindowProxy? open(USVString url, DOMString name, DOMString features); - [CEReactions] void close(); - [CEReactions] void write(DOMString... text); - [CEReactions] void writeln(DOMString... text); + [CEReactions] undefined close(); + [CEReactions] undefined write(DOMString... text); + [CEReactions] undefined writeln(DOMString... text); // user interaction readonly attribute WindowProxy? defaultView; @@ -112,7 +112,7 @@ interface HTMLElement : Element { // user interaction [CEReactions] attribute boolean hidden; - void click(); + undefined click(); [CEReactions] attribute DOMString accessKey; readonly attribute DOMString accessKeyLabel; [CEReactions] attribute boolean draggable; @@ -140,21 +140,23 @@ interface mixin HTMLOrSVGElement { [CEReactions] attribute boolean autofocus; [CEReactions] attribute long tabIndex; - void focus(optional FocusOptions options = {}); - void blur(); + undefined focus(optional FocusOptions options = {}); + undefined blur(); }; [Exposed=Window, LegacyOverrideBuiltIns] interface DOMStringMap { getter DOMString (DOMString name); - [CEReactions] setter void (DOMString name, DOMString value); - [CEReactions] deleter void (DOMString name); + [CEReactions] setter undefined (DOMString name, DOMString value); + [CEReactions] deleter undefined (DOMString name); }; [Exposed=Window] interface HTMLHtmlElement : HTMLElement { [HTMLConstructor] constructor(); + + // also has obsolete members }; [Exposed=Window] @@ -194,6 +196,9 @@ interface HTMLLinkElement : HTMLElement { [CEReactions] attribute USVString imageSrcset; [CEReactions] attribute DOMString imageSizes; [CEReactions] attribute DOMString referrerPolicy; + [CEReactions] attribute boolean disabled; + + // also has obsolete members }; HTMLLinkElement includes LinkStyle; @@ -204,6 +209,8 @@ interface HTMLMetaElement : HTMLElement { [CEReactions] attribute DOMString name; [CEReactions] attribute DOMString httpEquiv; [CEReactions] attribute DOMString content; + + // also has obsolete members }; [Exposed=Window] @@ -211,12 +218,16 @@ interface HTMLStyleElement : HTMLElement { [HTMLConstructor] constructor(); [CEReactions] attribute DOMString media; + + // also has obsolete members }; HTMLStyleElement includes LinkStyle; [Exposed=Window] interface HTMLBodyElement : HTMLElement { [HTMLConstructor] constructor(); + + // also has obsolete members }; HTMLBodyElement includes WindowEventHandlers; @@ -224,21 +235,29 @@ HTMLBodyElement includes WindowEventHandlers; [Exposed=Window] interface HTMLHeadingElement : HTMLElement { [HTMLConstructor] constructor(); + + // also has obsolete members }; [Exposed=Window] interface HTMLParagraphElement : HTMLElement { [HTMLConstructor] constructor(); + + // also has obsolete members }; [Exposed=Window] interface HTMLHRElement : HTMLElement { [HTMLConstructor] constructor(); + + // also has obsolete members }; [Exposed=Window] interface HTMLPreElement : HTMLElement { [HTMLConstructor] constructor(); + + // also has obsolete members }; [Exposed=Window] @@ -255,16 +274,22 @@ interface HTMLOListElement : HTMLElement { [CEReactions] attribute boolean reversed; [CEReactions] attribute long start; [CEReactions] attribute DOMString type; + + // also has obsolete members }; [Exposed=Window] interface HTMLUListElement : HTMLElement { [HTMLConstructor] constructor(); + + // also has obsolete members }; [Exposed=Window] interface HTMLMenuElement : HTMLElement { [HTMLConstructor] constructor(); + + // also has obsolete members }; [Exposed=Window] @@ -272,16 +297,22 @@ interface HTMLLIElement : HTMLElement { [HTMLConstructor] constructor(); [CEReactions] attribute long value; + + // also has obsolete members }; [Exposed=Window] interface HTMLDListElement : HTMLElement { [HTMLConstructor] constructor(); + + // also has obsolete members }; [Exposed=Window] interface HTMLDivElement : HTMLElement { [HTMLConstructor] constructor(); + + // also has obsolete members }; [Exposed=Window] @@ -299,6 +330,8 @@ interface HTMLAnchorElement : HTMLElement { [CEReactions] attribute DOMString text; [CEReactions] attribute DOMString referrerPolicy; + + // also has obsolete members }; HTMLAnchorElement includes HTMLHyperlinkElementUtils; @@ -324,6 +357,8 @@ interface HTMLSpanElement : HTMLElement { [Exposed=Window] interface HTMLBRElement : HTMLElement { [HTMLConstructor] constructor(); + + // also has obsolete members }; interface mixin HTMLHyperlinkElementUtils { @@ -386,7 +421,9 @@ interface HTMLImageElement : HTMLElement { [CEReactions] attribute DOMString decoding; [CEReactions] attribute DOMString loading; - Promise decode(); + Promise decode(); + + // also has obsolete members }; [Exposed=Window] @@ -403,9 +440,12 @@ interface HTMLIFrameElement : HTMLElement { [CEReactions] attribute DOMString width; [CEReactions] attribute DOMString height; [CEReactions] attribute DOMString referrerPolicy; + [CEReactions] attribute DOMString loading; readonly attribute Document? contentDocument; readonly attribute WindowProxy? contentWindow; Document? getSVGDocument(); + + // also has obsolete members }; [Exposed=Window] @@ -417,6 +457,8 @@ interface HTMLEmbedElement : HTMLElement { [CEReactions] attribute DOMString width; [CEReactions] attribute DOMString height; Document? getSVGDocument(); + + // also has obsolete members }; [Exposed=Window] @@ -439,7 +481,9 @@ interface HTMLObjectElement : HTMLElement { readonly attribute DOMString validationMessage; boolean checkValidity(); boolean reportValidity(); - void setCustomValidity(DOMString error); + undefined setCustomValidity(DOMString error); + + // also has obsolete members }; [Exposed=Window] @@ -448,6 +492,8 @@ interface HTMLParamElement : HTMLElement { [CEReactions] attribute DOMString name; [CEReactions] attribute DOMString value; + + // also has obsolete members }; [Exposed=Window] @@ -508,7 +554,7 @@ interface HTMLMediaElement : HTMLElement { readonly attribute unsigned short networkState; [CEReactions] attribute DOMString preload; readonly attribute TimeRanges buffered; - void load(); + undefined load(); CanPlayTypeResult canPlayType(DOMString type); // ready state @@ -522,19 +568,20 @@ interface HTMLMediaElement : HTMLElement { // playback state attribute double currentTime; - void fastSeek(double time); + undefined fastSeek(double time); readonly attribute unrestricted double duration; object getStartDate(); readonly attribute boolean paused; attribute double defaultPlaybackRate; attribute double playbackRate; + attribute boolean preservesPitch; readonly attribute TimeRanges played; readonly attribute TimeRanges seekable; readonly attribute boolean ended; [CEReactions] attribute boolean autoplay; [CEReactions] attribute boolean loop; - Promise play(); - void pause(); + Promise play(); + undefined pause(); // controls [CEReactions] attribute boolean controls; @@ -629,8 +676,8 @@ interface TextTrack : EventTarget { readonly attribute TextTrackCueList? cues; readonly attribute TextTrackCueList? activeCues; - void addCue(TextTrackCue cue); - void removeCue(TextTrackCue cue); + undefined addCue(TextTrackCue cue); + undefined removeCue(TextTrackCue cue); attribute EventHandler oncuechange; }; @@ -662,9 +709,10 @@ interface TimeRanges { double end(unsigned long index); }; -[Exposed=Window, - Constructor(DOMString type, optional TrackEventInit eventInitDict = {})] +[Exposed=Window] interface TrackEvent : Event { + constructor(DOMString type, optional TrackEventInit eventInitDict = {}); + readonly attribute (VideoTrack or AudioTrack or TextTrack)? track; }; @@ -693,6 +741,8 @@ interface HTMLAreaElement : HTMLElement { [CEReactions] attribute DOMString rel; [SameObject, PutForwards=value] readonly attribute DOMTokenList relList; [CEReactions] attribute DOMString referrerPolicy; + + // also has obsolete members }; HTMLAreaElement includes HTMLHyperlinkElementUtils; @@ -702,27 +752,31 @@ interface HTMLTableElement : HTMLElement { [CEReactions] attribute HTMLTableCaptionElement? caption; HTMLTableCaptionElement createCaption(); - [CEReactions] void deleteCaption(); + [CEReactions] undefined deleteCaption(); [CEReactions] attribute HTMLTableSectionElement? tHead; HTMLTableSectionElement createTHead(); - [CEReactions] void deleteTHead(); + [CEReactions] undefined deleteTHead(); [CEReactions] attribute HTMLTableSectionElement? tFoot; HTMLTableSectionElement createTFoot(); - [CEReactions] void deleteTFoot(); + [CEReactions] undefined deleteTFoot(); [SameObject] readonly attribute HTMLCollection tBodies; HTMLTableSectionElement createTBody(); [SameObject] readonly attribute HTMLCollection rows; HTMLTableRowElement insertRow(optional long index = -1); - [CEReactions] void deleteRow(long index); + [CEReactions] undefined deleteRow(long index); + + // also has obsolete members }; [Exposed=Window] interface HTMLTableCaptionElement : HTMLElement { [HTMLConstructor] constructor(); + + // also has obsolete members }; [Exposed=Window] @@ -730,6 +784,8 @@ interface HTMLTableColElement : HTMLElement { [HTMLConstructor] constructor(); [CEReactions] attribute unsigned long span; + + // also has obsolete members }; [Exposed=Window] @@ -738,7 +794,9 @@ interface HTMLTableSectionElement : HTMLElement { [SameObject] readonly attribute HTMLCollection rows; HTMLTableRowElement insertRow(optional long index = -1); - [CEReactions] void deleteRow(long index); + [CEReactions] undefined deleteRow(long index); + + // also has obsolete members }; [Exposed=Window] @@ -749,7 +807,9 @@ interface HTMLTableRowElement : HTMLElement { readonly attribute long sectionRowIndex; [SameObject] readonly attribute HTMLCollection cells; HTMLTableCellElement insertCell(optional long index = -1); - [CEReactions] void deleteCell(long index); + [CEReactions] undefined deleteCell(long index); + + // also has obsolete members }; [Exposed=Window] @@ -763,6 +823,8 @@ interface HTMLTableCellElement : HTMLElement { [CEReactions] attribute DOMString scope; // only conforming for th elements [CEReactions] attribute DOMString abbr; // only conforming for th elements + + // also has obsolete members }; [Exposed=Window, @@ -788,9 +850,9 @@ interface HTMLFormElement : HTMLElement { getter Element (unsigned long index); getter (RadioNodeList or Element) (DOMString name); - void submit(); - void requestSubmit(optional HTMLElement? submitter = null); - [CEReactions] void reset(); + undefined submit(); + undefined requestSubmit(optional HTMLElement? submitter = null); + [CEReactions] undefined reset(); boolean checkValidity(); boolean reportValidity(); }; @@ -845,25 +907,27 @@ interface HTMLInputElement : HTMLElement { attribute unrestricted double valueAsNumber; [CEReactions] attribute unsigned long width; - void stepUp(optional long n = 1); - void stepDown(optional long n = 1); + undefined stepUp(optional long n = 1); + undefined stepDown(optional long n = 1); readonly attribute boolean willValidate; readonly attribute ValidityState validity; readonly attribute DOMString validationMessage; boolean checkValidity(); boolean reportValidity(); - void setCustomValidity(DOMString error); + undefined setCustomValidity(DOMString error); readonly attribute NodeList? labels; - void select(); + undefined select(); attribute unsigned long? selectionStart; attribute unsigned long? selectionEnd; attribute DOMString? selectionDirection; - void setRangeText(DOMString replacement); - void setRangeText(DOMString replacement, unsigned long start, unsigned long end, optional SelectionMode selectionMode = "preserve"); - void setSelectionRange(unsigned long start, unsigned long end, optional DOMString direction); + undefined setRangeText(DOMString replacement); + undefined setRangeText(DOMString replacement, unsigned long start, unsigned long end, optional SelectionMode selectionMode = "preserve"); + undefined setSelectionRange(unsigned long start, unsigned long end, optional DOMString direction); + + // also has obsolete members }; [Exposed=Window] @@ -886,7 +950,7 @@ interface HTMLButtonElement : HTMLElement { readonly attribute DOMString validationMessage; boolean checkValidity(); boolean reportValidity(); - void setCustomValidity(DOMString error); + undefined setCustomValidity(DOMString error); readonly attribute NodeList labels; }; @@ -909,10 +973,10 @@ interface HTMLSelectElement : HTMLElement { [CEReactions] attribute unsigned long length; getter Element? item(unsigned long index); HTMLOptionElement? namedItem(DOMString name); - [CEReactions] void add((HTMLOptionElement or HTMLOptGroupElement) element, optional (HTMLElement or long)? before = null); - [CEReactions] void remove(); // ChildNode overload - [CEReactions] void remove(long index); - [CEReactions] setter void (unsigned long index, HTMLOptionElement? option); + [CEReactions] undefined add((HTMLOptionElement or HTMLOptGroupElement) element, optional (HTMLElement or long)? before = null); + [CEReactions] undefined remove(); // ChildNode overload + [CEReactions] undefined remove(long index); + [CEReactions] setter undefined (unsigned long index, HTMLOptionElement? option); [SameObject] readonly attribute HTMLCollection selectedOptions; attribute long selectedIndex; @@ -923,7 +987,7 @@ interface HTMLSelectElement : HTMLElement { readonly attribute DOMString validationMessage; boolean checkValidity(); boolean reportValidity(); - void setCustomValidity(DOMString error); + undefined setCustomValidity(DOMString error); readonly attribute NodeList labels; }; @@ -987,17 +1051,17 @@ interface HTMLTextAreaElement : HTMLElement { readonly attribute DOMString validationMessage; boolean checkValidity(); boolean reportValidity(); - void setCustomValidity(DOMString error); + undefined setCustomValidity(DOMString error); readonly attribute NodeList labels; - void select(); + undefined select(); attribute unsigned long selectionStart; attribute unsigned long selectionEnd; attribute DOMString selectionDirection; - void setRangeText(DOMString replacement); - void setRangeText(DOMString replacement, unsigned long start, unsigned long end, optional SelectionMode selectionMode = "preserve"); - void setSelectionRange(unsigned long start, unsigned long end, optional DOMString direction); + undefined setRangeText(DOMString replacement); + undefined setRangeText(DOMString replacement, unsigned long start, unsigned long end, optional SelectionMode selectionMode = "preserve"); + undefined setSelectionRange(unsigned long start, unsigned long end, optional DOMString direction); }; [Exposed=Window] @@ -1017,7 +1081,7 @@ interface HTMLOutputElement : HTMLElement { readonly attribute DOMString validationMessage; boolean checkValidity(); boolean reportValidity(); - void setCustomValidity(DOMString error); + undefined setCustomValidity(DOMString error); readonly attribute NodeList labels; }; @@ -1062,7 +1126,7 @@ interface HTMLFieldSetElement : HTMLElement { readonly attribute DOMString validationMessage; boolean checkValidity(); boolean reportValidity(); - void setCustomValidity(DOMString error); + undefined setCustomValidity(DOMString error); }; [Exposed=Window] @@ -1070,6 +1134,8 @@ interface HTMLLegendElement : HTMLElement { [HTMLConstructor] constructor(); readonly attribute HTMLFormElement? form; + + // also has obsolete members }; enum SelectionMode { @@ -1129,9 +1195,9 @@ interface HTMLDialogElement : HTMLElement { [CEReactions] attribute boolean open; attribute DOMString returnValue; - [CEReactions] void show(); - [CEReactions] void showModal(); - [CEReactions] void close(optional DOMString returnValue); + [CEReactions] undefined show(); + [CEReactions] undefined showModal(); + [CEReactions] undefined close(optional DOMString returnValue); }; [Exposed=Window] @@ -1148,6 +1214,7 @@ interface HTMLScriptElement : HTMLElement { [CEReactions] attribute DOMString integrity; [CEReactions] attribute DOMString referrerPolicy; + // also has obsolete members }; [Exposed=Window] @@ -1182,11 +1249,11 @@ interface HTMLCanvasElement : HTMLElement { RenderingContext? getContext(DOMString contextId, optional any options = null); USVString toDataURL(optional DOMString type = "image/png", optional any quality); - void toBlob(BlobCallback _callback, optional DOMString type = "image/png", optional any quality); + undefined toBlob(BlobCallback _callback, optional DOMString type = "image/png", optional any quality); OffscreenCanvas transferControlToOffscreen(); }; -callback BlobCallback = void (Blob? blob); +callback BlobCallback = undefined (Blob? blob); typedef (HTMLImageElement or SVGImageElement) HTMLOrSVGImageElement; @@ -1232,21 +1299,21 @@ CanvasRenderingContext2D includes CanvasPath; interface mixin CanvasState { // state - void save(); // push state on state stack - void restore(); // pop state stack and restore state + undefined save(); // push state on state stack + undefined restore(); // pop state stack and restore state }; interface mixin CanvasTransform { // transformations (default transform is the identity matrix) - void scale(unrestricted double x, unrestricted double y); - void rotate(unrestricted double angle); - void translate(unrestricted double x, unrestricted double y); - void transform(unrestricted double a, unrestricted double b, unrestricted double c, unrestricted double d, unrestricted double e, unrestricted double f); + undefined scale(unrestricted double x, unrestricted double y); + undefined rotate(unrestricted double angle); + undefined translate(unrestricted double x, unrestricted double y); + undefined transform(unrestricted double a, unrestricted double b, unrestricted double c, unrestricted double d, unrestricted double e, unrestricted double f); [NewObject] DOMMatrix getTransform(); - void setTransform(unrestricted double a, unrestricted double b, unrestricted double c, unrestricted double d, unrestricted double e, unrestricted double f); - void setTransform(optional DOMMatrix2DInit transform = {}); - void resetTransform(); + undefined setTransform(unrestricted double a, unrestricted double b, unrestricted double c, unrestricted double d, unrestricted double e, unrestricted double f); + undefined setTransform(optional DOMMatrix2DInit transform = {}); + undefined resetTransform(); }; @@ -1288,20 +1355,20 @@ interface mixin CanvasFilters { interface mixin CanvasRect { // rects - void clearRect(unrestricted double x, unrestricted double y, unrestricted double w, unrestricted double h); - void fillRect(unrestricted double x, unrestricted double y, unrestricted double w, unrestricted double h); - void strokeRect(unrestricted double x, unrestricted double y, unrestricted double w, unrestricted double h); + undefined clearRect(unrestricted double x, unrestricted double y, unrestricted double w, unrestricted double h); + undefined fillRect(unrestricted double x, unrestricted double y, unrestricted double w, unrestricted double h); + undefined strokeRect(unrestricted double x, unrestricted double y, unrestricted double w, unrestricted double h); }; interface mixin CanvasDrawPath { // path API (see also CanvasPath) - void beginPath(); - void fill(optional CanvasFillRule fillRule = "nonzero"); - void fill(Path2D path, optional CanvasFillRule fillRule = "nonzero"); - void stroke(); - void stroke(Path2D path); - void clip(optional CanvasFillRule fillRule = "nonzero"); - void clip(Path2D path, optional CanvasFillRule fillRule = "nonzero"); + undefined beginPath(); + undefined fill(optional CanvasFillRule fillRule = "nonzero"); + undefined fill(Path2D path, optional CanvasFillRule fillRule = "nonzero"); + undefined stroke(); + undefined stroke(Path2D path); + undefined clip(optional CanvasFillRule fillRule = "nonzero"); + undefined clip(Path2D path, optional CanvasFillRule fillRule = "nonzero"); boolean isPointInPath(unrestricted double x, unrestricted double y, optional CanvasFillRule fillRule = "nonzero"); boolean isPointInPath(Path2D path, unrestricted double x, unrestricted double y, optional CanvasFillRule fillRule = "nonzero"); boolean isPointInStroke(unrestricted double x, unrestricted double y); @@ -1309,24 +1376,24 @@ interface mixin CanvasDrawPath { }; interface mixin CanvasUserInterface { - void drawFocusIfNeeded(Element element); - void drawFocusIfNeeded(Path2D path, Element element); - void scrollPathIntoView(); - void scrollPathIntoView(Path2D path); + undefined drawFocusIfNeeded(Element element); + undefined drawFocusIfNeeded(Path2D path, Element element); + undefined scrollPathIntoView(); + undefined scrollPathIntoView(Path2D path); }; interface mixin CanvasText { // text (see also the CanvasPathDrawingStyles and CanvasTextDrawingStyles interfaces) - void fillText(DOMString text, unrestricted double x, unrestricted double y, optional unrestricted double maxWidth); - void strokeText(DOMString text, unrestricted double x, unrestricted double y, optional unrestricted double maxWidth); + undefined fillText(DOMString text, unrestricted double x, unrestricted double y, optional unrestricted double maxWidth); + undefined strokeText(DOMString text, unrestricted double x, unrestricted double y, optional unrestricted double maxWidth); TextMetrics measureText(DOMString text); }; interface mixin CanvasDrawImage { // drawing images - void drawImage(CanvasImageSource image, unrestricted double dx, unrestricted double dy); - void drawImage(CanvasImageSource image, unrestricted double dx, unrestricted double dy, unrestricted double dw, unrestricted double dh); - void drawImage(CanvasImageSource image, unrestricted double sx, unrestricted double sy, unrestricted double sw, unrestricted double sh, unrestricted double dx, unrestricted double dy, unrestricted double dw, unrestricted double dh); + undefined drawImage(CanvasImageSource image, unrestricted double dx, unrestricted double dy); + undefined drawImage(CanvasImageSource image, unrestricted double dx, unrestricted double dy, unrestricted double dw, unrestricted double dh); + undefined drawImage(CanvasImageSource image, unrestricted double sx, unrestricted double sy, unrestricted double sw, unrestricted double sh, unrestricted double dx, unrestricted double dy, unrestricted double dw, unrestricted double dh); }; interface mixin CanvasImageData { @@ -1334,8 +1401,8 @@ interface mixin CanvasImageData { ImageData createImageData(long sw, long sh); ImageData createImageData(ImageData imagedata); ImageData getImageData(long sx, long sy, long sw, long sh); - void putImageData(ImageData imagedata, long dx, long dy); - void putImageData(ImageData imagedata, long dx, long dy, long dirtyX, long dirtyY, long dirtyWidth, long dirtyHeight); + undefined putImageData(ImageData imagedata, long dx, long dy); + undefined putImageData(ImageData imagedata, long dx, long dy, long dirtyX, long dirtyY, long dirtyWidth, long dirtyHeight); }; enum CanvasLineCap { "butt", "round", "square" }; @@ -1352,7 +1419,7 @@ interface mixin CanvasPathDrawingStyles { attribute unrestricted double miterLimit; // (default 10) // dashed lines - void setLineDash(sequence segments); // default empty + undefined setLineDash(sequence segments); // default empty sequence getLineDash(); attribute unrestricted double lineDashOffset; }; @@ -1367,27 +1434,27 @@ interface mixin CanvasTextDrawingStyles { interface mixin CanvasPath { // shared path API methods - void closePath(); - void moveTo(unrestricted double x, unrestricted double y); - void lineTo(unrestricted double x, unrestricted double y); - void quadraticCurveTo(unrestricted double cpx, unrestricted double cpy, unrestricted double x, unrestricted double y); - void bezierCurveTo(unrestricted double cp1x, unrestricted double cp1y, unrestricted double cp2x, unrestricted double cp2y, unrestricted double x, unrestricted double y); - void arcTo(unrestricted double x1, unrestricted double y1, unrestricted double x2, unrestricted double y2, unrestricted double radius); - void rect(unrestricted double x, unrestricted double y, unrestricted double w, unrestricted double h); - void arc(unrestricted double x, unrestricted double y, unrestricted double radius, unrestricted double startAngle, unrestricted double endAngle, optional boolean anticlockwise = false); - void ellipse(unrestricted double x, unrestricted double y, unrestricted double radiusX, unrestricted double radiusY, unrestricted double rotation, unrestricted double startAngle, unrestricted double endAngle, optional boolean anticlockwise = false); + undefined closePath(); + undefined moveTo(unrestricted double x, unrestricted double y); + undefined lineTo(unrestricted double x, unrestricted double y); + undefined quadraticCurveTo(unrestricted double cpx, unrestricted double cpy, unrestricted double x, unrestricted double y); + undefined bezierCurveTo(unrestricted double cp1x, unrestricted double cp1y, unrestricted double cp2x, unrestricted double cp2y, unrestricted double x, unrestricted double y); + undefined arcTo(unrestricted double x1, unrestricted double y1, unrestricted double x2, unrestricted double y2, unrestricted double radius); + undefined rect(unrestricted double x, unrestricted double y, unrestricted double w, unrestricted double h); + undefined arc(unrestricted double x, unrestricted double y, unrestricted double radius, unrestricted double startAngle, unrestricted double endAngle, optional boolean anticlockwise = false); + undefined ellipse(unrestricted double x, unrestricted double y, unrestricted double radiusX, unrestricted double radiusY, unrestricted double rotation, unrestricted double startAngle, unrestricted double endAngle, optional boolean anticlockwise = false); }; [Exposed=(Window,Worker)] interface CanvasGradient { // opaque object - void addColorStop(double offset, DOMString color); + undefined addColorStop(double offset, DOMString color); }; [Exposed=(Window,Worker)] interface CanvasPattern { // opaque object - void setTransform(optional DOMMatrix2DInit transform = {}); + undefined setTransform(optional DOMMatrix2DInit transform = {}); }; [Exposed=(Window,Worker)] @@ -1424,14 +1491,14 @@ interface ImageData { interface Path2D { constructor(optional (Path2D or DOMString) path); - void addPath(Path2D path, optional DOMMatrix2DInit transform = {}); + undefined addPath(Path2D path, optional DOMMatrix2DInit transform = {}); }; Path2D includes CanvasPath; [Exposed=(Window,Worker)] interface ImageBitmapRenderingContext { readonly attribute (HTMLCanvasElement or OffscreenCanvas) canvas; - void transferFromImageBitmap(ImageBitmap? bitmap); + undefined transferFromImageBitmap(ImageBitmap? bitmap); }; dictionary ImageBitmapRenderingContextSettings { @@ -1461,7 +1528,7 @@ interface OffscreenCanvas : EventTarget { [Exposed=(Window,Worker)] interface OffscreenCanvasRenderingContext2D { - void commit(); + undefined commit(); readonly attribute OffscreenCanvas canvas; }; @@ -1483,10 +1550,10 @@ OffscreenCanvasRenderingContext2D includes CanvasPath; [Exposed=Window] interface CustomElementRegistry { - [CEReactions] void define(DOMString name, CustomElementConstructor constructor, optional ElementDefinitionOptions options = {}); - any get(DOMString name); - Promise whenDefined(DOMString name); - [CEReactions] void upgrade(Node root); + [CEReactions] undefined define(DOMString name, CustomElementConstructor constructor, optional ElementDefinitionOptions options = {}); + (CustomElementConstructor or undefined) get(DOMString name); + Promise whenDefined(DOMString name); + [CEReactions] undefined upgrade(Node root); }; callback CustomElementConstructor = HTMLElement (); @@ -1499,12 +1566,12 @@ dictionary ElementDefinitionOptions { interface ElementInternals { // Form-associated custom elements - void setFormValue((File or USVString or FormData)? value, + undefined setFormValue((File or USVString or FormData)? value, optional (File or USVString or FormData)? state); readonly attribute HTMLFormElement? form; - void setValidity(optional ValidityStateFlags flags = {}, + undefined setValidity(optional ValidityStateFlags flags = {}, optional DOMString message, optional HTMLElement anchor); readonly attribute boolean willValidate; @@ -1540,21 +1607,22 @@ interface mixin ElementContentEditable { [CEReactions] attribute DOMString inputMode; }; -[Exposed=Window, - Constructor] +[Exposed=Window] interface DataTransfer { + constructor(); + attribute DOMString dropEffect; attribute DOMString effectAllowed; [SameObject] readonly attribute DataTransferItemList items; - void setDragImage(Element image, long x, long y); + undefined setDragImage(Element image, long x, long y); /* old interface */ readonly attribute FrozenArray types; DOMString getData(DOMString format); - void setData(DOMString format, DOMString data); - void clearData(optional DOMString format); + undefined setData(DOMString format, DOMString data); + undefined clearData(optional DOMString format); [SameObject] readonly attribute FileList files; }; @@ -1564,23 +1632,24 @@ interface DataTransferItemList { getter DataTransferItem (unsigned long index); DataTransferItem? add(DOMString data, DOMString type); DataTransferItem? add(File data); - void remove(unsigned long index); - void clear(); + undefined remove(unsigned long index); + undefined clear(); }; [Exposed=Window] interface DataTransferItem { readonly attribute DOMString kind; readonly attribute DOMString type; - void getAsString(FunctionStringCallback? _callback); + undefined getAsString(FunctionStringCallback? _callback); File? getAsFile(); }; -callback FunctionStringCallback = void (DOMString data); +callback FunctionStringCallback = undefined (DOMString data); -[Exposed=Window, - Constructor(DOMString type, optional DragEventInit eventInitDict = {})] +[Exposed=Window] interface DragEvent : MouseEvent { + constructor(DOMString type, optional DragEventInit eventInitDict = {}); + readonly attribute DataTransfer? dataTransfer; }; @@ -1607,11 +1676,11 @@ interface Window : EventTarget { [Replaceable] readonly attribute BarProp statusbar; [Replaceable] readonly attribute BarProp toolbar; attribute DOMString status; - void close(); + undefined close(); readonly attribute boolean closed; - void stop(); - void focus(); - void blur(); + undefined stop(); + undefined focus(); + undefined blur(); // other browsing contexts [Replaceable] readonly attribute WindowProxy frames; @@ -1629,16 +1698,19 @@ interface Window : EventTarget { // the user agent readonly attribute Navigator navigator; [SecureContext] readonly attribute ApplicationCache applicationCache; + readonly attribute boolean originIsolated; // user prompts - void alert(); - void alert(DOMString message); + undefined alert(); + undefined alert(DOMString message); boolean confirm(optional DOMString message = ""); DOMString? prompt(optional DOMString message = "", optional DOMString default = ""); - void print(); + undefined print(); - void postMessage(any message, USVString targetOrigin, optional sequence transfer = []); - void postMessage(any message, optional WindowPostMessageOptions options = {}); + undefined postMessage(any message, USVString targetOrigin, optional sequence transfer = []); + undefined postMessage(any message, optional WindowPostMessageOptions options = {}); + + // also has obsolete members }; Window includes GlobalEventHandlers; Window includes WindowEventHandlers; @@ -1659,11 +1731,11 @@ interface History { readonly attribute unsigned long length; attribute ScrollRestoration scrollRestoration; readonly attribute any state; - void go(optional long delta = 0); - void back(); - void forward(); - void pushState(any data, DOMString title, optional USVString? url = null); - void replaceState(any data, DOMString title, optional USVString? url = null); + undefined go(optional long delta = 0); + undefined back(); + undefined forward(); + undefined pushState(any data, DOMString title, optional USVString? url = null); + undefined replaceState(any data, DOMString title, optional USVString? url = null); }; [Exposed=Window] @@ -1678,16 +1750,17 @@ interface Location { // but see also additional creation steps and overridden in [LegacyUnforgeable] attribute USVString search; [LegacyUnforgeable] attribute USVString hash; - [LegacyUnforgeable] void assign(USVString url); - [LegacyUnforgeable] void replace(USVString url); - [LegacyUnforgeable] void reload(); + [LegacyUnforgeable] undefined assign(USVString url); + [LegacyUnforgeable] undefined replace(USVString url); + [LegacyUnforgeable] undefined reload(); [LegacyUnforgeable, SameObject] readonly attribute DOMStringList ancestorOrigins; }; -[Exposed=Window, - Constructor(DOMString type, optional PopStateEventInit eventInitDict = {})] +[Exposed=Window] interface PopStateEvent : Event { + constructor(DOMString type, optional PopStateEventInit eventInitDict = {}); + readonly attribute any state; }; @@ -1695,9 +1768,10 @@ dictionary PopStateEventInit : EventInit { any state = null; }; -[Exposed=Window, - Constructor(DOMString type, optional HashChangeEventInit eventInitDict = {})] +[Exposed=Window] interface HashChangeEvent : Event { + constructor(DOMString type, optional HashChangeEventInit eventInitDict = {}); + readonly attribute USVString oldURL; readonly attribute USVString newURL; }; @@ -1707,9 +1781,10 @@ dictionary HashChangeEventInit : EventInit { USVString newURL = ""; }; -[Exposed=Window, - Constructor(DOMString type, optional PageTransitionEventInit eventInitDict = {})] +[Exposed=Window] interface PageTransitionEvent : Event { + constructor(DOMString type, optional PageTransitionEventInit eventInitDict = {}); + readonly attribute boolean persisted; }; @@ -1736,9 +1811,9 @@ interface ApplicationCache : EventTarget { readonly attribute unsigned short status; // updates - void update(); - void abort(); - void swapCache(); + undefined update(); + undefined abort(); + undefined swapCache(); // events attribute EventHandler onchecking; @@ -1815,7 +1890,6 @@ interface mixin GlobalEventHandlers { attribute EventHandler ondrag; attribute EventHandler ondragend; attribute EventHandler ondragenter; - attribute EventHandler ondragexit; attribute EventHandler ondragleave; attribute EventHandler ondragover; attribute EventHandler ondragstart; @@ -1898,6 +1972,8 @@ typedef (DOMString or Function) TimerHandler; interface mixin WindowOrWorkerGlobalScope { [Replaceable] readonly attribute USVString origin; + readonly attribute boolean isSecureContext; + readonly attribute boolean crossOriginIsolated; // base64 utility methods DOMString btoa(DOMString data); @@ -1905,12 +1981,12 @@ interface mixin WindowOrWorkerGlobalScope { // timers long setTimeout(TimerHandler handler, optional long timeout = 0, any... arguments); - void clearTimeout(optional long handle = 0); + undefined clearTimeout(optional long handle = 0); long setInterval(TimerHandler handler, optional long timeout = 0, any... arguments); - void clearInterval(optional long handle = 0); + undefined clearInterval(optional long handle = 0); // microtask queuing - void queueMicrotask(VoidFunction callback); + undefined queueMicrotask(VoidFunction callback); // ImageBitmap Promise createImageBitmap(ImageBitmapSource image, optional ImageBitmapOptions options = {}); @@ -1969,8 +2045,8 @@ interface mixin NavigatorLanguage { }; interface mixin NavigatorContentUtils { - [SecureContext] void registerProtocolHandler(DOMString scheme, USVString url); - [SecureContext] void unregisterProtocolHandler(DOMString scheme, USVString url); + [SecureContext] undefined registerProtocolHandler(DOMString scheme, USVString url); + [SecureContext] undefined unregisterProtocolHandler(DOMString scheme, USVString url); }; interface mixin NavigatorCookies { @@ -1986,7 +2062,7 @@ interface mixin NavigatorPlugins { [Exposed=Window, LegacyUnenumerableNamedProperties] interface PluginArray { - void refresh(optional boolean reload = false); + undefined refresh(optional boolean reload = false); readonly attribute unsigned long length; getter Plugin? item(unsigned long index); getter Plugin? namedItem(DOMString name); @@ -2023,7 +2099,7 @@ interface MimeType { interface ImageBitmap { readonly attribute unsigned long width; readonly attribute unsigned long height; - void close(); + undefined close(); }; typedef (CanvasImageSource or @@ -2044,11 +2120,11 @@ dictionary ImageBitmapOptions { ResizeQuality resizeQuality = "low"; }; -callback FrameRequestCallback = void (DOMHighResTimeStamp time); +callback FrameRequestCallback = undefined (DOMHighResTimeStamp time); interface mixin AnimationFrameProvider { unsigned long requestAnimationFrame(FrameRequestCallback callback); - void cancelAnimationFrame(unsigned long handle); + undefined cancelAnimationFrame(unsigned long handle); }; Window includes AnimationFrameProvider; DedicatedWorkerGlobalScope includes AnimationFrameProvider; @@ -2063,7 +2139,7 @@ interface MessageEvent : Event { readonly attribute MessageEventSource? source; readonly attribute FrozenArray ports; - void initMessageEvent(DOMString type, optional boolean bubbles = false, optional boolean cancelable = false, optional any data = null, optional USVString origin = "", optional DOMString lastEventId = "", optional MessageEventSource? source = null, optional sequence ports = []); + undefined initMessageEvent(DOMString type, optional boolean bubbles = false, optional boolean cancelable = false, optional any data = null, optional USVString origin = "", optional DOMString lastEventId = "", optional MessageEventSource? source = null, optional sequence ports = []); }; dictionary MessageEventInit : EventInit { @@ -2093,7 +2169,7 @@ interface EventSource : EventTarget { attribute EventHandler onopen; attribute EventHandler onmessage; attribute EventHandler onerror; - void close(); + undefined close(); }; dictionary EventSourceInit { @@ -2121,15 +2197,15 @@ interface WebSocket : EventTarget { attribute EventHandler onclose; readonly attribute DOMString extensions; readonly attribute DOMString protocol; - void close(optional [Clamp] unsigned short code, optional USVString reason); + undefined close(optional [Clamp] unsigned short code, optional USVString reason); // messaging attribute EventHandler onmessage; attribute BinaryType binaryType; - void send(USVString data); - void send(Blob data); - void send(ArrayBuffer data); - void send(ArrayBufferView data); + undefined send(USVString data); + undefined send(Blob data); + undefined send(ArrayBuffer data); + undefined send(ArrayBufferView data); }; [Exposed=(Window,Worker)] @@ -2147,18 +2223,20 @@ dictionary CloseEventInit : EventInit { USVString reason = ""; }; -[Constructor, Exposed=(Window,Worker)] +[Exposed=(Window,Worker)] interface MessageChannel { + constructor(); + readonly attribute MessagePort port1; readonly attribute MessagePort port2; }; [Exposed=(Window,Worker,AudioWorklet), Transferable] interface MessagePort : EventTarget { - void postMessage(any message, sequence transfer); - void postMessage(any message, optional PostMessageOptions options = {}); - void start(); - void close(); + undefined postMessage(any message, sequence transfer); + undefined postMessage(any message, optional PostMessageOptions options = {}); + undefined start(); + undefined close(); // event handlers attribute EventHandler onmessage; @@ -2174,8 +2252,8 @@ interface BroadcastChannel : EventTarget { constructor(DOMString name); readonly attribute DOMString name; - void postMessage(any message); - void close(); + undefined postMessage(any message); + undefined close(); attribute EventHandler onmessage; attribute EventHandler onmessageerror; }; @@ -2185,7 +2263,7 @@ interface WorkerGlobalScope : EventTarget { readonly attribute WorkerGlobalScope self; readonly attribute WorkerLocation location; readonly attribute WorkerNavigator navigator; - void importScripts(USVString... urls); + undefined importScripts(USVString... urls); attribute OnErrorEventHandler onerror; attribute EventHandler onlanguagechange; @@ -2199,10 +2277,10 @@ interface WorkerGlobalScope : EventTarget { interface DedicatedWorkerGlobalScope : WorkerGlobalScope { [Replaceable] readonly attribute DOMString name; - void postMessage(any message, sequence transfer); - void postMessage(any message, optional PostMessageOptions options = {}); + undefined postMessage(any message, sequence transfer); + undefined postMessage(any message, optional PostMessageOptions options = {}); - void close(); + undefined close(); attribute EventHandler onmessage; attribute EventHandler onmessageerror; @@ -2212,7 +2290,7 @@ interface DedicatedWorkerGlobalScope : WorkerGlobalScope { interface SharedWorkerGlobalScope : WorkerGlobalScope { [Replaceable] readonly attribute DOMString name; - void close(); + undefined close(); attribute EventHandler onconnect; }; @@ -2221,14 +2299,14 @@ interface mixin AbstractWorker { attribute EventHandler onerror; }; -[Exposed=(Window,Worker)] +[Exposed=(Window,DedicatedWorker,SharedWorker)] interface Worker : EventTarget { constructor(USVString scriptURL, optional WorkerOptions options = {}); - void terminate(); + undefined terminate(); - void postMessage(any message, sequence transfer); - void postMessage(any message, optional PostMessageOptions options = {}); + undefined postMessage(any message, sequence transfer); + undefined postMessage(any message, optional PostMessageOptions options = {}); attribute EventHandler onmessage; attribute EventHandler onmessageerror; }; @@ -2243,7 +2321,7 @@ enum WorkerType { "classic", "module" }; Worker includes AbstractWorker; -[Exposed=(Window,Worker)] +[Exposed=Window] interface SharedWorker : EventTarget { constructor(USVString scriptURL, optional (DOMString or WorkerOptions) options = {}); @@ -2280,9 +2358,9 @@ interface Storage { readonly attribute unsigned long length; DOMString? key(unsigned long index); getter DOMString? getItem(DOMString key); - setter void setItem(DOMString key, DOMString value); - deleter void removeItem(DOMString key); - void clear(); + setter undefined setItem(DOMString key, DOMString value); + deleter undefined removeItem(DOMString key); + undefined clear(); }; interface mixin WindowSessionStorage { @@ -2295,16 +2373,17 @@ interface mixin WindowLocalStorage { }; Window includes WindowLocalStorage; -[Exposed=Window, - Constructor(DOMString type, optional StorageEventInit eventInitDict = {})] +[Exposed=Window] interface StorageEvent : Event { + constructor(DOMString type, optional StorageEventInit eventInitDict = {}); + readonly attribute DOMString? key; readonly attribute DOMString? oldValue; readonly attribute DOMString? newValue; readonly attribute USVString url; readonly attribute Storage? storageArea; - void initStorageEvent(DOMString type, optional boolean bubbles = false, optional boolean cancelable = false, optional DOMString? key = null, optional DOMString? oldValue = null, optional DOMString? newValue = null, optional USVString url = "", optional Storage? storageArea = null); + undefined initStorageEvent(DOMString type, optional boolean bubbles = false, optional boolean cancelable = false, optional DOMString? key = null, optional DOMString? oldValue = null, optional DOMString? newValue = null, optional USVString url = "", optional Storage? storageArea = null); }; dictionary StorageEventInit : EventInit { @@ -2335,8 +2414,8 @@ interface HTMLMarqueeElement : HTMLElement { attribute EventHandler onfinish; attribute EventHandler onstart; - void start(); - void stop(); + undefined start(); + undefined stop(); }; [Exposed=Window] @@ -2594,22 +2673,22 @@ partial interface Document { [SameObject] readonly attribute HTMLCollection anchors; [SameObject] readonly attribute HTMLCollection applets; - void clear(); - void captureEvents(); - void releaseEvents(); + undefined clear(); + undefined captureEvents(); + undefined releaseEvents(); [SameObject] readonly attribute HTMLAllCollection all; }; partial interface Window { - void captureEvents(); - void releaseEvents(); + undefined captureEvents(); + undefined releaseEvents(); [Replaceable, SameObject] readonly attribute External external; }; [Exposed=Window] interface External { - void AddSearchProvider(); - void IsSearchProviderInstalled(); + undefined AddSearchProvider(); + undefined IsSearchProviderInstalled(); }; diff --git a/test/fixtures/wpt/interfaces/url.idl b/test/fixtures/wpt/interfaces/url.idl index b0b237e8524a86..b00ab4b83bc60b 100644 --- a/test/fixtures/wpt/interfaces/url.idl +++ b/test/fixtures/wpt/interfaces/url.idl @@ -1,6 +1,6 @@ // GENERATED CONTENT - DO NOT EDIT -// Content was automatically extracted by Reffy into reffy-reports -// (https://github.com/tidoust/reffy-reports) +// Content was automatically extracted by Reffy into webref +// (https://github.com/w3c/webref) // Source: URL Standard (https://url.spec.whatwg.org/) [Exposed=(Window,Worker), @@ -28,14 +28,14 @@ interface URL { interface URLSearchParams { constructor(optional (sequence> or record or USVString) init = ""); - void append(USVString name, USVString value); - void delete(USVString name); + undefined append(USVString name, USVString value); + undefined delete(USVString name); USVString? get(USVString name); sequence getAll(USVString name); boolean has(USVString name); - void set(USVString name, USVString value); + undefined set(USVString name, USVString value); - void sort(); + undefined sort(); iterable; stringifier; diff --git a/test/fixtures/wpt/resources/META.yml b/test/fixtures/wpt/resources/META.yml index 8f988f99a82e09..64a240ccbe8116 100644 --- a/test/fixtures/wpt/resources/META.yml +++ b/test/fixtures/wpt/resources/META.yml @@ -1,3 +1,2 @@ suggested_reviewers: - jgraham - - gsnedders diff --git a/test/fixtures/wpt/resources/check-layout-th.js b/test/fixtures/wpt/resources/check-layout-th.js index 928b0cf2a1041e..5d4236d1cedb0e 100644 --- a/test/fixtures/wpt/resources/check-layout-th.js +++ b/test/fixtures/wpt/resources/check-layout-th.js @@ -25,8 +25,41 @@ function assert_tolerance(actual, expected, message) } } +function checkDataKeys(node) { + var validData = new Set([ + "data-expected-width", + "data-expected-height", + "data-offset-x", + "data-offset-y", + "data-expected-client-width", + "data-expected-client-height", + "data-expected-scroll-width", + "data-expected-scroll-height", + "data-expected-bounding-client-rect-width", + "data-total-x", + "data-total-y", + "data-expected-display", + "data-expected-padding-top", + "data-expected-padding-bottom", + "data-expected-padding-left", + "data-expected-padding-right", + "data-expected-margin-top", + "data-expected-margin-bottom", + "data-expected-margin-left", + "data-expected-margin-right" + ]); + if (!node || !node.getAttributeNames) + return; + // Use "data-test" prefix if you need custom-named data elements. + for (let name of node.getAttributeNames()) { + if (name.startsWith("data-") && !name.startsWith("data-test")) + assert_true(validData.has(name), name + " is a valid data attribute"); + } +} + function checkExpectedValues(t, node, prefix) { + checkDataKeys(node); var output = { checked: false }; var expectedWidth = checkAttribute(output, node, "data-expected-width"); @@ -162,6 +195,8 @@ function checkExpectedValues(t, node, prefix) } var testNumber = 0; +var highlightError = false; // displays outline around failed test element. +var printDomOnError = true; // prints dom when test fails. window.checkLayout = function(selectorList, callDone = true) { @@ -175,13 +210,24 @@ window.checkLayout = function(selectorList, callDone = true) Array.prototype.forEach.call(nodes, function(node) { test(function(t) { var container = node.parentNode.className == 'container' ? node.parentNode : node; - var prefix = "\n" + container.outerHTML + "\n"; + var prefix = + printDomOnError ? '\n' + container.outerHTML + '\n' : ''; var passed = false; try { checkedLayout |= checkExpectedValues(t, node.parentNode, prefix); checkedLayout |= checkSubtreeExpectedValues(t, node, prefix); passed = true; } finally { + if (!passed && highlightError) { + if (!document.getElementById('testharness_error_css')) { + var style = document.createElement('style'); + style.id = 'testharness_error_css'; + style.textContent = '.testharness_error { outline: red dotted 2px !important; }'; + document.body.appendChild(style); + } + if (node) + node.classList.add('testharness_error'); + } checkedLayout |= !passed; } }, selectorList + ' ' + String(++testNumber)); diff --git a/test/fixtures/wpt/resources/idlharness.js b/test/fixtures/wpt/resources/idlharness.js index c7a040996b9994..994a0d82ef444b 100644 --- a/test/fixtures/wpt/resources/idlharness.js +++ b/test/fixtures/wpt/resources/idlharness.js @@ -84,16 +84,33 @@ function minOverloadLength(overloads) .reduce(function(m, n) { return Math.min(m, n); }); } +// A helper to get the global of a Function object. This is needed to determine +// which global exceptions the function throws will come from. +function globalOf(func) +{ + try { + // Use the fact that .constructor for a Function object is normally the + // Function constructor, which can be used to mint a new function in the + // right global. + return func.constructor("return this;")(); + } catch (e) { + } + // If the above fails, because someone gave us a non-function, or a function + // with a weird proto chain or weird .constructor property, just fall back + // to 'self'. + return self; +} + function throwOrReject(a_test, operation, fn, obj, args, message, cb) { if (operation.idlType.generic !== "Promise") { - assert_throws(new TypeError(), function() { + assert_throws_js(globalOf(fn).TypeError, function() { fn.apply(obj, args); }, message); cb(); } else { try { - promise_rejects(a_test, new TypeError(), fn.apply(obj, args), message).then(cb, cb); + promise_rejects_js(a_test, TypeError, fn.apply(obj, args), message).then(cb, cb); } catch (e){ a_test.step(function() { assert_unreached("Throws \"" + e + "\" instead of rejecting promise"); @@ -193,6 +210,12 @@ self.IdlArray = function() this["implements"] = {}; this["includes"] = {}; this["inheritance"] = {}; + + /** + * Record of skipped IDL items, in case we later realize that they are a + * dependency (to retroactively process them). + */ + this.skipped = new Map(); }; IdlArray.prototype.add_idls = function(raw_idls, options) @@ -230,7 +253,11 @@ IdlArray.prototype.is_excluded_by_options = function (name, options) IdlArray.prototype.add_dependency_idls = function(raw_idls, options) { - const parsed_idls = WebIDL2.parse(raw_idls); + return this.internal_add_dependency_idls(WebIDL2.parse(raw_idls), options); +}; + +IdlArray.prototype.internal_add_dependency_idls = function(parsed_idls, options) +{ const new_options = { only: [] } const all_deps = new Set(); @@ -244,22 +271,50 @@ IdlArray.prototype.add_dependency_idls = function(raw_idls, options) all_deps.add(k); this.includes[k].forEach(v => all_deps.add(v)); }); - this.partials.map(p => p.name).forEach(v => all_deps.add(v)); - // Add the attribute idlTypes of all the nested members of all tested idls. - for (const obj of [this.members, this.partials]) { - const tested = Object.values(obj).filter(m => !m.untested && m.members); - for (const parsed of tested) { - for (const attr of Object.values(parsed.members).filter(m => !m.untested && m.type === 'attribute')) { - all_deps.add(attr.idlType.idlType); - } + this.partials.forEach(p => all_deps.add(p.name)); + // Add 'TypeOfType' for each "typedef TypeOfType MyType;" entry. + Object.entries(this.members).forEach(([k, v]) => { + if (v instanceof IdlTypedef) { + let defs = v.idlType.union + ? v.idlType.idlType.map(t => t.idlType) + : [v.idlType.idlType]; + defs.forEach(d => all_deps.add(d)); } - } + }); + + // Add the attribute idlTypes of all the nested members of idls. + const attrDeps = parsedIdls => { + return parsedIdls.reduce((deps, parsed) => { + if (parsed.members) { + for (const attr of Object.values(parsed.members).filter(m => m.type === 'attribute')) { + let attrType = attr.idlType; + // Check for generic members (e.g. FrozenArray) + if (attrType.generic) { + deps.add(attrType.generic); + attrType = attrType.idlType; + } + deps.add(attrType.idlType); + } + } + if (parsed.base in this.members) { + attrDeps([this.members[parsed.base]]).forEach(dep => deps.add(dep)); + } + return deps; + }, new Set()); + }; + + const testedMembers = Object.values(this.members).filter(m => !m.untested && m.members); + attrDeps(testedMembers).forEach(dep => all_deps.add(dep)); + + const testedPartials = this.partials.filter(m => !m.untested && m.members); + attrDeps(testedPartials).forEach(dep => all_deps.add(dep)); + if (options && options.except && options.only) { throw new IdlHarnessError("The only and except options can't be used together."); } - const should_skip = name => { + const defined_or_untested = name => { // NOTE: Deps are untested, so we're lenient, and skip re-encountered definitions. // e.g. for 'idl' containing A:B, B:C, C:D // array.add_idls(idl, {only: ['A','B']}). @@ -268,9 +323,7 @@ IdlArray.prototype.add_dependency_idls = function(raw_idls, options) return name in this.members || this.is_excluded_by_options(name, options); } - // Record of skipped items, in case we later determine they are a dependency. // Maps name -> [parsed_idl, ...] - const skipped = new Map(); const process = function(parsed) { var deps = []; if (parsed.name) { @@ -284,13 +337,15 @@ IdlArray.prototype.add_dependency_idls = function(raw_idls, options) } deps = deps.filter(function(name) { - if (!name || should_skip(name) || !all_deps.has(name)) { + if (!name + || name === parsed.name && defined_or_untested(name) + || !all_deps.has(name)) { // Flag as skipped, if it's not already processed, so we can // come back to it later if we retrospectively call it a dep. if (name && !(name in this.members)) { - skipped.has(name) - ? skipped.get(name).push(parsed) - : skipped.set(name, [parsed]); + this.skipped.has(name) + ? this.skipped.get(name).push(parsed) + : this.skipped.set(name, [parsed]); } return false; } @@ -328,9 +383,9 @@ IdlArray.prototype.add_dependency_idls = function(raw_idls, options) } for (const deferred of follow_up) { - if (skipped.has(deferred)) { - const next = skipped.get(deferred); - skipped.delete(deferred); + if (this.skipped.has(deferred)) { + const next = this.skipped.get(deferred); + this.skipped.delete(deferred); next.forEach(process); } } @@ -463,8 +518,7 @@ IdlArray.prototype.internal_add_idls = function(parsed_idls, options) break; case "callback": - // TODO - console.log("callback not yet supported"); + this.members[parsed_idl.name] = new IdlCallback(parsed_idl); break; case "enum": @@ -586,7 +640,7 @@ IdlArray.prototype.is_json_type = function(type) // sequence types if (type.generic == "sequence" || type.generic == "FrozenArray") { - return this.is_json_type(idlType); + return this.is_json_type(idlType[0]); } if (typeof idlType != "string") { throw new Error("Unexpected type " + JSON.stringify(idlType)); } @@ -625,6 +679,7 @@ IdlArray.prototype.is_json_type = function(type) case "Uint32Array": case "Uint8ClampedArray": case "Float32Array": + case "Float64Array": case "ArrayBuffer": case "DataView": case "any": @@ -694,11 +749,11 @@ function exposure_set(object, default_set) { result = new Set(result); } if (exposed && exposed.length) { - var set = exposed[0].rhs.value; + const { rhs } = exposed[0]; // Could be a list or a string. - if (typeof set == "string") { - set = [ set ]; - } + const set = rhs.type === "identifier-list" ? + rhs.value.map(id => id.value) : + [ rhs.value ]; result = new Set(set); } if (result && result.has("Worker")) { @@ -771,10 +826,20 @@ IdlArray.prototype.test = function() if (!(this.members[lhs] instanceof IdlInterface)) throw errStr + lhs + " is not an interface."; if (!(rhs in this.members)) throw errStr + rhs + " is undefined."; if (!(this.members[rhs] instanceof IdlInterface)) throw errStr + rhs + " is not an interface."; - this.members[rhs].members.forEach(function(member) - { - this.members[lhs].members.push(new IdlInterfaceMember(member)); - }.bind(this)); + + if (this.members[rhs].members.length) { + test(function () { + var clash = this.members[rhs].members.find(function(member) { + return this.members[lhs].members.find(function(m) { + return this.are_duplicate_members(m, member); + }.bind(this)); + }.bind(this)); + this.members[rhs].members.forEach(function(member) { + this.members[lhs].members.push(new IdlInterfaceMember(member)); + }.bind(this)); + assert_true(!clash, "member " + (clash && clash.name) + " is unique"); + }.bind(this), lhs + " implements " + rhs + ": member names are unique"); + } }.bind(this)); } this["implements"] = {}; @@ -788,10 +853,23 @@ IdlArray.prototype.test = function() if (!(this.members[lhs] instanceof IdlInterface)) throw errStr + lhs + " is not an interface."; if (!(rhs in this.members)) throw errStr + rhs + " is undefined."; if (!(this.members[rhs] instanceof IdlInterface)) throw errStr + rhs + " is not an interface."; - this.members[rhs].members.forEach(function(member) - { - this.members[lhs].members.push(new IdlInterfaceMember(member)); - }.bind(this)); + + if (this.members[rhs].members.length) { + test(function () { + var clash = this.members[rhs].members.find(function(member) { + return this.members[lhs].members.find(function(m) { + return this.are_duplicate_members(m, member); + }.bind(this)); + }.bind(this)); + this.members[rhs].members.forEach(function(member) { + assert_true( + this.members[lhs].members.every(m => !this.are_duplicate_members(m, member)), + "member " + member.name + " is unique"); + this.members[lhs].members.push(new IdlInterfaceMember(member)); + }.bind(this)); + assert_true(!clash, "member " + (clash && clash.name) + " is unique"); + }.bind(this), lhs + " includes " + rhs + ": member names are unique"); + } }.bind(this)); } this["includes"] = {}; @@ -853,24 +931,27 @@ IdlArray.prototype.collapse_partials = function() || this.members[parsed_idl.name] instanceof IdlDictionary || this.members[parsed_idl.name] instanceof IdlNamespace); + // Ensure unique test name in case of multiple partials. let partialTestName = parsed_idl.name; - if (!parsed_idl.untested) { - // Ensure unique test name in case of multiple partials. - let partialTestCount = 1; - if (testedPartials.has(parsed_idl.name)) { - partialTestCount += testedPartials.get(parsed_idl.name); - partialTestName = `${partialTestName}[${partialTestCount}]`; - } - testedPartials.set(parsed_idl.name, partialTestCount); + let partialTestCount = 1; + if (testedPartials.has(parsed_idl.name)) { + partialTestCount += testedPartials.get(parsed_idl.name); + partialTestName = `${partialTestName}[${partialTestCount}]`; + } + testedPartials.set(parsed_idl.name, partialTestCount); + if (!parsed_idl.untested) { test(function () { assert_true(originalExists, `Original ${parsed_idl.type} should be defined`); - var expected = IdlInterface; + var expected; switch (parsed_idl.type) { - case 'interface': expected = IdlInterface; break; case 'dictionary': expected = IdlDictionary; break; case 'namespace': expected = IdlNamespace; break; + case 'interface': + case 'interface mixin': + default: + expected = IdlInterface; break; } assert_true( expected.prototype.isPrototypeOf(this.members[parsed_idl.name]), @@ -916,14 +997,36 @@ IdlArray.prototype.collapse_partials = function() this.members[parsed_idl.name].extAttrs.push(extAttr); }.bind(this)); } - parsed_idl.members.forEach(function(member) - { - this.members[parsed_idl.name].members.push(new IdlInterfaceMember(member)); - }.bind(this)); + if (parsed_idl.members.length) { + test(function () { + var clash = parsed_idl.members.find(function(member) { + return this.members[parsed_idl.name].members.find(function(m) { + return this.are_duplicate_members(m, member); + }.bind(this)); + }.bind(this)); + parsed_idl.members.forEach(function(member) + { + this.members[parsed_idl.name].members.push(new IdlInterfaceMember(member)); + }.bind(this)); + assert_true(!clash, "member " + (clash && clash.name) + " is unique"); + }.bind(this), `Partial ${parsed_idl.type} ${partialTestName}: member names are unique`); + } }.bind(this)); this.partials = []; } +IdlArray.prototype.are_duplicate_members = function(m1, m2) { + if (m1.name !== m2.name) { + return false; + } + if (m1.type === 'operation' && m2.type === 'operation' + && m1.arguments.length !== m2.arguments.length) { + // Method overload. TODO: Deep comparison of arguments. + return false; + } + return true; +} + IdlArray.prototype.assert_type_is = function(value, type) { if (type.idlType in this.members @@ -931,6 +1034,13 @@ IdlArray.prototype.assert_type_is = function(value, type) this.assert_type_is(value, this.members[type.idlType].idlType); return; } + + if (type.nullable && value === null) + { + // This is fine + return; + } + if (type.union) { for (var i = 0; i < type.idlType.length; i++) { try { @@ -964,12 +1074,6 @@ IdlArray.prototype.assert_type_is = function(value, type) return; } - if (type.nullable && value === null) - { - // This is fine - return; - } - if (type.array) { // TODO: not supported yet @@ -984,7 +1088,7 @@ IdlArray.prototype.assert_type_is = function(value, type) // Nothing we can do. return; } - this.assert_type_is(value[0], type.idlType); + this.assert_type_is(value[0], type.idlType[0]); return; } @@ -1004,11 +1108,11 @@ IdlArray.prototype.assert_type_is = function(value, type) // Nothing we can do. return; } - this.assert_type_is(value[0], type.idlType); + this.assert_type_is(value[0], type.idlType[0]); return; } - type = type.idlType; + type = Array.isArray(type.idlType) ? type.idlType[0] : type.idlType; switch(type) { @@ -1105,14 +1209,26 @@ IdlArray.prototype.assert_type_is = function(value, type) assert_regexp_match(value, /^([\x00-\ud7ff\ue000-\uffff]|[\ud800-\udbff][\udc00-\udfff])*$/); return; + case "ArrayBufferView": + assert_true(ArrayBuffer.isView(value)); + return; + case "object": assert_in_array(typeof value, ["object", "function"], "wrong type: not object or function"); return; } + // This is a catch-all for any IDL type name which follows JS class + // semantics. This includes some non-interface IDL types (e.g. Int8Array, + // Function, ...), as well as any interface types that are not in the IDL + // that is fed to the harness. If an IDL type does not follow JS class + // semantics then it should go in the switch statement above. If an IDL + // type needs full checking, then the test should include it in the IDL it + // feeds to the harness. if (!(type in this.members)) { - throw new IdlHarnessError("Unrecognized type " + type); + assert_true(value instanceof self[type], "wrong type: not a " + type); + return; } if (this.members[type] instanceof IdlInterface) @@ -1138,9 +1254,13 @@ IdlArray.prototype.assert_type_is = function(value, type) { // TODO: Test when we actually have something to test this on } + else if (this.members[type] instanceof IdlCallback) + { + assert_equals(typeof value, "function"); + } else { - throw new IdlHarnessError("Type " + type + " isn't an interface or dictionary"); + throw new IdlHarnessError("Type " + type + " isn't an interface, callback or dictionary"); } }; @@ -1223,9 +1343,9 @@ function IdlInterface(obj, is_callback, is_mixin) /** An array of IdlInterfaceMembers. */ this.members = obj.members.map(function(m){return new IdlInterfaceMember(m); }); - if (this.has_extended_attribute("Unforgeable")) { + if (this.has_extended_attribute("LegacyUnforgeable")) { this.members - .filter(function(m) { return !m["static"] && (m.type == "attribute" || m.type == "operation"); }) + .filter(function(m) { return m.special !== "static" && (m.type == "attribute" || m.type == "operation"); }) .forEach(function(m) { return m.isUnforgeable = true; }); } @@ -1289,6 +1409,18 @@ IdlInterface.prototype.get_interface_object_owner = function() return legacyNamespace ? self[legacyNamespace] : self; }; +IdlInterface.prototype.should_have_interface_object = function() +{ + // "For every interface that is exposed in a given ECMAScript global + // environment and: + // * is a callback interface that has constants declared on it, or + // * is a non-callback interface that is not declared with the + // [NoInterfaceObject] extended attribute, + // a corresponding property MUST exist on the ECMAScript global object. + + return this.is_callback() ? this.has_constants() : !this.has_extended_attribute("NoInterfaceObject"); +}; + IdlInterface.prototype.assert_interface_object_exists = function() { var owner = this.get_legacy_namespace() || "self"; @@ -1296,8 +1428,9 @@ IdlInterface.prototype.assert_interface_object_exists = function() }; IdlInterface.prototype.get_interface_object = function() { - if (this.has_extended_attribute("NoInterfaceObject")) { - throw new IdlHarnessError(this.name + " has no interface object due to NoInterfaceObject"); + if (!this.should_have_interface_object()) { + var reason = this.is_callback() ? "lack of declared constants" : "declared [NoInterfaceObject] attribute"; + throw new IdlHarnessError(this.name + " has no interface object due to " + reason); } return this.get_interface_object_owner()[this.name]; @@ -1376,7 +1509,7 @@ IdlInterface.prototype.default_to_json_operation = function(callback) { if (I.has_default_to_json_regular_operation()) { isDefault = true; I.members.forEach(function(m) { - if (!m.static && m.type == "attribute" && I.array.is_json_type(m.idlType)) { + if (m.special !== "static" && m.type == "attribute" && I.array.is_json_type(m.idlType)) { map.set(m.name, m.idlType); } }); @@ -1470,26 +1603,31 @@ IdlInterface.prototype.test = function() this.test_members(); }; +// This supports both Constructor extended attributes and constructor +// operations until all idl fragments have been updated. +IdlInterface.prototype.constructors = function() +{ + var extendedAttributes = this.extAttrs + .filter(function(attr) { return attr.name == "Constructor"; }); + var operations = this.members + .filter(function(m) { return m.type == "constructor"; }); + return extendedAttributes.concat(operations); +} + IdlInterface.prototype.test_self = function() { subsetTestByKey(this.name, test, function() { // This function tests WebIDL as of 2015-01-13. - // "For every interface that is exposed in a given ECMAScript global - // environment and: - // * is a callback interface that has constants declared on it, or - // * is a non-callback interface that is not declared with the - // [NoInterfaceObject] extended attribute, - // a corresponding property MUST exist on the ECMAScript global object. + if (!this.should_have_interface_object()) { + return; + } + // The name of the property is the identifier of the interface, and its // value is an object called the interface object. // The property has the attributes { [[Writable]]: true, // [[Enumerable]]: false, [[Configurable]]: true }." - if (this.is_callback() && !this.has_constants()) { - return; - } - // TODO: Should we test here that the property is actually writable // etc., or trust getOwnPropertyDescriptor? this.assert_interface_object_exists(); @@ -1557,23 +1695,23 @@ IdlInterface.prototype.test_self = function() "prototype of self's property " + format_value(this.name) + " is not Function.prototype"); } - if (!this.has_extended_attribute("Constructor")) { + if (!this.constructors().length) { // "The internal [[Call]] method of the interface object behaves as // follows . . . // // "If I was not declared with a [Constructor] extended attribute, // then throw a TypeError." var interface_object = this.get_interface_object(); - assert_throws(new TypeError(), function() { + assert_throws_js(globalOf(interface_object).TypeError, function() { interface_object(); }, "interface object didn't throw TypeError when called as a function"); - assert_throws(new TypeError(), function() { + assert_throws_js(globalOf(interface_object).TypeError, function() { new interface_object(); }, "interface object didn't throw TypeError when called as a constructor"); } }.bind(this), this.name + " interface: existence and properties of interface object"); - if (!this.is_callback()) { + if (this.should_have_interface_object() && !this.is_callback()) { subsetTestByKey(this.name, test, function() { // This function tests WebIDL as of 2014-10-25. // https://heycam.github.io/webidl/#es-interface-call @@ -1592,14 +1730,13 @@ IdlInterface.prototype.test_self = function() assert_false(desc.enumerable, this.name + ".length should not be enumerable"); assert_true(desc.configurable, this.name + ".length should be configurable"); - var constructors = this.extAttrs - .filter(function(attr) { return attr.name == "Constructor"; }); + var constructors = this.constructors(); var expected_length = minOverloadLength(constructors); assert_equals(this.get_interface_object().length, expected_length, "wrong value for " + this.name + ".length"); }.bind(this), this.name + " interface object length"); } - if (!this.is_callback() || this.has_constants()) { + if (this.should_have_interface_object()) { subsetTestByKey(this.name, test, function() { // This function tests WebIDL as of 2015-11-17. // https://heycam.github.io/webidl/#interface-object @@ -1647,7 +1784,7 @@ IdlInterface.prototype.test_self = function() } var aliases; if (rhs.type === "identifier-list") { - aliases = rhs.value; + aliases = rhs.value.map(id => id.value); } else { // rhs.type === identifier aliases = [ rhs.value ]; } @@ -1675,11 +1812,11 @@ IdlInterface.prototype.test_self = function() }.bind(this), this.name + " interface: legacy window alias"); } - if (this.has_extended_attribute("NamedConstructor")) { + if (this.has_extended_attribute("LegacyFactoryFunction")) { var constructors = this.extAttrs - .filter(function(attr) { return attr.name == "NamedConstructor"; }); + .filter(function(attr) { return attr.name == "LegacyFactoryFunction"; }); if (constructors.length !== 1) { - throw new IdlHarnessError("Internal error: missing support for multiple NamedConstructor extended attributes"); + throw new IdlHarnessError("Internal error: missing support for multiple LegacyFactoryFunction extended attributes"); } var constructor = constructors[0]; var min_length = minOverloadLength([constructor]); @@ -1688,10 +1825,10 @@ IdlInterface.prototype.test_self = function() { // This function tests WebIDL as of 2019-01-14. - // "for every [NamedConstructor] extended attribute on an exposed + // "for every [LegacyFactoryFunction] extended attribute on an exposed // interface, a corresponding property must exist on the ECMAScript // global object. The name of the property is the - // [NamedConstructor]'s identifier, and its value is an object + // [LegacyFactoryFunction]'s identifier, and its value is an object // called a named constructor, ... . The property has the attributes // { [[Writable]]: true, [[Enumerable]]: false, // [[Configurable]]: true }." @@ -1785,7 +1922,7 @@ IdlInterface.prototype.test_self = function() var args = constructor.arguments.map(function(arg) { return create_suitable_object(arg.idlType); }); - assert_throws(new TypeError(), function() { + assert_throws_js(globalOf(self[name]).TypeError, function() { self[name](...args); }.bind(this)); }.bind(this), this.name + " interface: named constructor without 'new'"); @@ -1796,7 +1933,7 @@ IdlInterface.prototype.test_self = function() // This function tests WebIDL as of 2015-01-21. // https://heycam.github.io/webidl/#interface-object - if (this.is_callback() && !this.has_constants()) { + if (!this.should_have_interface_object()) { return; } @@ -1920,7 +2057,7 @@ IdlInterface.prototype.test_self = function() subsetTestByKey(this.name, test, function() { - if (this.is_callback() && !this.has_constants()) { + if (!this.should_have_interface_object()) { return; } @@ -1955,7 +2092,7 @@ IdlInterface.prototype.test_self = function() subsetTestByKey(this.name, test, function() { - if (this.is_callback() && !this.has_constants()) { + if (!this.should_have_interface_object()) { return; } @@ -2030,7 +2167,7 @@ IdlInterface.prototype.test_immutable_prototype = function(type, obj) } catch (err) {} }); - assert_throws(new TypeError(), function() { + assert_throws_js(TypeError, function() { Object.setPrototypeOf(obj, newValue); }); @@ -2048,7 +2185,7 @@ IdlInterface.prototype.test_immutable_prototype = function(type, obj) var newValue = Object.create(null); t.add_cleanup(function() { - var setter = Object.getOwnPropertyDescriptor( + let setter = Object.getOwnPropertyDescriptor( Object.prototype, '__proto__' ).set; @@ -2057,7 +2194,22 @@ IdlInterface.prototype.test_immutable_prototype = function(type, obj) } catch (err) {} }); - assert_throws(new TypeError(), function() { + // We need to find the actual setter for the '__proto__' property, so we + // can determine the right global for it. Walk up the prototype chain + // looking for that property until we find it. + let setter; + { + let cur = obj; + while (cur) { + const desc = Object.getOwnPropertyDescriptor(cur, "__proto__"); + if (desc) { + setter = desc.set; + break; + } + cur = Object.getPrototypeOf(cur); + } + } + assert_throws_js(globalOf(setter).TypeError, function() { obj.__proto__ = newValue; }); @@ -2181,8 +2333,8 @@ IdlInterface.prototype.test_member_attribute = function(member) var a_test = subsetTestByKey(this.name, async_test, this.name + " interface: attribute " + member.name); a_test.step(function() { - if (this.is_callback() && !this.has_constants()) { - a_test.done() + if (!this.should_have_interface_object()) { + a_test.done(); return; } @@ -2190,7 +2342,7 @@ IdlInterface.prototype.test_member_attribute = function(member) assert_own_property(this.get_interface_object(), "prototype", 'interface "' + this.name + '" does not have own property "prototype"'); - if (member["static"]) { + if (member.special === "static") { assert_own_property(this.get_interface_object(), member.name, "The interface object must have a property " + format_value(member.name)); @@ -2236,23 +2388,24 @@ IdlInterface.prototype.test_member_attribute = function(member) "The prototype object must have a property " + format_value(member.name)); - if (!member.has_extended_attribute("LenientThis")) { + if (!member.has_extended_attribute("LegacyLenientThis")) { if (member.idlType.generic !== "Promise") { - assert_throws(new TypeError(), function() { + // this.get_interface_object() returns a thing in our global + assert_throws_js(TypeError, function() { this.get_interface_object().prototype[member.name]; }.bind(this), "getting property on prototype object must throw TypeError"); // do_interface_attribute_asserts must be the last thing we // do, since it will call done() on a_test. this.do_interface_attribute_asserts(this.get_interface_object().prototype, member, a_test); } else { - promise_rejects(a_test, new TypeError(), + promise_rejects_js(a_test, TypeError, this.get_interface_object().prototype[member.name]) - .then(function() { + .then(a_test.step_func(function() { // do_interface_attribute_asserts must be the last // thing we do, since it will call done() on a_test. this.do_interface_attribute_asserts(this.get_interface_object().prototype, member, a_test); - }.bind(this)); + }.bind(this))); } } else { assert_equals(this.get_interface_object().prototype[member.name], undefined, @@ -2270,16 +2423,13 @@ IdlInterface.prototype.test_member_operation = function(member) if (!shouldRunSubTest(this.name)) { return; } - var a_test = subsetTestByKey(this.name, async_test, this.name + " interface: operation " + member.name + - "(" + member.arguments.map( - function(m) {return m.idlType.idlType; } ).join(", ") - +")"); + var a_test = subsetTestByKey(this.name, async_test, this.name + " interface: operation " + member); a_test.step(function() { // This function tests WebIDL as of 2015-12-29. // https://heycam.github.io/webidl/#es-operations - if (this.is_callback() && !this.has_constants()) { + if (!this.should_have_interface_object()) { a_test.done(); return; } @@ -2307,7 +2457,7 @@ IdlInterface.prototype.test_member_operation = function(member) var memberHolderObject; // "* If the operation is static, then the property exists on the // interface object." - if (member["static"]) { + if (member.special === "static") { assert_own_property(this.get_interface_object(), member.name, "interface object missing static operation"); memberHolderObject = this.get_interface_object(); @@ -2384,6 +2534,10 @@ IdlInterface.prototype.do_member_operation_asserts = function(memberHolderObject memberHolderObject[member.name].length, minOverloadLength(ctors), "property has wrong .length"); + assert_equals( + memberHolderObject[member.name].name, + member.name, + "property has wrong .name"); // Make some suitable arguments var args = member.arguments.map(function(arg) { @@ -2400,7 +2554,7 @@ IdlInterface.prototype.do_member_operation_asserts = function(memberHolderObject // check for globals, since otherwise we'll invoke window.close(). And we // have to skip this test for anything that on the proto chain of "self", // since that does in fact have implicit-this behavior. - if (!member["static"]) { + if (member.special !== "static") { var cb; if (!this.is_global() && memberHolderObject[member.name] != self[member.name]) @@ -2455,32 +2609,74 @@ IdlInterface.prototype.test_member_iterable = function(member) { var isPairIterator = member.idlType.length === 2; var proto = this.get_interface_object().prototype; - var descriptor = Object.getOwnPropertyDescriptor(proto, Symbol.iterator); + var iteratorDesc = Object.getOwnPropertyDescriptor(proto, Symbol.iterator); - assert_true(descriptor.writable, "@@iterator property should be writable"); - assert_true(descriptor.configurable, "@@iterator property should be configurable"); - assert_false(descriptor.enumerable, "@@iterator property should not be enumerable"); - assert_equals(typeof descriptor.value, "function", "@@iterator property should be a function"); - assert_equals(descriptor.value.length, 0, "@@iterator function object length should be 0"); - assert_equals(descriptor.value.name, isPairIterator ? "entries" : "values", "@@iterator function object should have the right name"); + assert_true(iteratorDesc.writable, "@@iterator property should be writable"); + assert_true(iteratorDesc.configurable, "@@iterator property should be configurable"); + assert_false(iteratorDesc.enumerable, "@@iterator property should not be enumerable"); + assert_equals(typeof iteratorDesc.value, "function", "@@iterator property should be a function"); + assert_equals(iteratorDesc.value.length, 0, "@@iterator function object length should be 0"); + assert_equals(iteratorDesc.value.name, isPairIterator ? "entries" : "values", "@@iterator function object should have the right name"); if (isPairIterator) { assert_equals(proto["entries"], proto[Symbol.iterator], "entries method should be the same as @@iterator method"); + [ + ["entries", 0], + ["keys", 0], + ["values", 0], + ["forEach", 1] + ].forEach(([property, length]) => { + var desc = Object.getOwnPropertyDescriptor(proto, property); + assert_equals(typeof desc.value, "function", property + " property should be a function"); + assert_equals(desc.value.length, length, property + " function object length should be " + length); + assert_equals(desc.value.name, property, property + " function object should have the right name"); + }); } else { assert_equals(proto[Symbol.iterator], Array.prototype[Symbol.iterator], "@@iterator method should be the same as Array prototype's"); - ["entries", "keys", "values", "forEach", Symbol.iterator].forEach(function(property) { + ["entries", "keys", "values", "forEach", Symbol.iterator].forEach(property => { var propertyName = property === Symbol.iterator ? "@@iterator" : property; assert_equals(proto[property], Array.prototype[property], propertyName + " method should be the same as Array prototype's"); - }.bind(this)); + }); } }.bind(this), this.name + " interface: iterable<" + member.idlType.map(function(t) { return t.idlType; }).join(", ") + ">"); }; +IdlInterface.prototype.test_member_async_iterable = function(member) +{ + subsetTestByKey(this.name, test, function() + { + var isPairIterator = member.idlType.length === 2; + var proto = this.get_interface_object().prototype; + var iteratorDesc = Object.getOwnPropertyDescriptor(proto, Symbol.asyncIterator); + + assert_true(iteratorDesc.writable, "@@asyncIterator property should be writable"); + assert_true(iteratorDesc.configurable, "@@asyncIterator property should be configurable"); + assert_false(iteratorDesc.enumerable, "@@asyncIterator property should not be enumerable"); + assert_equals(typeof iteratorDesc.value, "function", "@@asyncIterator property should be a function"); + assert_equals(iteratorDesc.value.length, 0, "@@asyncIterator function object length should be 0"); + assert_equals(iteratorDesc.value.name, isPairIterator ? "entries" : "values", "@@asyncIterator function object should have the right name"); + + if (isPairIterator) { + assert_equals(proto["entries"], proto[Symbol.asyncIterator], "entries method should be the same as @@asyncIterator method"); + ["entries", "keys", "values"].forEach(property => { + var desc = Object.getOwnPropertyDescriptor(proto, property); + assert_equals(typeof desc.value, "function", property + " property should be a function"); + assert_equals(desc.value.length, 0, property + " function object length should be 0"); + assert_equals(desc.value.name, property, property + " function object should have the right name"); + }); + } else { + assert_equals(proto["values"], proto[Symbol.asyncIterator], "values method should be the same as @@asyncIterator method"); + assert_false("entries" in proto, "should not have an entries method"); + assert_false("keys" in proto, "should not have a keys method"); + } + }.bind(this), this.name + " interface: async iterable<" + member.idlType.map(function(t) { return t.idlType; }).join(", ") + ">"); +}; + IdlInterface.prototype.test_member_stringifier = function(member) { subsetTestByKey(this.name, test, function() { - if (this.is_callback() && !this.has_constants()) { + if (!this.should_have_interface_object()) { return; } @@ -2522,7 +2718,7 @@ IdlInterface.prototype.test_member_stringifier = function(member) "property has wrong .length"); // "Let O be the result of calling ToObject on the this value." - assert_throws(new TypeError(), function() { + assert_throws_js(globalOf(interfacePrototypeObject.toString).TypeError, function() { interfacePrototypeObject.toString.apply(null, []); }, "calling stringifier with this = null didn't throw TypeError"); @@ -2531,7 +2727,7 @@ IdlInterface.prototype.test_member_stringifier = function(member) // // TODO: Test a platform object that implements some other // interface. (Have to be sure to get inheritance right.) - assert_throws(new TypeError(), function() { + assert_throws_js(globalOf(interfacePrototypeObject.toString).TypeError, function() { interfacePrototypeObject.toString.apply({}, []); }, "calling stringifier with this = {} didn't throw TypeError"); }.bind(this), this.name + " interface: stringifier"); @@ -2571,7 +2767,7 @@ IdlInterface.prototype.test_members = function() { this.test_member_attribute(member); } - if (member.stringifier) { + if (member.special === "stringifier") { this.test_member_stringifier(member); } break; @@ -2586,13 +2782,17 @@ IdlInterface.prototype.test_members = function() { this.test_member_operation(member); } - } else if (member.stringifier) { + } else if (member.special === "stringifier") { this.test_member_stringifier(member); } break; case "iterable": - this.test_member_iterable(member); + if (member.async) { + this.test_member_async_iterable(member); + } else { + this.test_member_iterable(member); + } break; default: // TODO: check more member types. @@ -2613,10 +2813,19 @@ IdlInterface.prototype.test_object = function(desc) exception = e; } - var expected_typeof = - this.members.some(function(member) { return member.legacycaller; }) - ? "function" - : "object"; + var expected_typeof; + if (this.name == "HTMLAllCollection") + { + // Result of [[IsHTMLDDA]] slot + expected_typeof = "undefined"; + } else if (this.members.some(function(member) { return member.legacycaller; })) + { + expected_typeof = "function"; + } + else + { + expected_typeof = "object"; + } this.test_primary_interface_of(desc, obj, exception, expected_typeof); @@ -2661,7 +2870,7 @@ IdlInterface.prototype.test_primary_interface_of = function(desc, obj, exception // interface object, or the object is from a different global environment // (not instanceof Object). TODO: test in this case that its prototype at // least looks correct, even if we can't test that it's actually correct. - if (!this.has_extended_attribute("NoInterfaceObject") + if (this.should_have_interface_object() && (typeof obj != expected_typeof || obj instanceof Object)) { subsetTestByKey(this.name, test, function() @@ -2747,16 +2956,11 @@ IdlInterface.prototype.test_interface_of = function(desc, obj, exception, expect || member.type == "operation") && member.name) { - var described_name = member.name; - if (member.type == "operation") - { - described_name += "(" + member.arguments.map(arg => arg.idlType.idlType).join(", ") + ")"; - } subsetTestByKey(this.name, test, function() { assert_equals(exception, null, "Unexpected exception when evaluating object"); assert_equals(typeof obj, expected_typeof, "wrong typeof object"); - if (!member["static"]) { + if (member.special !== "static") { if (!this.is_global()) { assert_inherits(obj, member.name); } else { @@ -2783,7 +2987,15 @@ IdlInterface.prototype.test_interface_of = function(desc, obj, exception, expect } if (!thrown) { - this.array.assert_type_is(property, member.idlType); + if (this.name == "Document" && member.name == "all") + { + // Result of [[IsHTMLDDA]] slot + assert_equals(typeof property, "undefined"); + } + else + { + this.array.assert_type_is(property, member.idlType); + } } } if (member.type == "operation") @@ -2791,22 +3003,23 @@ IdlInterface.prototype.test_interface_of = function(desc, obj, exception, expect assert_equals(typeof obj[member.name], "function"); } } - }.bind(this), this.name + " interface: " + desc + ' must inherit property "' + described_name + '" with the proper type'); + }.bind(this), this.name + " interface: " + desc + ' must inherit property "' + member + '" with the proper type'); } // TODO: This is wrong if there are multiple operations with the same // identifier. // TODO: Test passing arguments of the wrong type. if (member.type == "operation" && member.name && member.arguments.length) { - var a_test = subsetTestByKey(this.name, async_test, this.name + " interface: calling " + member.name + - "(" + member.arguments.map(function(m) { return m.idlType.idlType; }).join(", ") + - ") on " + desc + " with too few arguments must throw TypeError"); + var description = + this.name + " interface: calling " + member + " on " + desc + + " with too few arguments must throw TypeError"; + var a_test = subsetTestByKey(this.name, async_test, description); a_test.step(function() { assert_equals(exception, null, "Unexpected exception when evaluating object"); assert_equals(typeof obj, expected_typeof, "wrong typeof object"); var fn; - if (!member["static"]) { + if (member.special !== "static") { if (!this.is_global() && !member.isUnforgeable) { assert_inherits(obj, member.name); } else { @@ -2849,7 +3062,7 @@ IdlInterface.prototype.has_stringifier = function() // default stringifer return true; } - if (this.members.some(function(member) { return member.stringifier; })) { + if (this.members.some(function(member) { return member.special === "stringifier"; })) { return true; } if (this.base && @@ -2881,7 +3094,7 @@ IdlInterface.prototype.do_interface_attribute_asserts = function(obj, member, a_ // "The property has attributes { [[Get]]: G, [[Set]]: S, [[Enumerable]]: // true, [[Configurable]]: configurable }, where: // "configurable is false if the attribute was declared with the - // [Unforgeable] extended attribute and true otherwise; + // [LegacyUnforgeable] extended attribute and true otherwise; // "G is the attribute getter, defined below; and // "S is the attribute setter, also defined below." var desc = Object.getOwnPropertyDescriptor(obj, member.name); @@ -2890,7 +3103,7 @@ IdlInterface.prototype.do_interface_attribute_asserts = function(obj, member, a_ assert_true(desc.enumerable, "property should be enumerable"); if (member.isUnforgeable) { - assert_false(desc.configurable, "[Unforgeable] property must not be configurable"); + assert_false(desc.configurable, "[LegacyUnforgeable] property must not be configurable"); } else { @@ -2903,19 +3116,19 @@ IdlInterface.prototype.do_interface_attribute_asserts = function(obj, member, a_ assert_equals(typeof desc.get, "function", "getter must be Function"); // "If the attribute is a regular attribute, then:" - if (!member["static"]) { + if (member.special !== "static") { // "If O is not a platform object that implements I, then: - // "If the attribute was specified with the [LenientThis] extended + // "If the attribute was specified with the [LegacyLenientThis] extended // attribute, then return undefined. // "Otherwise, throw a TypeError." - if (!member.has_extended_attribute("LenientThis")) { + if (!member.has_extended_attribute("LegacyLenientThis")) { if (member.idlType.generic !== "Promise") { - assert_throws(new TypeError(), function() { + assert_throws_js(globalOf(desc.get).TypeError, function() { desc.get.call({}); }.bind(this), "calling getter on wrong object type must throw TypeError"); } else { pendingPromises.push( - promise_rejects(a_test, new TypeError(), desc.get.call({}), + promise_rejects_js(a_test, TypeError, desc.get.call({}), "calling getter on wrong object type must reject the return promise with TypeError")); } } else { @@ -2937,7 +3150,7 @@ IdlInterface.prototype.do_interface_attribute_asserts = function(obj, member, a_ // TODO: Test calling setter on the interface prototype (should throw // TypeError in most cases). if (member.readonly - && !member.has_extended_attribute("LenientSetter") + && !member.has_extended_attribute("LegacyLenientSetter") && !member.has_extended_attribute("PutForwards") && !member.has_extended_attribute("Replaceable")) { @@ -2953,15 +3166,15 @@ IdlInterface.prototype.do_interface_attribute_asserts = function(obj, member, a_ assert_equals(typeof desc.set, "function", "setter must be function for PutForwards, Replaceable, or non-readonly attributes"); // "If the attribute is a regular attribute, then:" - if (!member["static"]) { + if (member.special !== "static") { // "If /validThis/ is false and the attribute was not specified - // with the [LenientThis] extended attribute, then throw a + // with the [LegacyLenientThis] extended attribute, then throw a // TypeError." // "If the attribute is declared with a [Replaceable] extended // attribute, then: ..." // "If validThis is false, then return." - if (!member.has_extended_attribute("LenientThis")) { - assert_throws(new TypeError(), function() { + if (!member.has_extended_attribute("LegacyLenientThis")) { + assert_throws_js(globalOf(desc.set).TypeError, function() { desc.set.call({}); }.bind(this), "calling setter on wrong object type must throw TypeError"); } else { @@ -2991,7 +3204,7 @@ function IdlInterfaceMember(obj) * We just forward all properties to this object without modification, * except for special extAttrs handling. */ - for (var k in obj) + for (var k in obj.toJSON()) { this[k] = obj[k]; } @@ -3000,16 +3213,50 @@ function IdlInterfaceMember(obj) this.extAttrs = []; } - this.isUnforgeable = this.has_extended_attribute("Unforgeable"); + this.isUnforgeable = this.has_extended_attribute("LegacyUnforgeable"); this.isUnscopable = this.has_extended_attribute("Unscopable"); } IdlInterfaceMember.prototype = Object.create(IdlObject.prototype); +IdlInterfaceMember.prototype.toJSON = function() { + return this; +}; + IdlInterfaceMember.prototype.is_to_json_regular_operation = function() { - return this.type == "operation" && !this.static && this.name == "toJSON"; + return this.type == "operation" && this.special !== "static" && this.name == "toJSON"; }; +IdlInterfaceMember.prototype.toString = function() { + function formatType(type) { + var result; + if (type.generic) { + result = type.generic + "<" + type.idlType.map(formatType).join(", ") + ">"; + } else if (type.union) { + result = "(" + type.subtype.map(formatType).join(" or ") + ")"; + } else { + result = type.idlType; + } + if (type.nullable) { + result += "?" + } + return result; + } + + if (this.type === "operation") { + var args = this.arguments.map(function(m) { + return [ + m.optional ? "optional " : "", + formatType(m.idlType), + m.variadic ? "..." : "", + ].join(""); + }).join(", "); + return this.name + "(" + args + ")"; + } + + return this.name; +} + /// Internal helper functions /// function create_suitable_object(type) { @@ -3067,6 +3314,24 @@ function IdlEnum(obj) IdlEnum.prototype = Object.create(IdlObject.prototype); +/// IdlCallback /// +// Used for IdlArray.prototype.assert_type_is +function IdlCallback(obj) +{ + /** + * obj is an object produced by the WebIDLParser.js "callback" + * production. + */ + + /** Self-explanatory. */ + this.name = obj.name; + + /** Arguments for the callback. */ + this.arguments = obj.arguments; +} + +IdlCallback.prototype = Object.create(IdlObject.prototype); + /// IdlTypedef /// // Used for IdlArray.prototype.assert_type_is function IdlTypedef(obj) @@ -3136,17 +3401,10 @@ IdlNamespace.prototype.test_member_operation = function(member) if (!shouldRunSubTest(this.name)) { return; } - var args = member.arguments.map(function(a) { - var s = a.idlType.idlType; - if (a.variadic) { - s += '...'; - } - return s; - }).join(", "); var a_test = subsetTestByKey( this.name, async_test, - this.name + ' namespace: operation ' + member.name + '(' + args + ')'); + this.name + ' namespace: operation ' + member); a_test.step(function() { assert_own_property( self[this.name], @@ -3179,16 +3437,60 @@ IdlNamespace.prototype.test_member_attribute = function (member) }.bind(this)); }; -IdlNamespace.prototype.test = function () +IdlNamespace.prototype.test_self = function () { /** * TODO(lukebjerring): Assert: * - "Note that unlike interfaces or dictionaries, namespaces do not create types." - * - "Of the extended attributes defined in this specification, only the - * [Exposed] and [SecureContext] extended attributes are applicable to namespaces." - * - "Namespaces must be annotated with the [Exposed] extended attribute." */ + subsetTestByKey(this.name, test, () => { + assert_true(this.extAttrs.every(o => o.name === "Exposed" || o.name === "SecureContext"), + "Only the [Exposed] and [SecureContext] extended attributes are applicable to namespaces"); + assert_true(this.has_extended_attribute("Exposed"), + "Namespaces must be annotated with the [Exposed] extended attribute"); + }, `${this.name} namespace: extended attributes`); + + const namespaceObject = self[this.name]; + + subsetTestByKey(this.name, test, () => { + const desc = Object.getOwnPropertyDescriptor(self, this.name); + assert_equals(desc.value, namespaceObject, `wrong value for ${this.name} namespace object`); + assert_true(desc.writable, "namespace object should be writable"); + assert_false(desc.enumerable, "namespace object should not be enumerable"); + assert_true(desc.configurable, "namespace object should be configurable"); + assert_false("get" in desc, "namespace object should not have a getter"); + assert_false("set" in desc, "namespace object should not have a setter"); + }, `${this.name} namespace: property descriptor`); + + subsetTestByKey(this.name, test, () => { + assert_true(Object.isExtensible(namespaceObject)); + }, `${this.name} namespace: [[Extensible]] is true`); + + subsetTestByKey(this.name, test, () => { + assert_true(namespaceObject instanceof Object); + + if (this.name === "console") { + // https://console.spec.whatwg.org/#console-namespace + const namespacePrototype = Object.getPrototypeOf(namespaceObject); + assert_equals(Reflect.ownKeys(namespacePrototype).length, 0); + assert_equals(Object.getPrototypeOf(namespacePrototype), Object.prototype); + } else { + assert_equals(Object.getPrototypeOf(namespaceObject), Object.prototype); + } + }, `${this.name} namespace: [[Prototype]] is Object.prototype`); + + subsetTestByKey(this.name, test, () => { + assert_equals(typeof namespaceObject, "object"); + }, `${this.name} namespace: typeof is "object"`); +}; + +IdlNamespace.prototype.test = function () +{ + if (!this.untested) { + this.test_self(); + } + for (const v of Object.values(this.members)) { switch (v.type) { @@ -3228,16 +3530,30 @@ function idl_test(srcs, deps, idl_setup_func) { srcs = (srcs instanceof Array) ? srcs : [srcs] || []; deps = (deps instanceof Array) ? deps : [deps] || []; var setup_error = null; + const validationIgnored = [ + "constructor-member", + "dict-arg-default", + "require-exposed" + ]; return Promise.all( - srcs.concat(deps).map(function(spec) { - return fetch_spec(spec); - })) - .then(function(idls) { + srcs.concat(deps).map(fetch_spec)) + .then(function(results) { + const astArray = results.map(result => + WebIDL2.parse(result.idl, { sourceName: result.spec }) + ); + test(() => { + const validations = WebIDL2.validate(astArray) + .filter(v => !validationIgnored.includes(v.ruleName)); + if (validations.length) { + const message = validations.map(v => v.message).join("\n\n"); + throw new Error(message); + } + }, "idl_test validation"); for (var i = 0; i < srcs.length; i++) { - idl_array.add_idls(idls[i]); + idl_array.internal_add_idls(astArray[i]); } for (var i = srcs.length; i < srcs.length + deps.length; i++) { - idl_array.add_dependency_idls(idls[i]); + idl_array.internal_add_dependency_idls(astArray[i]); } }) .then(function() { @@ -3272,6 +3588,6 @@ function fetch_spec(spec) { throw new IdlHarnessError("Error fetching " + url + "."); } return r.text(); - }); + }).then(idl => ({ spec, idl })); } // vim: set expandtab shiftwidth=4 tabstop=4 foldmarker=@{,@} foldmethod=marker: diff --git a/test/fixtures/wpt/resources/readme.md b/test/fixtures/wpt/resources/readme.md index 97d2e6f92203d1..09a62fbd7bfd7c 100644 --- a/test/fixtures/wpt/resources/readme.md +++ b/test/fixtures/wpt/resources/readme.md @@ -1,5 +1,8 @@ # Resources +This directory contains utilities intended for use by tests and maintained as project infrastructure. +It does not contain tests. + ## `testharness.js` `testharness.js` is a framework for writing low-level tests of @@ -7,20 +10,5 @@ browser functionality in javascript. It provides a convenient API for making assertions and is intended to work for both simple synchronous tests, and tests of asynchronous behaviour. -### Getting started - -To use `testharness.js` you must include two scripts, in the order given: - -``` html - - -``` - -### Full documentation - -For detailed API documentation please visit [https://web-platform-tests.org/writing-tests/testharness-api.html](https://web-platform-tests.org/writing-tests/testharness-api.html). - -### Tutorials - -You can also read a tutorial on -[Using testharness.js](http://darobin.github.com/test-harness-tutorial/docs/using-testharness.html). +Complete documentation is available in the `docs/` directory of this repository +and on the web at https://web-platform-tests.org/writing-tests/. diff --git a/test/fixtures/wpt/resources/sriharness.js b/test/fixtures/wpt/resources/sriharness.js index b36d29223585ec..d57a1b38465d64 100644 --- a/test/fixtures/wpt/resources/sriharness.js +++ b/test/fixtures/wpt/resources/sriharness.js @@ -32,6 +32,111 @@ SRIScriptTest.prototype.execute = function() { document.body.appendChild(e); }; +function set_extra_attributes(element, attrs) { + // Apply the rest of the attributes, if any. + for (const [attr_name, attr_val] of Object.entries(attrs)) { + element[attr_name] = attr_val; + } +} + +function buildElementFromDestination(resource_url, destination, attrs) { + // Assert: |destination| is a valid destination. + let element; + + // The below switch is responsible for: + // 1. Creating the correct subresource element + // 2. Setting said element's href, src, or fetch-instigating property + // appropriately. + switch (destination) { + case "script": + element = document.createElement(destination); + set_extra_attributes(element, attrs); + element.src = resource_url; + break; + case "style": + element = document.createElement('link'); + set_extra_attributes(element, attrs); + element.rel = 'stylesheet'; + element.href = resource_url; + break; + case "image": + element = document.createElement('img'); + set_extra_attributes(element, attrs); + element.src = resource_url; + break; + default: + assert_unreached("INVALID DESTINATION"); + } + + return element; +} + +// When using SRIPreloadTest, also include /preload/resources/preload_helper.js +// |number_of_requests| is used to ensure that preload requests are actually +// reused as expected. +const SRIPreloadTest = (preload_sri_success, subresource_sri_success, name, + number_of_requests, destination, resource_url, + link_attrs, subresource_attrs) => { + const test = async_test(name); + const link = document.createElement('link'); + + // Early-fail in UAs that do not support `preload` links. + test.step_func(() => { + assert_true(link.relList.supports('preload'), + "This test is automatically failing because the browser does not" + + "support `preload` links."); + })(); + + // Build up the link. + link.rel = 'preload'; + link.as = destination; + link.href = resource_url; + for (const [attr_name, attr_val] of Object.entries(link_attrs)) { + link[attr_name] = attr_val; // This may override `rel` to modulepreload. + } + + // Preload + subresource success and failure loading functions. + const valid_preload_failed = test.step_func(() => + { assert_unreached("Valid preload fired error handler.") }); + const invalid_preload_succeeded = test.step_func(() => + { assert_unreached("Invalid preload load succeeded.") }); + const valid_subresource_failed = test.step_func(() => + { assert_unreached("Valid subresource fired error handler.") }); + const invalid_subresource_succeeded = test.step_func(() => + { assert_unreached("Invalid subresource load succeeded.") }); + const subresource_pass = test.step_func(() => { + verifyNumberOfResourceTimingEntries(resource_url, number_of_requests); + test.done(); + }); + const preload_pass = test.step_func(() => { + const subresource_element = buildElementFromDestination( + resource_url, + destination, + subresource_attrs + ); + + if (subresource_sri_success) { + subresource_element.onload = subresource_pass; + subresource_element.onerror = valid_subresource_failed; + } else { + subresource_element.onload = invalid_subresource_succeeded; + subresource_element.onerror = subresource_pass; + } + + document.body.append(subresource_element); + }); + + if (preload_sri_success) { + link.onload = preload_pass; + link.onerror = valid_preload_failed; + } else { + link.onload = invalid_preload_succeeded; + link.onerror = preload_pass; + } + + document.head.append(link); +} + // tests // Style tests must be done synchronously because they rely on the presence // and absence of global style, which can affect later tests. Thus, instead @@ -63,6 +168,8 @@ SRIStyleTest.prototype.execute = function() { var div = document.createElement("div"); div.className = "testdiv"; var e = document.createElement("link"); + + // The link relation is guaranteed to not be "preload" or "modulepreload". this.attrs.rel = this.attrs.rel || "stylesheet"; for (var key in this.attrs) { if (this.attrs.hasOwnProperty(key)) { @@ -97,3 +204,4 @@ SRIStyleTest.prototype.execute = function() { container.appendChild(e); this.customCallback(e, container); }; + diff --git a/test/fixtures/wpt/resources/test-only-api.js b/test/fixtures/wpt/resources/test-only-api.js new file mode 100644 index 00000000000000..3fdf1cea6eb137 --- /dev/null +++ b/test/fixtures/wpt/resources/test-only-api.js @@ -0,0 +1,84 @@ +'use strict'; + +/* Whether the browser is Chromium-based with MojoJS enabled */ +const isChromiumBased = 'MojoInterfaceInterceptor' in self; +/* Whether the browser is WebKit-based with internal test-only API enabled */ +const isWebKitBased = !isChromiumBased && 'internals' in self; + +/** + * Loads a script in a window or worker. + * + * @param {string} path - A script path + * @returns {Promise} + */ +function loadScript(path) { + if (typeof document === 'undefined') { + // Workers (importScripts is synchronous and may throw.) + importScripts(path); + return Promise.resolve(); + } else { + // Window + const script = document.createElement('script'); + script.src = path; + script.async = false; + const p = new Promise((resolve, reject) => { + script.onload = () => { resolve(); }; + script.onerror = e => { reject(`Error loading ${path}`); }; + }) + document.head.appendChild(script); + return p; + } +} + +/** + * A helper for Chromium-based browsers to load Mojo JS bindings + * + * This is an async function that works in both workers and windows. It first + * loads mojo_bindings.js, disables automatic dependency loading, and loads all + * resources sequentially. The promise resolves if everything loads + * successfully, or rejects if any exception is raised. If testharness.js is + * used, an uncaught exception will terminate the test with a harness error + * (unless `allow_uncaught_exception` is true), which is usually the desired + * behaviour. + * + * This function also works with Blink web tests loaded from file://, in which + * case file:// will be prepended to all '/gen/...' URLs. + * + * Only call this function if isChromiumBased === true. + * + * @param {Array.} resources - A list of scripts to load: Mojo JS + * bindings should be of the form '/gen/../*.mojom.js', the ordering of which + * does not matter. Do not include mojo_bindings.js in this list. + * @returns {Promise} + */ +async function loadMojoResources(resources) { + if (!isChromiumBased) { + throw new Error('MojoJS not enabled; start Chrome with --enable-blink-features=MojoJS,MojoJSTest'); + } + if (resources.length == 0) { + return; + } + + let genPrefix = ''; + if (self.location.pathname.includes('/web_tests/')) { + // Blink internal web tests + genPrefix = 'file://'; + } + + for (const path of resources) { + // We want to load mojo_bindings.js separately to set mojo.config. + if (path.endsWith('/mojo_bindings.js')) { + throw new Error('Do not load mojo_bindings.js explicitly.'); + } + if (! /^\/gen\/.*\.mojom\.js$/.test(path)) { + throw new Error(`Unrecognized resource path: ${path}`); + } + } + + await loadScript(genPrefix + '/gen/layout_test_data/mojo/public/js/mojo_bindings.js'); + mojo.config.autoLoadMojomDeps = false; + + for (const path of resources) { + await loadScript(genPrefix + path); + } +} diff --git a/test/fixtures/wpt/resources/test-only-api.js.headers b/test/fixtures/wpt/resources/test-only-api.js.headers new file mode 100644 index 00000000000000..5e8f640c6659d1 --- /dev/null +++ b/test/fixtures/wpt/resources/test-only-api.js.headers @@ -0,0 +1,2 @@ +Content-Type: text/javascript; charset=utf-8 +Cache-Control: max-age=3600 diff --git a/test/fixtures/wpt/resources/testdriver-actions.js b/test/fixtures/wpt/resources/testdriver-actions.js index 43d8b1df00ae4c..870a2e8e266780 100644 --- a/test/fixtures/wpt/resources/testdriver-actions.js +++ b/test/fixtures/wpt/resources/testdriver-actions.js @@ -3,8 +3,10 @@ /** * Builder for creating a sequence of actions + * The default tick duration is set to 16ms, which is one frame time based on + * 60Hz display. */ - function Actions() { + function Actions(defaultTickDuration=16) { this.sourceTypes = new Map([["key", KeySource], ["pointer", PointerSource], ["none", GeneralSource]]); @@ -19,6 +21,7 @@ } this.createSource("none"); this.tickIdx = 0; + this.defaultTickDuration = defaultTickDuration; } Actions.prototype = { @@ -40,7 +43,7 @@ let actions = []; for (let [sourceType, sourceName] of this.sourceOrder) { let source = this.sources.get(sourceType).get(sourceName); - let serialized = source.serialize(this.tickIdx + 1); + let serialized = source.serialize(this.tickIdx + 1, this.defaultTickDuration); if (serialized) { serialized.id = sourceName; actions.push(serialized); @@ -127,9 +130,9 @@ /** * Add a new pointer input source with the given name * - * @param {String} type - Name of the key source + * @param {String} type - Name of the pointer source * @param {String} pointerType - Type of pointing device - * @param {Bool} set - Set source as the default key source + * @param {Bool} set - Set source as the default pointer source * @returns {Actions} */ addPointer: function(name, pointerType="mouse", set=true) { @@ -192,10 +195,16 @@ * Add a pause to the current tick * * @param {Number?} duration - Minimum length of the tick in ms. + * @param {String} sourceType - source type + * @param {String?} sourceName - Named key or pointer source to use or null for the default + * key or pointer source * @returns {Actions} */ - pause: function(duration) { - this.getSource("none").addPause(this, duration); + pause: function(duration=0, sourceType="none", {sourceName=null}={}) { + if (sourceType=="none") + this.getSource("none").addPause(this, duration); + else + this.getSource(sourceType, sourceName).addPause(this, duration); return this; }, @@ -278,17 +287,14 @@ } GeneralSource.prototype = { - serialize: function(tickCount) { - if (!this.actions.size) { - return undefined; - } + serialize: function(tickCount, defaultTickDuration) { let actions = []; let data = {"type": "none", "actions": actions}; for (let i=0; i 0) { + output.beginEllipsis = true; + } + if (upper_bound < arr.length) { + output.endEllipsis = true; + } + return output; + } + assert(typeof actual === "object" && actual !== null && "length" in actual, "assert_array_equals", description, "value is ${actual}, expected array", {actual:actual}); assert(actual.length === expected.length, "assert_array_equals", description, - "lengths differ, expected ${expected} got ${actual}", - {expected:expected.length, actual:actual.length}); + "lengths differ, expected array ${expected} length ${expectedLength}, got ${actual} length ${actualLength}", + {expected:shorten_array(expected, expected.length - 1), expectedLength:expected.length, + actual:shorten_array(actual, actual.length - 1), actualLength:actual.length + }); for (var i = 0; i < actual.length; i++) { assert(actual.hasOwnProperty(i) === expected.hasOwnProperty(i), "assert_array_equals", description, - "property ${i}, property expected to be ${expected} but was ${actual}", + "expected property ${i} to be ${expected} but was ${actual} (expected array ${arrayExpected} got ${arrayActual})", {i:i, expected:expected.hasOwnProperty(i) ? "present" : "missing", - actual:actual.hasOwnProperty(i) ? "present" : "missing"}); + actual:actual.hasOwnProperty(i) ? "present" : "missing", + arrayExpected:shorten_array(expected, i), arrayActual:shorten_array(actual, i)}); assert(same_value(expected[i], actual[i]), "assert_array_equals", description, - "property ${i}, expected ${expected} but got ${actual}", - {i:i, expected:expected[i], actual:actual[i]}); + "expected property ${i} to be ${expected} but got ${actual} (expected array ${arrayExpected} got ${arrayActual})", + {i:i, expected:expected[i], actual:actual[i], + arrayExpected:shorten_array(expected, i), arrayActual:shorten_array(actual, i)}); } } expose(assert_array_equals, "assert_array_equals"); @@ -1124,7 +1300,7 @@ policies and contribution forms [3]. "assert_array_approx_equals", description, "property ${i}, property expected to be ${expected} but was ${actual}", {i:i, expected:expected.hasOwnProperty(i) ? "present" : "missing", - actual:actual.hasOwnProperty(i) ? "present" : "missing"}); + actual:actual.hasOwnProperty(i) ? "present" : "missing"}); assert(typeof actual[i] === "number", "assert_array_approx_equals", description, "property ${i}, expected a number but got a ${type_actual}", @@ -1270,8 +1446,11 @@ policies and contribution forms [3]. expose(assert_regexp_match, "assert_regexp_match"); function assert_class_string(object, class_string, description) { - assert_equals({}.toString.call(object), "[object " + class_string + "]", - description); + var actual = {}.toString.call(object); + var expected = "[object " + class_string + "]"; + assert(same_value(actual, expected), "assert_class_string", description, + "expected ${expected} but got ${actual}", + {expected:expected, actual:actual}); } expose(assert_class_string, "assert_class_string"); @@ -1293,7 +1472,9 @@ policies and contribution forms [3]. function _assert_inherits(name) { return function (object, property_name, description) { - assert(typeof object === "object" || typeof object === "function", + assert(typeof object === "object" || typeof object === "function" || + // Or has [[IsHTMLDDA]] slot + String(object) === "[object HTMLAllCollection]", name, description, "provided value is not an object"); @@ -1333,47 +1514,153 @@ policies and contribution forms [3]. expose(assert_readonly, "assert_readonly"); /** - * Assert an Exception with the expected code is thrown. + * Assert a JS Error with the expected constructor is thrown. * - * @param {object|number|string} code The expected exception code. + * @param {object} constructor The expected exception constructor. * @param {Function} func Function which should throw. * @param {string} description Error description for the case that the error is not thrown. */ - function assert_throws(code, func, description) + function assert_throws_js(constructor, func, description) + { + assert_throws_js_impl(constructor, func, description, + "assert_throws_js"); + } + expose(assert_throws_js, "assert_throws_js"); + + /** + * Like assert_throws_js but allows specifying the assertion type + * (assert_throws_js or promise_rejects_js, in practice). + */ + function assert_throws_js_impl(constructor, func, description, + assertion_type) { try { func.call(this); - assert(false, "assert_throws", description, + assert(false, assertion_type, description, "${func} did not throw", {func:func}); } catch (e) { if (e instanceof AssertionError) { throw e; } + // Basic sanity-checks on the thrown exception. assert(typeof e === "object", - "assert_throws", description, + assertion_type, description, "${func} threw ${e} with type ${type}, not an object", {func:func, e:e, type:typeof e}); assert(e !== null, - "assert_throws", description, + assertion_type, description, "${func} threw null, not an object", {func:func}); - if (code === null) { - throw new AssertionError('Test bug: need to pass exception to assert_throws()'); + // Basic sanity-check on the passed-in constructor + assert(typeof constructor == "function", + assertion_type, description, + "${constructor} is not a constructor", + {constructor:constructor}); + var obj = constructor; + while (obj) { + if (typeof obj === "function" && + obj.name === "Error") { + break; + } + obj = Object.getPrototypeOf(obj); } - if (typeof code === "object") { - assert("name" in e && e.name == code.name, - "assert_throws", description, - "${func} threw ${actual} (${actual_name}) expected ${expected} (${expected_name})", - {func:func, actual:e, actual_name:e.name, - expected:code, - expected_name:code.name}); - return; + assert(obj != null, + assertion_type, description, + "${constructor} is not an Error subtype", + {constructor:constructor}); + + // And checking that our exception is reasonable + assert(e.constructor === constructor && + e.name === constructor.name, + assertion_type, description, + "${func} threw ${actual} (${actual_name}) expected instance of ${expected} (${expected_name})", + {func:func, actual:e, actual_name:e.name, + expected:constructor, + expected_name:constructor.name}); + } + } + + /** + * Assert a DOMException with the expected type is thrown. + * + * @param {number|string} type The expected exception name or code. See the + * table of names and codes at + * https://heycam.github.io/webidl/#dfn-error-names-table + * If a number is passed it should be one of the numeric code values + * in that table (e.g. 3, 4, etc). If a string is passed it can + * either be an exception name (e.g. "HierarchyRequestError", + * "WrongDocumentError") or the name of the corresponding error code + * (e.g. "HIERARCHY_REQUEST_ERR", "WRONG_DOCUMENT_ERR"). + * + * For the remaining arguments, there are two ways of calling + * promise_rejects_dom: + * + * 1) If the DOMException is expected to come from the current global, the + * second argument should be the function expected to throw and a third, + * optional, argument is the assertion description. + * + * 2) If the DOMException is expected to come from some other global, the + * second argument should be the DOMException constructor from that global, + * the third argument the function expected to throw, and the fourth, optional, + * argument the assertion description. + */ + function assert_throws_dom(type, funcOrConstructor, descriptionOrFunc, maybeDescription) + { + let constructor, func, description; + if (funcOrConstructor.name === "DOMException") { + constructor = funcOrConstructor; + func = descriptionOrFunc; + description = maybeDescription; + } else { + constructor = self.DOMException; + func = funcOrConstructor; + description = descriptionOrFunc; + assert(maybeDescription === undefined, + "Too many args pased to no-constructor version of assert_throws_dom"); + } + assert_throws_dom_impl(type, func, description, "assert_throws_dom", constructor) + } + expose(assert_throws_dom, "assert_throws_dom"); + + /** + * Similar to assert_throws_dom but allows specifying the assertion type + * (assert_throws_dom or promise_rejects_dom, in practice). The + * "constructor" argument must be the DOMException constructor from the + * global we expect the exception to come from. + */ + function assert_throws_dom_impl(type, func, description, assertion_type, constructor) + { + try { + func.call(this); + assert(false, assertion_type, description, + "${func} did not throw", {func:func}); + } catch (e) { + if (e instanceof AssertionError) { + throw e; } - var code_name_map = { + // Basic sanity-checks on the thrown exception. + assert(typeof e === "object", + assertion_type, description, + "${func} threw ${e} with type ${type}, not an object", + {func:func, e:e, type:typeof e}); + + assert(e !== null, + assertion_type, description, + "${func} threw null, not an object", + {func:func}); + + // Sanity-check our type + assert(typeof type == "number" || + typeof type == "string", + assertion_type, description, + "${type} is not a number or string", + {type:type}); + + var codename_name_map = { INDEX_SIZE_ERR: 'IndexSizeError', HIERARCHY_REQUEST_ERR: 'HierarchyRequestError', WRONG_DOCUMENT_ERR: 'WrongDocumentError', @@ -1398,8 +1685,6 @@ policies and contribution forms [3]. DATA_CLONE_ERR: 'DataCloneError' }; - var name = code in code_name_map ? code_name_map[code] : code; - var name_code_map = { IndexSizeError: 1, HierarchyRequestError: 3, @@ -1436,11 +1721,32 @@ policies and contribution forms [3]. NotAllowedError: 0 }; - if (!(name in name_code_map)) { - throw new AssertionError('Test bug: unrecognized DOMException code "' + code + '" passed to assert_throws()'); + var code_name_map = {}; + for (var key in name_code_map) { + if (name_code_map[key] > 0) { + code_name_map[name_code_map[key]] = key; + } } - var required_props = { code: name_code_map[name] }; + var required_props = {}; + var name; + + if (typeof type === "number") { + if (type === 0) { + throw new AssertionError('Test bug: ambiguous DOMException code 0 passed to assert_throws_dom()'); + } else if (!(type in code_name_map)) { + throw new AssertionError('Test bug: unrecognized DOMException code "' + type + '" passed to assert_throws_dom()'); + } + name = code_name_map[type]; + required_props.code = type; + } else if (typeof type === "string") { + name = type in codename_name_map ? codename_name_map[type] : type; + if (!(name in name_code_map)) { + throw new AssertionError('Test bug: unrecognized DOMException code name or name "' + type + '" passed to assert_throws_dom()'); + } + + required_props.code = name_code_map[name]; + } if (required_props.code === 0 || ("name" in e && @@ -1450,20 +1756,59 @@ policies and contribution forms [3]. required_props.name = name; } - //We'd like to test that e instanceof the appropriate interface, - //but we can't, because we don't know what window it was created - //in. It might be an instanceof the appropriate interface on some - //unknown other window. TODO: Work around this somehow? - for (var prop in required_props) { assert(prop in e && e[prop] == required_props[prop], - "assert_throws", description, - "${func} threw ${e} that is not a DOMException " + code + ": property ${prop} is equal to ${actual}, expected ${expected}", + assertion_type, description, + "${func} threw ${e} that is not a DOMException " + type + ": property ${prop} is equal to ${actual}, expected ${expected}", {func:func, e:e, prop:prop, actual:e[prop], expected:required_props[prop]}); } + + // Check that the exception is from the right global. This check is last + // so more specific, and more informative, checks on the properties can + // happen in case a totally incorrect exception is thrown. + assert(e.constructor === constructor, + assertion_type, description, + "${func} threw an exception from the wrong global", + {func}); + + } + } + + /** + * Assert the provided value is thrown. + * + * @param {value} exception The expected exception. + * @param {Function} func Function which should throw. + * @param {string} description Error description for the case that the error is not thrown. + */ + function assert_throws_exactly(exception, func, description) + { + assert_throws_exactly_impl(exception, func, description, + "assert_throws_exactly"); + } + expose(assert_throws_exactly, "assert_throws_exactly"); + + /** + * Like assert_throws_exactly but allows specifying the assertion type + * (assert_throws_exactly or promise_rejects_exactly, in practice). + */ + function assert_throws_exactly_impl(exception, func, description, + assertion_type) + { + try { + func.call(this); + assert(false, assertion_type, description, + "${func} did not throw", {func:func}); + } catch (e) { + if (e instanceof AssertionError) { + throw e; + } + + assert(same_value(e, exception), assertion_type, description, + "${func} threw ${e} but we expected it to throw ${exception}", + {func:func, e:e, exception:exception}); } } - expose(assert_throws, "assert_throws"); function assert_unreached(description) { assert(false, "assert_unreached", description, @@ -1492,6 +1837,43 @@ policies and contribution forms [3]. } expose(assert_any, "assert_any"); + /** + * Assert that a feature is implemented, based on a 'truthy' condition. + * + * This function should be used to early-exit from tests in which there is + * no point continuing without support for a non-optional spec or spec + * feature. For example: + * + * assert_implements(window.Foo, 'Foo is not supported'); + * + * @param {object} condition The truthy value to test + * @param {string} description Error description for the case that the condition is not truthy. + */ + function assert_implements(condition, description) { + assert(!!condition, "assert_implements", description); + } + expose(assert_implements, "assert_implements") + + /** + * Assert that an optional feature is implemented, based on a 'truthy' condition. + * + * This function should be used to early-exit from tests in which there is + * no point continuing without support for an explicitly optional spec or + * spec feature. For example: + * + * assert_implements_optional(video.canPlayType("video/webm"), + * "webm video playback not supported"); + * + * @param {object} condition The truthy value to test + * @param {string} description Error description for the case that the condition is not truthy. + */ + function assert_implements_optional(condition, description) { + if (!condition) { + throw new OptionalFeatureUnsupportedError(description); + } + } + expose(assert_implements_optional, "assert_implements_optional") + function Test(name, properties) { if (tests.file_is_test && tests.tests.length) { @@ -1506,7 +1888,7 @@ policies and contribution forms [3]. this.timeout_id = null; this.index = null; - this.properties = properties; + this.properties = properties || {}; this.timeout_length = settings.test_timeout; if (this.timeout_length !== null) { this.timeout_length *= tests.timeout_multiplier; @@ -1536,7 +1918,8 @@ policies and contribution forms [3]. PASS:0, FAIL:1, TIMEOUT:2, - NOTRUN:3 + NOTRUN:3, + PRECONDITION_FAILED:4 }; Test.prototype = merge({}, Test.statuses); @@ -1596,10 +1979,11 @@ policies and contribution forms [3]. if (this.phase >= this.phases.HAS_RESULT) { return; } + var status = e instanceof OptionalFeatureUnsupportedError ? this.PRECONDITION_FAILED : this.FAIL; var message = String((typeof e === "object" && e !== null) ? e.message : e); var stack = e.stack ? e.stack : null; - this.set_status(this.FAIL, message, stack); + this.set_status(status, message, stack); this.phase = this.phases.HAS_RESULT; this.done(); } @@ -1651,6 +2035,105 @@ policies and contribution forms [3]. return setTimeout(this.step_func(function() { return f.apply(test_this, args); }), timeout * tests.timeout_multiplier); + }; + + Test.prototype.step_wait_func = function(cond, func, description, + timeout=3000, interval=100) { + /** + * Poll for a function to return true, and call a callback + * function once it does, or assert if a timeout is + * reached. This is preferred over a simple step_timeout + * whenever possible since it allows the timeout to be longer + * to reduce intermittents without compromising test execution + * speed when the condition is quickly met. + * + * @param {Function} cond A function taking no arguments and + * returning a boolean. The callback is called + * when this function returns true. + * @param {Function} func A function taking no arguments to call once + * the condition is met. + * @param {string} description Error message to add to assert in case of + * failure. + * @param {number} timeout Timeout in ms. This is multiplied by the global + * timeout_multiplier + * @param {number} interval Polling interval in ms + * + **/ + + var timeout_full = timeout * tests.timeout_multiplier; + var remaining = Math.ceil(timeout_full / interval); + var test_this = this; + + var wait_for_inner = test_this.step_func(() => { + if (cond()) { + func(); + } else { + if(remaining === 0) { + assert(false, "wait_for", description, + "Timed out waiting on condition"); + } + remaining--; + setTimeout(wait_for_inner, interval); + } + }); + + wait_for_inner(); + }; + + Test.prototype.step_wait_func_done = function(cond, func, description, + timeout=3000, interval=100) { + /** + * Poll for a function to return true, and invoke a callback + * followed by this.done() once it does, or assert if a timeout + * is reached. This is preferred over a simple step_timeout + * whenever possible since it allows the timeout to be longer + * to reduce intermittents without compromising test execution speed + * when the condition is quickly met. + * + * @param {Function} cond A function taking no arguments and + * returning a boolean. The callback is called + * when this function returns true. + * @param {Function} func A function taking no arguments to call once + * the condition is met. + * @param {string} description Error message to add to assert in case of + * failure. + * @param {number} timeout Timeout in ms. This is multiplied by the global + * timeout_multiplier + * @param {number} interval Polling interval in ms + * + **/ + + this.step_wait_func(cond, () => { + if (func) { + func(); + } + this.done(); + }, description, timeout, interval); + } + + Test.prototype.step_wait = function(cond, description, timeout=3000, interval=100) { + /** + * Poll for a function to return true, and resolve a promise + * once it does, or assert if a timeout is reached. This is + * preferred over a simple step_timeout whenever possible + * since it allows the timeout to be longer to reduce + * intermittents without compromising test execution speed + * when the condition is quickly met. + * + * @param {Function} cond A function taking no arguments and + * returning a boolean. + * @param {string} description Error message to add to assert in case of + * failure. + * @param {number} timeout Timeout in ms. This is multiplied by the global + * timeout_multiplier + * @param {number} interval Polling interval in ms + * @returns {Promise} Promise resolved once cond is met. + * + **/ + + return new Promise(resolve => { + this.step_wait_func(cond, resolve, description, timeout, interval); + }); } /* @@ -2070,7 +2553,8 @@ policies and contribution forms [3]. TestsStatus.statuses = { OK:0, ERROR:1, - TIMEOUT:2 + TIMEOUT:2, + PRECONDITION_FAILED:3 }; TestsStatus.prototype = merge({}, TestsStatus.statuses); @@ -2111,6 +2595,10 @@ policies and contribution forms [3]. this.allow_uncaught_exception = false; this.file_is_test = false; + // This value is lazily initialized in order to avoid introducing a + // dependency on ECMAScript 2015 Promises to all tests. + this.promise_tests = null; + this.promise_setup_called = false; this.timeout_multiplier = 1; this.timeout_length = test_environment.test_timeout(); @@ -2121,6 +2609,7 @@ policies and contribution forms [3]. this.test_done_callbacks = []; this.all_done_callbacks = []; + this.hide_test_state = false; this.pending_remotes = []; this.status = new TestsStatus(); @@ -2161,11 +2650,15 @@ policies and contribution forms [3]. { clearTimeout(this.timeout_id); } + } else if (p == "single_test" && value) { + this.set_file_is_test(); } else if (p == "timeout_multiplier") { this.timeout_multiplier = value; if (this.timeout_length) { this.timeout_length *= this.timeout_multiplier; } + } else if (p == "hide_test_state") { + this.hide_test_state = value; } } } @@ -2174,9 +2667,10 @@ policies and contribution forms [3]. try { func(); } catch (e) { - this.status.status = this.status.ERROR; + this.status.status = e instanceof OptionalFeatureUnsupportedError ? this.status.PRECONDITION_FAILED : this.status.ERROR; this.status.message = String(e); this.status.stack = e.stack ? e.stack : null; + this.complete(); } } this.set_timeout(); @@ -2467,24 +2961,8 @@ policies and contribution forms [3]. var message_port; if (is_service_worker(worker)) { - if (window.MessageChannel) { - // The ServiceWorker's implicit MessagePort is currently not - // reliably accessible from the ServiceWorkerGlobalScope due to - // Blink setting MessageEvent.source to null for messages sent - // via ServiceWorker.postMessage(). Until that's resolved, - // create an explicit MessageChannel and pass one end to the - // worker. - var message_channel = new MessageChannel(); - message_port = message_channel.port1; - message_port.start(); - worker.postMessage({type: "connect"}, [message_channel.port2]); - } else { - // If MessageChannel is not available, then try the - // ServiceWorker.postMessage() approach using MessageEvent.source - // on the other end. - message_port = navigator.serviceWorker; - worker.postMessage({type: "connect"}); - } + message_port = navigator.serviceWorker; + worker.postMessage({type: "connect"}); } else if (is_shared_worker(worker)) { message_port = worker.port; message_port.start(); @@ -2687,7 +3165,7 @@ policies and contribution forms [3]. this.phase = this.HAVE_RESULTS; } var done_count = tests.tests.length - tests.num_pending; - if (this.output_node) { + if (this.output_node && !tests.hide_test_state) { if (done_count < 100 || (done_count < 1000 && done_count % 100 === 0) || done_count % 1000 === 0) { @@ -2731,12 +3209,14 @@ policies and contribution forms [3]. status_text_harness[harness_status.OK] = "OK"; status_text_harness[harness_status.ERROR] = "Error"; status_text_harness[harness_status.TIMEOUT] = "Timeout"; + status_text_harness[harness_status.PRECONDITION_FAILED] = "Optional Feature Unsupported"; var status_text = {}; status_text[Test.prototype.PASS] = "Pass"; status_text[Test.prototype.FAIL] = "Fail"; status_text[Test.prototype.TIMEOUT] = "Timeout"; status_text[Test.prototype.NOTRUN] = "Not Run"; + status_text[Test.prototype.PRECONDITION_FAILED] = "Optional Feature Unsupported"; var status_number = {}; forEach(tests, @@ -3056,9 +3536,6 @@ policies and contribution forms [3]. */ function assert(expected_true, function_name, description, error, substitutions) { - if (tests.tests.length === 0) { - tests.set_file_is_test(); - } if (expected_true !== true) { var msg = make_message(function_name, description, error, substitutions); @@ -3121,6 +3598,13 @@ policies and contribution forms [3]. return lines.slice(i).join("\n"); } + function OptionalFeatureUnsupportedError(message) + { + AssertionError.call(this, message); + } + OptionalFeatureUnsupportedError.prototype = Object.create(AssertionError.prototype); + expose(OptionalFeatureUnsupportedError, "OptionalFeatureUnsupportedError"); + function make_message(function_name, description, error, substitutions) { for (var p in substitutions) { @@ -3348,38 +3832,56 @@ policies and contribution forms [3]. var tests = new Tests(); if (global_scope.addEventListener) { - var error_handler = function(e) { - if (tests.tests.length === 0 && !tests.allow_uncaught_exception) { - tests.set_file_is_test(); - } - - var stack; - if (e.error && e.error.stack) { - stack = e.error.stack; - } else { - stack = e.filename + ":" + e.lineno + ":" + e.colno; - } - + var error_handler = function(error, message, stack) { + var optional_unsupported = error instanceof OptionalFeatureUnsupportedError; if (tests.file_is_test) { var test = tests.tests[0]; if (test.phase >= test.phases.HAS_RESULT) { return; } - test.set_status(test.FAIL, e.message, stack); + var status = optional_unsupported ? test.PRECONDITION_FAILED : test.FAIL; + test.set_status(status, message, stack); test.phase = test.phases.HAS_RESULT; - // The following function invocation is superfluous. - // TODO: Remove. - test.done(); } else if (!tests.allow_uncaught_exception) { - tests.status.status = tests.status.ERROR; - tests.status.message = e.message; + var status = optional_unsupported ? tests.status.PRECONDITION_FAILED : tests.status.ERROR; + tests.status.status = status; + tests.status.message = message; tests.status.stack = stack; } - done(); + + // Do not transition to the "complete" phase if the test has been + // configured to allow uncaught exceptions. This gives the test an + // opportunity to define subtests based on the exception reporting + // behavior. + if (!tests.allow_uncaught_exception) { + done(); + } }; - addEventListener("error", error_handler, false); - addEventListener("unhandledrejection", function(e){ error_handler(e.reason); }, false); + addEventListener("error", function(e) { + var message = e.message; + var stack; + if (e.error && e.error.stack) { + stack = e.error.stack; + } else { + stack = e.filename + ":" + e.lineno + ":" + e.colno; + } + error_handler(e.error, message, stack); + }, false); + + addEventListener("unhandledrejection", function(e) { + var message; + if (e.reason && e.reason.message) { + message = "Unhandled rejection: " + e.reason.message; + } else { + message = "Unhandled rejection"; + } + var stack; + if (e.reason && e.reason.stack) { + stack = e.reason.stack; + } + error_handler(e.reason, message, stack); + }, false); } test_environment.on_tests_ready(); @@ -3416,7 +3918,7 @@ table#results {\ \ table#results th:first-child,\ table#results td:first-child {\ - width:4em;\ + width:8em;\ }\ \ table#results th:last-child,\ @@ -3457,7 +3959,11 @@ tr.notrun > td:first-child {\ color:blue;\ }\ \ -.pass > td:first-child, .fail > td:first-child, .timeout > td:first-child, .notrun > td:first-child {\ +tr.optionalunsupported > td:first-child {\ + color:blue;\ +}\ +\ +.pass > td:first-child, .fail > td:first-child, .timeout > td:first-child, .notrun > td:first-child, .optionalunsupported > td:first-child {\ font-variant:small-caps;\ }\ \ @@ -3492,5 +3998,5 @@ span.ok, span.timeout, span.error {\ }\ "; -})(this); +})(self); // vim: set expandtab shiftwidth=4 tabstop=4: diff --git a/test/fixtures/wpt/resources/webidl2/lib/webidl2.js b/test/fixtures/wpt/resources/webidl2/lib/webidl2.js index ef519c09df6d6d..d707905fa63e16 100644 --- a/test/fixtures/wpt/resources/webidl2/lib/webidl2.js +++ b/test/fixtures/wpt/resources/webidl2/lib/webidl2.js @@ -1,970 +1,3223 @@ +(function webpackUniversalModuleDefinition(root, factory) { + if(typeof exports === 'object' && typeof module === 'object') + module.exports = factory(); + else if(typeof define === 'function' && define.amd) + define([], factory); + else if(typeof exports === 'object') + exports["WebIDL2"] = factory(); + else + root["WebIDL2"] = factory(); +})(this, function() { +return /******/ (function(modules) { // webpackBootstrap +/******/ // The module cache +/******/ var installedModules = {}; +/******/ +/******/ // The require function +/******/ function __webpack_require__(moduleId) { +/******/ +/******/ // Check if module is in cache +/******/ if(installedModules[moduleId]) { +/******/ return installedModules[moduleId].exports; +/******/ } +/******/ // Create a new module (and put it into the cache) +/******/ var module = installedModules[moduleId] = { +/******/ i: moduleId, +/******/ l: false, +/******/ exports: {} +/******/ }; +/******/ +/******/ // Execute the module function +/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); +/******/ +/******/ // Flag the module as loaded +/******/ module.l = true; +/******/ +/******/ // Return the exports of the module +/******/ return module.exports; +/******/ } +/******/ +/******/ +/******/ // expose the modules object (__webpack_modules__) +/******/ __webpack_require__.m = modules; +/******/ +/******/ // expose the module cache +/******/ __webpack_require__.c = installedModules; +/******/ +/******/ // define getter function for harmony exports +/******/ __webpack_require__.d = function(exports, name, getter) { +/******/ if(!__webpack_require__.o(exports, name)) { +/******/ Object.defineProperty(exports, name, { enumerable: true, get: getter }); +/******/ } +/******/ }; +/******/ +/******/ // define __esModule on exports +/******/ __webpack_require__.r = function(exports) { +/******/ if(typeof Symbol !== 'undefined' && Symbol.toStringTag) { +/******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' }); +/******/ } +/******/ Object.defineProperty(exports, '__esModule', { value: true }); +/******/ }; +/******/ +/******/ // create a fake namespace object +/******/ // mode & 1: value is a module id, require it +/******/ // mode & 2: merge all properties of value into the ns +/******/ // mode & 4: return value when already ns object +/******/ // mode & 8|1: behave like require +/******/ __webpack_require__.t = function(value, mode) { +/******/ if(mode & 1) value = __webpack_require__(value); +/******/ if(mode & 8) return value; +/******/ if((mode & 4) && typeof value === 'object' && value && value.__esModule) return value; +/******/ var ns = Object.create(null); +/******/ __webpack_require__.r(ns); +/******/ Object.defineProperty(ns, 'default', { enumerable: true, value: value }); +/******/ if(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key)); +/******/ return ns; +/******/ }; +/******/ +/******/ // getDefaultExport function for compatibility with non-harmony modules +/******/ __webpack_require__.n = function(module) { +/******/ var getter = module && module.__esModule ? +/******/ function getDefault() { return module['default']; } : +/******/ function getModuleExports() { return module; }; +/******/ __webpack_require__.d(getter, 'a', getter); +/******/ return getter; +/******/ }; +/******/ +/******/ // Object.prototype.hasOwnProperty.call +/******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); }; +/******/ +/******/ // __webpack_public_path__ +/******/ __webpack_require__.p = ""; +/******/ +/******/ +/******/ // Load entry module and return exports +/******/ return __webpack_require__(__webpack_require__.s = 0); +/******/ }) +/************************************************************************/ +/******/ ([ +/* 0 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + "use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony import */ var _lib_webidl2_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(1); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "parse", function() { return _lib_webidl2_js__WEBPACK_IMPORTED_MODULE_0__["parse"]; }); -(() => { - // These regular expressions use the sticky flag so they will only match at - // the current location (ie. the offset of lastIndex). - const tokenRe = { - // This expression uses a lookahead assertion to catch false matches - // against integers early. - "float": /-?(?=[0-9]*\.|[0-9]+[eE])(([0-9]+\.[0-9]*|[0-9]*\.[0-9]+)([Ee][-+]?[0-9]+)?|[0-9]+[Ee][-+]?[0-9]+)/y, - "integer": /-?(0([Xx][0-9A-Fa-f]+|[0-7]*)|[1-9][0-9]*)/y, - "identifier": /_?[A-Za-z][0-9A-Z_a-z-]*/y, - "string": /"[^"]*"/y, - "whitespace": /[\t\n\r ]+/y, - "comment": /((\/(\/.*|\*([^*]|\*[^\/])*\*\/)[\t\n\r ]*)+)/y, - "other": /[^\t\n\r 0-9A-Za-z]/y - }; +/* harmony import */ var _lib_writer_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(30); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "write", function() { return _lib_writer_js__WEBPACK_IMPORTED_MODULE_1__["write"]; }); - const stringTypes = [ - "ByteString", - "DOMString", - "USVString" - ]; - - const argumentNameKeywords = [ - "attribute", - "callback", - "const", - "deleter", - "dictionary", - "enum", - "getter", - "includes", - "inherit", - "interface", - "iterable", - "maplike", - "namespace", - "partial", - "required", - "setlike", - "setter", - "static", - "stringifier", - "typedef", - "unrestricted" - ]; - - const nonRegexTerminals = [ - "FrozenArray", - "Infinity", - "NaN", - "Promise", - "boolean", - "byte", - "double", - "false", - "float", - "implements", - "legacyiterable", - "long", - "mixin", - "null", - "octet", - "optional", - "or", - "readonly", - "record", - "sequence", - "short", - "true", - "unsigned", - "void" - ].concat(argumentNameKeywords, stringTypes); - - const punctuations = [ - "(", - ")", - ",", - "-Infinity", - "...", - ":", - ";", - "<", - "=", - ">", - "?", - "[", - "]", - "{", - "}" - ]; - - function tokenise(str) { - const tokens = []; - let lastIndex = 0; - let trivia = ""; - while (lastIndex < str.length) { - const nextChar = str.charAt(lastIndex); - let result = -1; - - if (/[\t\n\r ]/.test(nextChar)) { - result = attemptTokenMatch("whitespace", { noFlushTrivia: true }); - } else if (nextChar === '/') { - result = attemptTokenMatch("comment", { noFlushTrivia: true }); - } +/* harmony import */ var _lib_validator_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(31); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "validate", function() { return _lib_validator_js__WEBPACK_IMPORTED_MODULE_2__["validate"]; }); - if (result !== -1) { - trivia += tokens.pop().value; - } else if (/[-0-9.]/.test(nextChar)) { - result = attemptTokenMatch("float"); - if (result === -1) { - result = attemptTokenMatch("integer"); - } - } else if (/[A-Z_a-z]/.test(nextChar)) { - result = attemptTokenMatch("identifier"); - const token = tokens[tokens.length - 1]; - if (result !== -1 && nonRegexTerminals.includes(token.value)) { - token.type = token.value; - } - } else if (nextChar === '"') { - result = attemptTokenMatch("string"); - } +/* harmony import */ var _lib_tokeniser_js__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(2); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "WebIDLParseError", function() { return _lib_tokeniser_js__WEBPACK_IMPORTED_MODULE_3__["WebIDLParseError"]; }); - for (const punctuation of punctuations) { - if (str.startsWith(punctuation, lastIndex)) { - tokens.push({ type: punctuation, value: punctuation, trivia }); - trivia = ""; - lastIndex += punctuation.length; - result = lastIndex; - break; - } - } - // other as the last try - if (result === -1) { - result = attemptTokenMatch("other"); - } - if (result === -1) { - throw new Error("Token stream not progressing"); - } - lastIndex = result; - } - return tokens; - function attemptTokenMatch(type, { noFlushTrivia } = {}) { - const re = tokenRe[type]; - re.lastIndex = lastIndex; - const result = re.exec(str); - if (result) { - tokens.push({ type, value: result[0], trivia }); - if (!noFlushTrivia) { - trivia = ""; - } - return re.lastIndex; - } - return -1; - } - } - class WebIDLParseError { - constructor(str, line, input, tokens) { - this.message = str; - this.line = line; - this.input = input; - this.tokens = tokens; - } - toString() { - const escapedInput = JSON.stringify(this.input); - const tokens = JSON.stringify(this.tokens, null, 4); - return `${this.message}, line ${this.line} (tokens: ${escapedInput})\n${tokens}`; - } - } - function parse(tokens) { - let line = 1; - tokens = tokens.slice(); - const names = new Map(); - let current = null; - const FLOAT = "float"; - const INT = "integer"; - const ID = "identifier"; - const STR = "string"; - const OTHER = "other"; +/***/ }), +/* 1 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { - const EMPTY_OPERATION = Object.freeze({ - type: "operation", - getter: false, - setter: false, - deleter: false, - static: false, - stringifier: false - }); +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "parse", function() { return parse; }); +/* harmony import */ var _tokeniser_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(2); +/* harmony import */ var _productions_enum_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(15); +/* harmony import */ var _productions_includes_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(16); +/* harmony import */ var _productions_extended_attributes_js__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(8); +/* harmony import */ var _productions_typedef_js__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(17); +/* harmony import */ var _productions_callback_js__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(18); +/* harmony import */ var _productions_interface_js__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(19); +/* harmony import */ var _productions_mixin_js__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(25); +/* harmony import */ var _productions_dictionary_js__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__(26); +/* harmony import */ var _productions_namespace_js__WEBPACK_IMPORTED_MODULE_9__ = __webpack_require__(28); +/* harmony import */ var _productions_callback_interface_js__WEBPACK_IMPORTED_MODULE_10__ = __webpack_require__(29); +/* harmony import */ var _productions_helpers_js__WEBPACK_IMPORTED_MODULE_11__ = __webpack_require__(4); - const EMPTY_IDLTYPE = Object.freeze({ - generic: null, - nullable: false, - union: false, - idlType: null, - extAttrs: [] - }); - function error(str) { - const maxTokens = 5; - const tok = tokens - .slice(consume_position, consume_position + maxTokens) - .map(t => t.trivia + t.value).join(""); - // Count newlines preceding the actual erroneous token - if (tokens.length) { - line += count(tokens[consume_position].trivia, "\n"); - } - let message; - if (current) { - message = `Got an error during or right after parsing \`${current.partial ? "partial " : ""}${current.type} ${current.name}\`: ${str}` - } - else { - // throwing before any valid definition - message = `Got an error before parsing any named definition: ${str}`; - } - throw new WebIDLParseError(message, line, tok, tokens.slice(0, maxTokens)); - } - function sanitize_name(name, type) { - if (names.has(name)) { - error(`The name "${name}" of type "${names.get(name)}" is already seen`); - } - names.set(name, type); - return name; - } - let consume_position = 0; - function probe(type) { - return tokens.length > consume_position && tokens[consume_position].type === type; - } - function consume(...candidates) { - // TODO: use const when Servo updates its JS engine - for (let type of candidates) { - if (!probe(type)) continue; - const token = tokens[consume_position]; - consume_position++; - line += count(token.trivia, "\n"); - return token; - } - } - function unescape(identifier) { - return identifier.startsWith('_') ? identifier.slice(1) : identifier; - } - function unconsume(position) { - while (consume_position > position) { - consume_position--; - line -= count(tokens[consume_position].trivia, "\n"); - } - } - function count(str, char) { - let total = 0; - for (let i = str.indexOf(char); i !== -1; i = str.indexOf(char, i + 1)) { - ++total; - } - return total; - } - function integer_type() { - let ret = ""; - if (consume("unsigned")) ret = "unsigned "; - if (consume("short")) return ret + "short"; - if (consume("long")) { - ret += "long"; - if (consume("long")) return ret + " long"; - return ret; - } - if (ret) error("Failed to parse integer type"); - } - function float_type() { - let ret = ""; - if (consume("unrestricted")) ret = "unrestricted "; - if (consume("float")) return ret + "float"; - if (consume("double")) return ret + "double"; - if (ret) error("Failed to parse float type"); - } - function primitive_type() { - const num_type = integer_type() || float_type(); - if (num_type) return num_type; - if (consume("boolean")) return "boolean"; - if (consume("byte")) return "byte"; - if (consume("octet")) return "octet"; - } - function const_value() { - if (consume("true")) return { type: "boolean", value: true }; - if (consume("false")) return { type: "boolean", value: false }; - if (consume("null")) return { type: "null" }; - if (consume("Infinity")) return { type: "Infinity", negative: false }; - if (consume("-Infinity")) return { type: "Infinity", negative: true }; - if (consume("NaN")) return { type: "NaN" }; - const ret = consume(FLOAT, INT); - if (ret) return { type: "number", value: ret.value }; - } +/** + * @param {Tokeniser} tokeniser + * @param {object} options + * @param {boolean} [options.concrete] + */ +function parseByTokens(tokeniser, options) { + const source = tokeniser.source; - function type_suffix(obj) { - obj.nullable = !!consume("?"); - if (probe("?")) error("Can't nullable more than once"); - } + function error(str) { + tokeniser.error(str); + } - function generic_type(typeName) { - const name = consume("FrozenArray", "Promise", "sequence", "record"); - if (!name) { - return; - } - const ret = { generic: name.type }; - consume("<") || error(`No opening bracket after ${name.type}`); - switch (name.type) { - case "Promise": - if (probe("[")) error("Promise type cannot have extended attribute"); - ret.idlType = return_type(typeName); - break; - case "sequence": - case "FrozenArray": - ret.idlType = type_with_extended_attributes(typeName); - break; - case "record": - if (probe("[")) error("Record key cannot have extended attribute"); - ret.idlType = []; - const keyType = consume(...stringTypes); - if (!keyType) error(`Record key must be a string type`); - ret.idlType.push(Object.assign({ type: typeName }, EMPTY_IDLTYPE, { idlType: keyType.value })); - consume(",") || error("Missing comma after record key type"); - const valueType = type_with_extended_attributes(typeName) || error("Error parsing generic type record"); - ret.idlType.push(valueType); - break; - } - if (!ret.idlType) error(`Error parsing generic type ${name.type}`); - consume(">") || error(`Missing closing bracket after ${name.type}`); - if (name.type === "Promise" && probe("?")) { - error("Promise type cannot be nullable"); - } - type_suffix(ret); - return ret; - } + function consume(...candidates) { + return tokeniser.consume(...candidates); + } - function single_type(typeName) { - const ret = Object.assign({ type: typeName || null }, EMPTY_IDLTYPE); - const generic = generic_type(typeName); - if (generic) { - return Object.assign(ret, generic); - } - const prim = primitive_type(); - let name; - if (prim) { - ret.idlType = prim; - } else if (name = consume(ID, ...stringTypes)) { - ret.idlType = name.value; - if (probe("<")) error(`Unsupported generic type ${name.value}`); - } else { - return; - } - type_suffix(ret); - if (ret.nullable && ret.idlType === "any") error("Type any cannot be made nullable"); - return ret; - } - - function union_type(typeName) { - if (!consume("(")) return; - const ret = Object.assign({ type: typeName || null }, EMPTY_IDLTYPE, { union: true, idlType: [] }); - do { - const typ = type_with_extended_attributes() || error("No type after open parenthesis or 'or' in union type"); - ret.idlType.push(typ); - } while (consume("or")); - if (ret.idlType.length < 2) { - error("At least two types are expected in a union type but found less"); - } - if (!consume(")")) error("Unterminated union type"); - type_suffix(ret); - return ret; + function callback() { + const callback = consume("callback"); + if (!callback) return; + if (tokeniser.probe("interface")) { + return _productions_callback_interface_js__WEBPACK_IMPORTED_MODULE_10__["CallbackInterface"].parse(tokeniser, callback); } + return _productions_callback_js__WEBPACK_IMPORTED_MODULE_5__["CallbackFunction"].parse(tokeniser, callback); + } - function type(typeName) { - return single_type(typeName) || union_type(typeName); - } + function interface_(opts) { + const base = consume("interface"); + if (!base) return; + const ret = _productions_mixin_js__WEBPACK_IMPORTED_MODULE_7__["Mixin"].parse(tokeniser, base, opts) || + _productions_interface_js__WEBPACK_IMPORTED_MODULE_6__["Interface"].parse(tokeniser, base, opts) || + error("Interface has no proper body"); + return ret; + } - function type_with_extended_attributes(typeName) { - const extAttrs = extended_attrs(); - const ret = single_type(typeName) || union_type(typeName); - if (extAttrs.length && ret) ret.extAttrs = extAttrs; - return ret; - } + function partial() { + const partial = consume("partial"); + if (!partial) return; + return _productions_dictionary_js__WEBPACK_IMPORTED_MODULE_8__["Dictionary"].parse(tokeniser, { partial }) || + interface_({ partial }) || + _productions_namespace_js__WEBPACK_IMPORTED_MODULE_9__["Namespace"].parse(tokeniser, { partial }) || + error("Partial doesn't apply to anything"); + } - function argument() { - const start_position = consume_position; - const ret = { optional: false, variadic: false, default: null }; - ret.extAttrs = extended_attrs(); - const opt_token = consume("optional"); - if (opt_token) { - ret.optional = true; - } - ret.idlType = type_with_extended_attributes("argument-type"); - if (!ret.idlType) { - unconsume(start_position); - return; - } - if (!ret.optional && consume("...")) { - ret.variadic = true; - } - const name = consume(ID, ...argumentNameKeywords); - if (!name) { - unconsume(start_position); - return; - } - ret.name = unescape(name.value); - ret.escapedName = name.value; - if (ret.optional) { - ret.default = default_() || null; + function definition() { + return callback() || + interface_() || + partial() || + _productions_dictionary_js__WEBPACK_IMPORTED_MODULE_8__["Dictionary"].parse(tokeniser) || + _productions_enum_js__WEBPACK_IMPORTED_MODULE_1__["Enum"].parse(tokeniser) || + _productions_typedef_js__WEBPACK_IMPORTED_MODULE_4__["Typedef"].parse(tokeniser) || + _productions_includes_js__WEBPACK_IMPORTED_MODULE_2__["Includes"].parse(tokeniser) || + _productions_namespace_js__WEBPACK_IMPORTED_MODULE_9__["Namespace"].parse(tokeniser); + } + + function definitions() { + if (!source.length) return []; + const defs = []; + while (true) { + const ea = _productions_extended_attributes_js__WEBPACK_IMPORTED_MODULE_3__["ExtendedAttributes"].parse(tokeniser); + const def = definition(); + if (!def) { + if (ea.length) error("Stray extended attributes"); + break; } - return ret; + Object(_productions_helpers_js__WEBPACK_IMPORTED_MODULE_11__["autoParenter"])(def).extAttrs = ea; + defs.push(def); + } + const eof = consume("eof"); + if (options.concrete) { + defs.push(eof); } + return defs; + } + const res = definitions(); + if (tokeniser.position < source.length) error("Unrecognised tokens"); + return res; +} + +/** + * @param {string} str + * @param {object} [options] + * @param {*} [options.sourceName] + * @param {boolean} [options.concrete] + */ +function parse(str, options = {}) { + const tokeniser = new _tokeniser_js__WEBPACK_IMPORTED_MODULE_0__["Tokeniser"](str); + if (typeof options.sourceName !== "undefined") { + tokeniser.source.name = options.sourceName; + } + return parseByTokens(tokeniser, options); +} - function argument_list() { - const ret = []; - const arg = argument(); - if (!arg) return ret; - ret.push(arg); - while (true) { - if (!consume(",")) return ret; - const nxt = argument() || error("Trailing comma in arguments list"); - ret.push(nxt); - } + +/***/ }), +/* 2 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "typeNameKeywords", function() { return typeNameKeywords; }); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "stringTypes", function() { return stringTypes; }); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "argumentNameKeywords", function() { return argumentNameKeywords; }); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "Tokeniser", function() { return Tokeniser; }); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "WebIDLParseError", function() { return WebIDLParseError; }); +/* harmony import */ var _error_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(3); +/* harmony import */ var _productions_helpers_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(4); + + + +// These regular expressions use the sticky flag so they will only match at +// the current location (ie. the offset of lastIndex). +const tokenRe = { + // This expression uses a lookahead assertion to catch false matches + // against integers early. + "decimal": /-?(?=[0-9]*\.|[0-9]+[eE])(([0-9]+\.[0-9]*|[0-9]*\.[0-9]+)([Ee][-+]?[0-9]+)?|[0-9]+[Ee][-+]?[0-9]+)/y, + "integer": /-?(0([Xx][0-9A-Fa-f]+|[0-7]*)|[1-9][0-9]*)/y, + "identifier": /[_-]?[A-Za-z][0-9A-Z_a-z-]*/y, + "string": /"[^"]*"/y, + "whitespace": /[\t\n\r ]+/y, + "comment": /((\/(\/.*|\*([^*]|\*[^/])*\*\/)[\t\n\r ]*)+)/y, + "other": /[^\t\n\r 0-9A-Za-z]/y +}; + +const typeNameKeywords = [ + "ArrayBuffer", + "DataView", + "Int8Array", + "Int16Array", + "Int32Array", + "Uint8Array", + "Uint16Array", + "Uint32Array", + "Uint8ClampedArray", + "Float32Array", + "Float64Array", + "any", + "object", + "symbol" +]; + +const stringTypes = [ + "ByteString", + "DOMString", + "USVString" +]; + +const argumentNameKeywords = [ + "async", + "attribute", + "callback", + "const", + "constructor", + "deleter", + "dictionary", + "enum", + "getter", + "includes", + "inherit", + "interface", + "iterable", + "maplike", + "namespace", + "partial", + "required", + "setlike", + "setter", + "static", + "stringifier", + "typedef", + "unrestricted" +]; + +const nonRegexTerminals = [ + "-Infinity", + "FrozenArray", + "Infinity", + "NaN", + "Promise", + "boolean", + "byte", + "double", + "false", + "float", + "long", + "mixin", + "null", + "octet", + "optional", + "or", + "readonly", + "record", + "sequence", + "short", + "true", + "unsigned", + "void" +].concat(argumentNameKeywords, stringTypes, typeNameKeywords); + +const punctuations = [ + "(", + ")", + ",", + "...", + ":", + ";", + "<", + "=", + ">", + "?", + "[", + "]", + "{", + "}" +]; + +const reserved = [ + // "constructor" is now a keyword + "_constructor", + "toString", + "_toString", +]; + +/** + * @typedef {ArrayItemType>} Token + * @param {string} str + */ +function tokenise(str) { + const tokens = []; + let lastCharIndex = 0; + let trivia = ""; + let line = 1; + let index = 0; + while (lastCharIndex < str.length) { + const nextChar = str.charAt(lastCharIndex); + let result = -1; + + if (/[\t\n\r ]/.test(nextChar)) { + result = attemptTokenMatch("whitespace", { noFlushTrivia: true }); + } else if (nextChar === '/') { + result = attemptTokenMatch("comment", { noFlushTrivia: true }); } - function simple_extended_attr() { - const name = consume(ID); - if (!name) return; - const ret = { - name: name.value, - arguments: null, - type: "extended-attribute", - rhs: null - }; - const eq = consume("="); - if (eq) { - ret.rhs = consume(ID, FLOAT, INT, STR); - if (ret.rhs) { - // No trivia exposure yet - ret.rhs.trivia = undefined; - } - } - if (consume("(")) { - if (eq && !ret.rhs) { - // [Exposed=(Window,Worker)] - ret.rhs = { - type: "identifier-list", - value: identifiers() - }; - } - else { - // [NamedConstructor=Audio(DOMString src)] or [Constructor(DOMString str)] - ret.arguments = argument_list(); - } - consume(")") || error("Unexpected token in extended attribute argument list"); - } - if (eq && !ret.rhs) error("No right hand side to extended attribute assignment"); - return ret; - } - - // Note: we parse something simpler than the official syntax. It's all that ever - // seems to be used - function extended_attrs() { - const eas = []; - if (!consume("[")) return eas; - eas[0] = simple_extended_attr() || error("Extended attribute with not content"); - while (consume(",")) { - eas.push(simple_extended_attr() || error("Trailing comma in extended attribute")); + if (result !== -1) { + const currentTrivia = tokens.pop().value; + line += (currentTrivia.match(/\n/g) || []).length; + trivia += currentTrivia; + index -= 1; + } else if (/[-0-9.A-Z_a-z]/.test(nextChar)) { + result = attemptTokenMatch("decimal"); + if (result === -1) { + result = attemptTokenMatch("integer"); } - consume("]") || error("No end of extended attribute"); - return eas; - } - - function default_() { - if (consume("=")) { - const def = const_value(); - if (def) { - return def; - } else if (consume("[")) { - if (!consume("]")) error("Default sequence value must be empty"); - return { type: "sequence", value: [] }; - } else { - const str = consume(STR) || error("No value for default"); - str.value = str.value.slice(1, -1); - // No trivia exposure yet - str.trivia = undefined; - return str; + if (result === -1) { + result = attemptTokenMatch("identifier"); + const lastIndex = tokens.length - 1; + const token = tokens[lastIndex]; + if (result !== -1) { + if (reserved.includes(token.value)) { + const message = `${Object(_productions_helpers_js__WEBPACK_IMPORTED_MODULE_1__["unescape"])(token.value)} is a reserved identifier and must not be used.`; + throw new WebIDLParseError(Object(_error_js__WEBPACK_IMPORTED_MODULE_0__["syntaxError"])(tokens, lastIndex, null, message)); + } else if (nonRegexTerminals.includes(token.value)) { + token.type = token.value; + } } } + } else if (nextChar === '"') { + result = attemptTokenMatch("string"); } - function const_() { - if (!consume("const")) return; - const ret = { type: "const", nullable: false }; - let typ = primitive_type(); - if (!typ) { - typ = consume(ID) || error("No type for const"); - typ = typ.value; - } - ret.idlType = Object.assign({ type: "const-type" }, EMPTY_IDLTYPE, { idlType: typ }); - type_suffix(ret); - const name = consume(ID) || error("No name for const"); - ret.name = name.value; - consume("=") || error("No value assignment for const"); - const cnt = const_value(); - if (cnt) ret.value = cnt; - else error("No value for const"); - consume(";") || error("Unterminated const"); - return ret; - } - - function inheritance() { - if (consume(":")) { - const inh = consume(ID) || error("No type in inheritance"); - return inh.value; + for (const punctuation of punctuations) { + if (str.startsWith(punctuation, lastCharIndex)) { + tokens.push({ type: punctuation, value: punctuation, trivia, line, index }); + trivia = ""; + lastCharIndex += punctuation.length; + result = lastCharIndex; + break; } } - function operation_rest(ret) { - if (!ret) ret = {}; - const name = consume(ID); - ret.name = name ? unescape(name.value) : null; - ret.escapedName = name ? name.value : null; - consume("(") || error("Invalid operation"); - ret.arguments = argument_list(); - consume(")") || error("Unterminated operation"); - consume(";") || error("Unterminated operation"); - return ret; - } - - function callback() { - let ret; - if (!consume("callback")) return; - const tok = consume("interface"); - if (tok) { - ret = interface_rest(false, "callback interface"); - return ret; - } - const name = consume(ID) || error("No name for callback"); - ret = current = { type: "callback", name: sanitize_name(name.value, "callback") }; - consume("=") || error("No assignment in callback"); - ret.idlType = return_type() || error("Missing return type"); - consume("(") || error("No arguments in callback"); - ret.arguments = argument_list(); - consume(")") || error("Unterminated callback"); - consume(";") || error("Unterminated callback"); - return ret; - } - - function attribute({ noInherit = false, readonly = false } = {}) { - const start_position = consume_position; - const ret = { - type: "attribute", - static: false, - stringifier: false, - inherit: false, - readonly: false - }; - if (!noInherit && consume("inherit")) { - ret.inherit = true; - } - if (consume("readonly")) { - ret.readonly = true; - } else if (readonly && probe("attribute")) { - error("Attributes must be readonly in this context"); - } - const rest = attribute_rest(ret); - if (!rest) { - unconsume(start_position); - } - return rest; + // other as the last try + if (result === -1) { + result = attemptTokenMatch("other"); } + if (result === -1) { + throw new Error("Token stream not progressing"); + } + lastCharIndex = result; + index += 1; + } - function attribute_rest(ret) { - if (!consume("attribute")) { - return; - } - ret.idlType = type_with_extended_attributes("attribute-type") || error("No type in attribute"); - if (ret.idlType.generic === "sequence") error("Attributes cannot accept sequence types"); - if (ret.idlType.generic === "record") error("Attributes cannot accept record types"); - const name = consume(ID, "required") || error("No name in attribute"); - ret.name = unescape(name.value); - ret.escapedName = name.value; - consume(";") || error("Unterminated attribute"); - return ret; - } - - function return_type(typeName) { - const typ = type(typeName || "return-type"); - if (typ) { - return typ; - } - if (consume("void")) { - return Object.assign({ type: "return-type" }, EMPTY_IDLTYPE, { idlType: "void" }); + // remaining trivia as eof + tokens.push({ + type: "eof", + value: "", + trivia + }); + + return tokens; + + /** + * @param {keyof typeof tokenRe} type + * @param {object} options + * @param {boolean} [options.noFlushTrivia] + */ + function attemptTokenMatch(type, { noFlushTrivia } = {}) { + const re = tokenRe[type]; + re.lastIndex = lastCharIndex; + const result = re.exec(str); + if (result) { + tokens.push({ type, value: result[0], trivia, line, index }); + if (!noFlushTrivia) { + trivia = ""; } + return re.lastIndex; } + return -1; + } +} + +class Tokeniser { + /** + * @param {string} idl + */ + constructor(idl) { + this.source = tokenise(idl); + this.position = 0; + } - function operation({ regular = false } = {}) { - const ret = Object.assign({}, EMPTY_OPERATION); - while (!regular) { - if (consume("getter")) ret.getter = true; - else if (consume("setter")) ret.setter = true; - else if (consume("deleter")) ret.deleter = true; - else break; - } - ret.idlType = return_type() || error("Missing return type"); - operation_rest(ret); - return ret; + /** + * @param {string} message + * @return {never} + */ + error(message) { + throw new WebIDLParseError(Object(_error_js__WEBPACK_IMPORTED_MODULE_0__["syntaxError"])(this.source, this.position, this.current, message)); + } + + /** + * @param {string} type + */ + probe(type) { + return this.source.length > this.position && this.source[this.position].type === type; + } + + /** + * @param {...string} candidates + */ + consume(...candidates) { + for (const type of candidates) { + if (!this.probe(type)) continue; + const token = this.source[this.position]; + this.position++; + return token; } + } + + /** + * @param {number} position + */ + unconsume(position) { + this.position = position; + } +} + +class WebIDLParseError extends Error { + /** + * @param {object} options + * @param {string} options.message + * @param {string} options.bareMessage + * @param {string} options.context + * @param {number} options.line + * @param {*} options.sourceName + * @param {string} options.input + * @param {*[]} options.tokens + */ + constructor({ message, bareMessage, context, line, sourceName, input, tokens }) { + super(message); + + this.name = "WebIDLParseError"; // not to be mangled + this.bareMessage = bareMessage; + this.context = context; + this.line = line; + this.sourceName = sourceName; + this.input = input; + this.tokens = tokens; + } +} + + +/***/ }), +/* 3 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "syntaxError", function() { return syntaxError; }); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "validationError", function() { return validationError; }); +/** + * @param {string} text + */ +function lastLine(text) { + const splitted = text.split("\n"); + return splitted[splitted.length - 1]; +} + +/** + * @typedef {object} WebIDL2ErrorOptions + * @property {"error" | "warning"} [level] + * @property {Function} [autofix] + * + * @param {string} message error message + * @param {"Syntax" | "Validation"} kind error type + * @param {WebIDL2ErrorOptions} [options] + */ +function error(source, position, current, message, kind, { level = "error", autofix, ruleName } = {}) { + /** + * @param {number} count + */ + function sliceTokens(count) { + return count > 0 ? + source.slice(position, position + count) : + source.slice(Math.max(position + count, 0), position); + } - function static_member() { - if (!consume("static")) return; - const member = attribute({ noInherit: true }) || - operation({ regular: true }) || - error("No body in static member"); - member.static = true; - return member; + function tokensToText(inputs, { precedes } = {}) { + const text = inputs.map(t => t.trivia + t.value).join(""); + const nextToken = source[position]; + if (nextToken.type === "eof") { + return text; } + if (precedes) { + return text + nextToken.trivia; + } + return text.slice(nextToken.trivia.length); + } - function stringifier() { - if (!consume("stringifier")) return; - if (consume(";")) { - return Object.assign({}, EMPTY_OPERATION, { stringifier: true }); - } - const member = attribute({ noInherit: true }) || - operation({ regular: true }) || - error("Unterminated stringifier"); - member.stringifier = true; - return member; - } - - function identifiers() { - const arr = []; - const id = consume(ID); - if (id) { - arr.push(id.value); - } - else error("Expected identifiers but not found"); - while (true) { - if (consume(",")) { - const name = consume(ID) || error("Trailing comma in identifiers list"); - arr.push(name.value); - } else break; + const maxTokens = 5; // arbitrary but works well enough + const line = + source[position].type !== "eof" ? source[position].line : + source.length > 1 ? source[position - 1].line : + 1; + + const precedingLastLine = lastLine( + tokensToText(sliceTokens(-maxTokens), { precedes: true }) + ); + + const subsequentTokens = sliceTokens(maxTokens); + const subsequentText = tokensToText(subsequentTokens); + const subsequentFirstLine = subsequentText.split("\n")[0]; + + const spaced = " ".repeat(precedingLastLine.length) + "^"; + const sourceContext = precedingLastLine + subsequentFirstLine + "\n" + spaced; + + const contextType = kind === "Syntax" ? "since" : "inside"; + const inSourceName = source.name ? ` in ${source.name}` : ""; + const grammaticalContext = (current && current.name) ? `, ${contextType} \`${current.partial ? "partial " : ""}${current.type} ${current.name}\`` : ""; + const context = `${kind} error at line ${line}${inSourceName}${grammaticalContext}:\n${sourceContext}`; + return { + message: `${context} ${message}`, + bareMessage: message, + context, + line, + sourceName: source.name, + level, + ruleName, + autofix, + input: subsequentText, + tokens: subsequentTokens + }; +} + +/** + * @param {string} message error message + */ +function syntaxError(source, position, current, message) { + return error(source, position, current, message, "Syntax"); +} + +/** + * @param {string} message error message + * @param {WebIDL2ErrorOptions} [options] + */ +function validationError(token, current, ruleName, message, options = {}) { + options.ruleName = ruleName; + return error(current.source, token.index, current, message, "Validation", options); +} + + +/***/ }), +/* 4 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "unescape", function() { return unescape; }); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "list", function() { return list; }); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "const_value", function() { return const_value; }); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "const_data", function() { return const_data; }); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "primitive_type", function() { return primitive_type; }); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "argument_list", function() { return argument_list; }); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "type_with_extended_attributes", function() { return type_with_extended_attributes; }); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "return_type", function() { return return_type; }); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "stringifier", function() { return stringifier; }); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "getLastIndentation", function() { return getLastIndentation; }); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "getMemberIndentation", function() { return getMemberIndentation; }); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "autofixAddExposedWindow", function() { return autofixAddExposedWindow; }); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "getFirstToken", function() { return getFirstToken; }); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "findLastIndex", function() { return findLastIndex; }); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "autoParenter", function() { return autoParenter; }); +/* harmony import */ var _type_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(5); +/* harmony import */ var _argument_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(11); +/* harmony import */ var _extended_attributes_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(8); +/* harmony import */ var _operation_js__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(13); +/* harmony import */ var _attribute_js__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(14); +/* harmony import */ var _tokeniser_js__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(2); + + + + + + + +/** + * @param {string} identifier + */ +function unescape(identifier) { + return identifier.startsWith('_') ? identifier.slice(1) : identifier; +} + +/** + * Parses comma-separated list + * @param {import("../tokeniser").Tokeniser} tokeniser + * @param {object} args + * @param {Function} args.parser parser function for each item + * @param {boolean} [args.allowDangler] whether to allow dangling comma + * @param {string} [args.listName] the name to be shown on error messages + */ +function list(tokeniser, { parser, allowDangler, listName = "list" }) { + const first = parser(tokeniser); + if (!first) { + return []; + } + first.tokens.separator = tokeniser.consume(","); + const items = [first]; + while (first.tokens.separator) { + const item = parser(tokeniser); + if (!item) { + if (!allowDangler) { + tokeniser.error(`Trailing comma in ${listName}`); } - return arr; + break; } - - function iterable_type() { - if (consume("iterable")) return "iterable"; - else if (consume("legacyiterable")) return "legacyiterable"; - else if (consume("maplike")) return "maplike"; - else if (consume("setlike")) return "setlike"; - else return; + item.tokens.separator = tokeniser.consume(","); + items.push(item); + if (!item.tokens.separator) break; + } + return items; +} + +/** + * @param {import("../tokeniser").Tokeniser} tokeniser + */ +function const_value(tokeniser) { + return tokeniser.consume("true", "false", "Infinity", "-Infinity", "NaN", "decimal", "integer"); +} + +/** + * @param {object} token + * @param {string} token.type + * @param {string} token.value + */ +function const_data({ type, value }) { + switch (type) { + case "true": + case "false": + return { type: "boolean", value: type === "true" }; + case "Infinity": + case "-Infinity": + return { type: "Infinity", negative: type.startsWith("-") }; + case "[": + return { type: "sequence", value: [] }; + case "{": + return { type: "dictionary" }; + case "decimal": + case "integer": + return { type: "number", value }; + case "string": + return { type: "string", value: value.slice(1, -1) }; + default: + return { type }; + } +} + +/** + * @param {import("../tokeniser").Tokeniser} tokeniser + */ +function primitive_type(tokeniser) { + function integer_type() { + const prefix = tokeniser.consume("unsigned"); + const base = tokeniser.consume("short", "long"); + if (base) { + const postfix = tokeniser.consume("long"); + return new _type_js__WEBPACK_IMPORTED_MODULE_0__["Type"]({ source, tokens: { prefix, base, postfix } }); } + if (prefix) tokeniser.error("Failed to parse integer type"); + } - function readonly_iterable_type() { - if (consume("maplike")) return "maplike"; - else if (consume("setlike")) return "setlike"; - else return; + function decimal_type() { + const prefix = tokeniser.consume("unrestricted"); + const base = tokeniser.consume("float", "double"); + if (base) { + return new _type_js__WEBPACK_IMPORTED_MODULE_0__["Type"]({ source, tokens: { prefix, base } }); } + if (prefix) tokeniser.error("Failed to parse float type"); + } - function iterable() { - const start_position = consume_position; - const ret = { type: null, idlType: null, readonly: false }; - if (consume("readonly")) { - ret.readonly = true; + const { source } = tokeniser; + const num_type = integer_type(tokeniser) || decimal_type(tokeniser); + if (num_type) return num_type; + const base = tokeniser.consume("boolean", "byte", "octet"); + if (base) { + return new _type_js__WEBPACK_IMPORTED_MODULE_0__["Type"]({ source, tokens: { base } }); + } +} + +/** + * @param {import("../tokeniser").Tokeniser} tokeniser + */ +function argument_list(tokeniser) { + return list(tokeniser, { parser: _argument_js__WEBPACK_IMPORTED_MODULE_1__["Argument"].parse, listName: "arguments list" }); +} + +/** + * @param {import("../tokeniser").Tokeniser} tokeniser + * @param {string} typeName + */ +function type_with_extended_attributes(tokeniser, typeName) { + const extAttrs = _extended_attributes_js__WEBPACK_IMPORTED_MODULE_2__["ExtendedAttributes"].parse(tokeniser); + const ret = _type_js__WEBPACK_IMPORTED_MODULE_0__["Type"].parse(tokeniser, typeName); + if (ret) autoParenter(ret).extAttrs = extAttrs; + return ret; +} + +/** + * @param {import("../tokeniser").Tokeniser} tokeniser + * @param {string} typeName + */ +function return_type(tokeniser, typeName) { + const typ = _type_js__WEBPACK_IMPORTED_MODULE_0__["Type"].parse(tokeniser, typeName || "return-type"); + if (typ) { + return typ; + } + const voidToken = tokeniser.consume("void"); + if (voidToken) { + const ret = new _type_js__WEBPACK_IMPORTED_MODULE_0__["Type"]({ source: tokeniser.source, tokens: { base: voidToken } }); + ret.type = "return-type"; + return ret; + } +} + +/** + * @param {import("../tokeniser").Tokeniser} tokeniser + */ +function stringifier(tokeniser) { + const special = tokeniser.consume("stringifier"); + if (!special) return; + const member = _attribute_js__WEBPACK_IMPORTED_MODULE_4__["Attribute"].parse(tokeniser, { special }) || + _operation_js__WEBPACK_IMPORTED_MODULE_3__["Operation"].parse(tokeniser, { special }) || + tokeniser.error("Unterminated stringifier"); + return member; +} + +/** + * @param {string} str + */ +function getLastIndentation(str) { + const lines = str.split("\n"); + // the first line visually binds to the preceding token + if (lines.length) { + const match = lines[lines.length - 1].match(/^\s+/); + if (match) { + return match[0]; + } + } + return ""; +} + +/** + * @param {string} parentTrivia + */ +function getMemberIndentation(parentTrivia) { + const indentation = getLastIndentation(parentTrivia); + const indentCh = indentation.includes("\t") ? "\t" : " "; + return indentation + indentCh; +} + +/** + * @param {object} def + * @param {import("./extended-attributes.js").ExtendedAttributes} def.extAttrs + */ +function autofixAddExposedWindow(def) { + return () => { + if (def.extAttrs.length){ + const tokeniser = new _tokeniser_js__WEBPACK_IMPORTED_MODULE_5__["Tokeniser"]("Exposed=Window,"); + const exposed = _extended_attributes_js__WEBPACK_IMPORTED_MODULE_2__["SimpleExtendedAttribute"].parse(tokeniser); + exposed.tokens.separator = tokeniser.consume(","); + const existing = def.extAttrs[0]; + if (!/^\s/.test(existing.tokens.name.trivia)) { + existing.tokens.name.trivia = ` ${existing.tokens.name.trivia}`; } - const consumeItType = ret.readonly ? readonly_iterable_type : iterable_type; - - const ittype = consumeItType(); - if (!ittype) { - unconsume(start_position); - return; + def.extAttrs.unshift(exposed); + } else { + autoParenter(def).extAttrs = _extended_attributes_js__WEBPACK_IMPORTED_MODULE_2__["ExtendedAttributes"].parse(new _tokeniser_js__WEBPACK_IMPORTED_MODULE_5__["Tokeniser"]("[Exposed=Window]")); + const trivia = def.tokens.base.trivia; + def.extAttrs.tokens.open.trivia = trivia; + def.tokens.base.trivia = `\n${getLastIndentation(trivia)}`; + } + }; +} + +/** + * Get the first syntax token for the given IDL object. + * @param {*} data + */ +function getFirstToken(data) { + if (data.extAttrs.length) { + return data.extAttrs.tokens.open; + } + if (data.type === "operation" && !data.special) { + return getFirstToken(data.idlType); + } + const tokens = Object.values(data.tokens).sort((x, y) => x.index - y.index); + return tokens[0]; +} + +/** + * @template T + * @param {T[]} array + * @param {(item: T) => boolean} predicate + */ +function findLastIndex(array, predicate) { + const index = array.slice().reverse().findIndex(predicate); + if (index === -1) { + return index; + } + return array.length - index - 1; +} + +/** + * Returns a proxy that auto-assign `parent` field. + * @template T + * @param {T} data + * @param {*} [parent] The object that will be assigned to `parent`. + * If absent, it will be `data` by default. + * @return {T} + */ +function autoParenter(data, parent) { + if (!parent) { + // Defaults to `data` unless specified otherwise. + parent = data; + } + if (!data) { + // This allows `autoParenter(undefined)` which again allows + // `autoParenter(parse())` where the function may return nothing. + return data; + } + return new Proxy(data, { + get(target, p) { + const value = target[p]; + if (Array.isArray(value)) { + // Wraps the array so that any added items will also automatically + // get their `parent` values. + return autoParenter(value, target); } - - const secondTypeRequired = ittype === "maplike"; - const secondTypeAllowed = secondTypeRequired || ittype === "iterable"; - ret.type = ittype; - if (ret.type !== 'maplike' && ret.type !== 'setlike') - delete ret.readonly; - if (consume("<")) { - ret.idlType = [type_with_extended_attributes()] || error(`Error parsing ${ittype} declaration`); - if (secondTypeAllowed) { - if (consume(",")) { - ret.idlType.push(type_with_extended_attributes()); + return value; + }, + set(target, p, value) { + target[p] = value; + if (!value) { + return true; + } else if (Array.isArray(value)) { + // Assigning an array will add `parent` to its items. + for (const item of value) { + if (typeof item.parent !== "undefined") { + item.parent = parent; } - else if (secondTypeRequired) - error(`Missing second type argument in ${ittype} declaration`); } - if (!consume(">")) error(`Unterminated ${ittype} declaration`); - if (!consume(";")) error(`Missing semicolon after ${ittype} declaration`); - } else - error(`Error parsing ${ittype} declaration`); - - return ret; - } - - function interface_rest(isPartial, typeName = "interface") { - const name = consume(ID) || error("No name for interface"); - const mems = []; - const ret = current = { - type: typeName, - name: isPartial ? name.value : sanitize_name(name.value, "interface"), - partial: isPartial, - members: mems - }; - if (!isPartial) ret.inheritance = inheritance() || null; - consume("{") || error("Bodyless interface"); - while (true) { - if (consume("}")) { - consume(";") || error("Missing semicolon after interface"); - return ret; - } - const ea = extended_attrs(); - const mem = const_() || - static_member() || - stringifier() || - iterable() || - attribute() || - operation() || - error("Unknown member"); - mem.extAttrs = ea; - ret.members.push(mem); + } else if (typeof value.parent !== "undefined") { + value.parent = parent; } + return true; } + }); +} - function mixin_rest(isPartial) { - if (!consume("mixin")) return; - const name = consume(ID) || error("No name for interface mixin"); - const mems = []; - const ret = current = { - type: "interface mixin", - name: isPartial ? name.value : sanitize_name(name.value, "interface mixin"), - partial: isPartial, - members: mems - }; - consume("{") || error("Bodyless interface mixin"); - while (true) { - if (consume("}")) { - consume(";") || error("Missing semicolon after interface mixin"); - return ret; - } - const ea = extended_attrs(); - const mem = const_() || - stringifier() || - attribute({ noInherit: true }) || - operation({ regular: true }) || - error("Unknown member"); - mem.extAttrs = ea; - ret.members.push(mem); - } + +/***/ }), +/* 5 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "Type", function() { return Type; }); +/* harmony import */ var _base_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(6); +/* harmony import */ var _helpers_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(4); +/* harmony import */ var _tokeniser_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(2); +/* harmony import */ var _error_js__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(3); +/* harmony import */ var _validators_helpers_js__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(7); +/* harmony import */ var _extended_attributes_js__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(8); + + + + + + + +/** + * @param {import("../tokeniser").Tokeniser} tokeniser + * @param {string} typeName + */ +function generic_type(tokeniser, typeName) { + const base = tokeniser.consume("FrozenArray", "Promise", "sequence", "record"); + if (!base) { + return; + } + const ret = Object(_helpers_js__WEBPACK_IMPORTED_MODULE_1__["autoParenter"])(new Type({ source: tokeniser.source, tokens: { base } })); + ret.tokens.open = tokeniser.consume("<") || tokeniser.error(`No opening bracket after ${base.type}`); + switch (base.type) { + case "Promise": { + if (tokeniser.probe("[")) tokeniser.error("Promise type cannot have extended attribute"); + const subtype = Object(_helpers_js__WEBPACK_IMPORTED_MODULE_1__["return_type"])(tokeniser, typeName) || tokeniser.error("Missing Promise subtype"); + ret.subtype.push(subtype); + break; + } + case "sequence": + case "FrozenArray": { + const subtype = Object(_helpers_js__WEBPACK_IMPORTED_MODULE_1__["type_with_extended_attributes"])(tokeniser, typeName) || tokeniser.error(`Missing ${base.type} subtype`); + ret.subtype.push(subtype); + break; + } + case "record": { + if (tokeniser.probe("[")) tokeniser.error("Record key cannot have extended attribute"); + const keyType = tokeniser.consume(..._tokeniser_js__WEBPACK_IMPORTED_MODULE_2__["stringTypes"]) || tokeniser.error(`Record key must be one of: ${_tokeniser_js__WEBPACK_IMPORTED_MODULE_2__["stringTypes"].join(", ")}`); + const keyIdlType = new Type({ source: tokeniser.source, tokens: { base: keyType }}); + keyIdlType.tokens.separator = tokeniser.consume(",") || tokeniser.error("Missing comma after record key type"); + keyIdlType.type = typeName; + const valueType = Object(_helpers_js__WEBPACK_IMPORTED_MODULE_1__["type_with_extended_attributes"])(tokeniser, typeName) || tokeniser.error("Error parsing generic type record"); + ret.subtype.push(keyIdlType, valueType); + break; + } + } + if (!ret.idlType) tokeniser.error(`Error parsing generic type ${base.type}`); + ret.tokens.close = tokeniser.consume(">") || tokeniser.error(`Missing closing bracket after ${base.type}`); + return ret.this; +} + +/** + * @param {import("../tokeniser").Tokeniser} tokeniser + */ +function type_suffix(tokeniser, obj) { + const nullable = tokeniser.consume("?"); + if (nullable) { + obj.tokens.nullable = nullable; + } + if (tokeniser.probe("?")) tokeniser.error("Can't nullable more than once"); +} + +/** + * @param {import("../tokeniser").Tokeniser} tokeniser + * @param {string} typeName + */ +function single_type(tokeniser, typeName) { + let ret = generic_type(tokeniser, typeName) || Object(_helpers_js__WEBPACK_IMPORTED_MODULE_1__["primitive_type"])(tokeniser); + if (!ret) { + const base = tokeniser.consume("identifier", ..._tokeniser_js__WEBPACK_IMPORTED_MODULE_2__["stringTypes"], ..._tokeniser_js__WEBPACK_IMPORTED_MODULE_2__["typeNameKeywords"]); + if (!base) { + return; + } + ret = new Type({ source: tokeniser.source, tokens: { base } }); + if (tokeniser.probe("<")) tokeniser.error(`Unsupported generic type ${base.value}`); + } + if (ret.generic === "Promise" && tokeniser.probe("?")) { + tokeniser.error("Promise type cannot be nullable"); + } + ret.type = typeName || null; + type_suffix(tokeniser, ret); + if (ret.nullable && ret.idlType === "any") tokeniser.error("Type `any` cannot be made nullable"); + return ret; +} + +/** + * @param {import("../tokeniser").Tokeniser} tokeniser + * @param {string} type + */ +function union_type(tokeniser, type) { + const tokens = {}; + tokens.open = tokeniser.consume("("); + if (!tokens.open) return; + const ret = Object(_helpers_js__WEBPACK_IMPORTED_MODULE_1__["autoParenter"])(new Type({ source: tokeniser.source, tokens })); + ret.type = type || null; + while (true) { + const typ = Object(_helpers_js__WEBPACK_IMPORTED_MODULE_1__["type_with_extended_attributes"])(tokeniser) || tokeniser.error("No type after open parenthesis or 'or' in union type"); + if (typ.idlType === "any") tokeniser.error("Type `any` cannot be included in a union type"); + if (typ.generic === "Promise") tokeniser.error("Type `Promise` cannot be included in a union type"); + ret.subtype.push(typ); + const or = tokeniser.consume("or"); + if (or) { + typ.tokens.separator = or; } + else break; + } + if (ret.idlType.length < 2) { + tokeniser.error("At least two types are expected in a union type but found less"); + } + tokens.close = tokeniser.consume(")") || tokeniser.error("Unterminated union type"); + type_suffix(tokeniser, ret); + return ret.this; +} + +class Type extends _base_js__WEBPACK_IMPORTED_MODULE_0__["Base"] { + /** + * @param {import("../tokeniser").Tokeniser} tokeniser + * @param {string} typeName + */ + static parse(tokeniser, typeName) { + return single_type(tokeniser, typeName) || union_type(tokeniser, typeName); + } + + constructor({ source, tokens }) { + super({ source, tokens }); + Object.defineProperty(this, "subtype", { value: [], writable: true }); + this.extAttrs = new _extended_attributes_js__WEBPACK_IMPORTED_MODULE_5__["ExtendedAttributes"]({}); + } - function interface_(isPartial) { - if (!consume("interface")) return; - return mixin_rest(isPartial) || - interface_rest(isPartial) || - error("Interface has no proper body"); + get generic() { + if (this.subtype.length && this.tokens.base) { + return this.tokens.base.value; + } + return ""; + } + get nullable() { + return Boolean(this.tokens.nullable); + } + get union() { + return Boolean(this.subtype.length) && !this.tokens.base; + } + get idlType() { + if (this.subtype.length) { + return this.subtype; } + // Adding prefixes/postfixes for "unrestricted float", etc. + const name = [ + this.tokens.prefix, + this.tokens.base, + this.tokens.postfix + ].filter(t => t).map(t => t.value).join(" "); + return Object(_helpers_js__WEBPACK_IMPORTED_MODULE_1__["unescape"])(name); + } - function namespace(isPartial) { - if (!consume("namespace")) return; - const name = consume(ID) || error("No name for namespace"); - const mems = []; - const ret = current = { - type: "namespace", - name: isPartial ? name.value : sanitize_name(name.value, "namespace"), - partial: isPartial, - members: mems - }; - consume("{") || error("Bodyless namespace"); - while (true) { - if (consume("}")) { - consume(";") || error("Missing semicolon after namespace"); - return ret; - } - const ea = extended_attrs(); - const mem = attribute({ noInherit: true, readonly: true }) || - operation({ regular: true }) || - error("Unknown member"); - mem.extAttrs = ea; - ret.members.push(mem); + *validate(defs) { + yield* this.extAttrs.validate(defs); + /* + * If a union is nullable, its subunions cannot include a dictionary + * If not, subunions may include dictionaries if each union is not nullable + */ + const typedef = !this.union && defs.unique.get(this.idlType); + const target = + this.union ? this : + (typedef && typedef.type === "typedef") ? typedef.idlType : + undefined; + if (target && this.nullable) { + // do not allow any dictionary + const { reference } = Object(_validators_helpers_js__WEBPACK_IMPORTED_MODULE_4__["idlTypeIncludesDictionary"])(target, defs) || {}; + if (reference) { + const targetToken = (this.union ? reference : this).tokens.base; + const message = `Nullable union cannot include a dictionary type`; + yield Object(_error_js__WEBPACK_IMPORTED_MODULE_3__["validationError"])(targetToken, this, "no-nullable-union-dict", message); + } + } else { + // allow some dictionary + for (const subtype of this.subtype) { + yield* subtype.validate(defs); } } + } +} - function partial() { - if (!consume("partial")) return; - const thing = dictionary(true) || - interface_(true) || - namespace(true) || - error("Partial doesn't apply to anything"); - return thing; - } - - function dictionary(isPartial) { - if (!consume("dictionary")) return; - const name = consume(ID) || error("No name for dictionary"); - const mems = []; - const ret = current = { - type: "dictionary", - name: isPartial ? name.value : sanitize_name(name.value, "dictionary"), - partial: isPartial, - members: mems - }; - if (!isPartial) ret.inheritance = inheritance() || null; - consume("{") || error("Bodyless dictionary"); - while (true) { - if (consume("}")) { - consume(";") || error("Missing semicolon after dictionary"); - return ret; + +/***/ }), +/* 6 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "Base", function() { return Base; }); +// @ts-check + +class Base { + /** + * @param {object} initializer + * @param {Base["source"]} initializer.source + * @param {Base["tokens"]} initializer.tokens + */ + constructor({ source, tokens }) { + Object.defineProperties(this, { + source: { value: source }, + tokens: { value: tokens, writable: true }, + parent: { value: null, writable: true }, + this: { value: this } // useful when escaping from proxy + }); + } + + toJSON() { + const json = { type: undefined, name: undefined, inheritance: undefined }; + let proto = this; + while (proto !== Object.prototype) { + const descMap = Object.getOwnPropertyDescriptors(proto); + for (const [key, value] of Object.entries(descMap)) { + if (value.enumerable || value.get) { + // @ts-ignore - allow indexing here + json[key] = this[key]; } - const ea = extended_attrs(); - const required = consume("required"); - const typ = type_with_extended_attributes("dictionary-type") || error("No type for dictionary member"); - const name = consume(ID) || error("No name for dictionary member"); - const dflt = default_() || null; - if (required && dflt) error("Required member must not have a default"); - const member = { - type: "field", - name: unescape(name.value), - escapedName: name.value, - required: !!required, - idlType: typ, - extAttrs: ea, - default: dflt - }; - ret.members.push(member); - consume(";") || error("Unterminated dictionary member"); } + proto = Object.getPrototypeOf(proto); } + return json; + } +} - function enum_() { - if (!consume("enum")) return; - const name = consume(ID) || error("No name for enum"); - const vals = []; - const ret = current = { - type: "enum", - name: sanitize_name(name.value, "enum"), - values: vals - }; - consume("{") || error("No curly for enum"); - let value_expected = true; - while (true) { - if (consume("}")) { - if (!ret.values.length) error("No value in enum"); - consume(";") || error("No semicolon after enum"); - return ret; - } - else if (!value_expected) { - error("No comma between enum values"); - } - const val = consume(STR) || error("Unexpected value in enum"); - val.value = val.value.slice(1, -1); - // No trivia exposure yet - val.trivia = undefined; - ret.values.push(val); - value_expected = !!consume(","); + +/***/ }), +/* 7 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "idlTypeIncludesDictionary", function() { return idlTypeIncludesDictionary; }); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "dictionaryIncludesRequiredField", function() { return dictionaryIncludesRequiredField; }); +// @ts-check + +/** + * @typedef {import("../productions/dictionary.js").Dictionary} Dictionary + * + * @param {*} idlType + * @param {import("../validator.js").Definitions} defs + * @param {object} [options] + * @param {boolean} [options.useNullableInner] use when the input idlType is nullable and you want to use its inner type + * @return {{ reference: *, dictionary: Dictionary }} the type reference that ultimately includes dictionary. + */ +function idlTypeIncludesDictionary(idlType, defs, { useNullableInner } = {}) { + if (!idlType.union) { + const def = defs.unique.get(idlType.idlType); + if (!def) { + return; + } + if (def.type === "typedef") { + const { typedefIncludesDictionary } = defs.cache; + if (typedefIncludesDictionary.has(def)) { + // Note that this also halts when it met indeterminate state + // to prevent infinite recursion + return typedefIncludesDictionary.get(def); + } + defs.cache.typedefIncludesDictionary.set(def, undefined); // indeterminate state + const result = idlTypeIncludesDictionary(def.idlType, defs); + defs.cache.typedefIncludesDictionary.set(def, result); + if (result) { + return { + reference: idlType, + dictionary: result.dictionary + }; } } - - function typedef() { - if (!consume("typedef")) return; - const ret = { - type: "typedef" + if (def.type === "dictionary" && (useNullableInner || !idlType.nullable)) { + return { + reference: idlType, + dictionary: def }; - ret.idlType = type_with_extended_attributes("typedef-type") || error("No type in typedef"); - const name = consume(ID) || error("No name in typedef"); - ret.name = sanitize_name(name.value, "typedef"); - current = ret; - consume(";") || error("Unterminated typedef"); - return ret; - } - - function implements_() { - const start_position = consume_position; - const target = consume(ID); - if (!target) return; - if (consume("implements")) { - const ret = { - type: "implements", - target: target.value - }; - const imp = consume(ID) || error("Incomplete implements statement"); - ret.implements = imp.value; - consume(";") || error("No terminating ; for implements statement"); - return ret; - } else { - // rollback - unconsume(start_position); + } + } + for (const subtype of idlType.subtype) { + const result = idlTypeIncludesDictionary(subtype, defs); + if (result) { + if (subtype.union) { + return result; } + return { + reference: subtype, + dictionary: result.dictionary + }; } + } +} + +/** + * @param {*} dict dictionary type + * @param {import("../validator.js").Definitions} defs + * @return {boolean} + */ +function dictionaryIncludesRequiredField(dict, defs) { + if (defs.cache.dictionaryIncludesRequiredField.has(dict)) { + return defs.cache.dictionaryIncludesRequiredField.get(dict); + } + defs.cache.dictionaryIncludesRequiredField.set(dict, undefined); // indeterminate + if (dict.inheritance) { + const superdict = defs.unique.get(dict.inheritance); + if (!superdict) { + return true; + } + if (dictionaryIncludesRequiredField(superdict, defs)) { + return true; + } + } + const result = dict.members.some(field => field.required); + defs.cache.dictionaryIncludesRequiredField.set(dict, result); + return result; +} - function includes() { - const start_position = consume_position; - const target = consume(ID); - if (!target) return; - if (consume("includes")) { - const ret = { - type: "includes", - target: target.value - }; - const imp = consume(ID) || error("Incomplete includes statement"); - ret.includes = imp.value; - consume(";") || error("No terminating ; for includes statement"); - return ret; - } else { - // rollback - unconsume(start_position); - } + +/***/ }), +/* 8 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "SimpleExtendedAttribute", function() { return SimpleExtendedAttribute; }); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "ExtendedAttributes", function() { return ExtendedAttributes; }); +/* harmony import */ var _base_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(6); +/* harmony import */ var _array_base_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(9); +/* harmony import */ var _token_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(10); +/* harmony import */ var _helpers_js__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(4); +/* harmony import */ var _error_js__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(3); + + + + + + +/** + * @param {import("../tokeniser").Tokeniser} tokeniser + * @param {string} tokenName + */ +function tokens(tokeniser, tokenName) { + return Object(_helpers_js__WEBPACK_IMPORTED_MODULE_3__["list"])(tokeniser, { + parser: _token_js__WEBPACK_IMPORTED_MODULE_2__["Token"].parser(tokeniser, tokenName), + listName: tokenName + " list" + }); +} + +const extAttrValueSyntax = ["identifier", "decimal", "integer", "string"]; + +const shouldBeLegacyPrefixed = [ + "NoInterfaceObject", + "LenientSetter", + "LenientThis", + "TreatNonObjectAsNull", + "Unforgeable", +]; + +const renamedLegacies = new Map([ + ...shouldBeLegacyPrefixed.map(name => [name, `Legacy${name}`]), + ["NamedConstructor", "LegacyFactoryFunction"], + ["OverrideBuiltins", "LegacyOverrideBuiltIns"], + ["TreatNullAs", "LegacyNullToEmptyString"], +]); + +/** + * This will allow a set of extended attribute values to be parsed. + * @param {import("../tokeniser").Tokeniser} tokeniser + */ +function extAttrListItems(tokeniser) { + for (const syntax of extAttrValueSyntax) { + const toks = tokens(tokeniser, syntax); + if (toks.length) { + return toks; } + } + tokeniser.error(`Expected identifiers, strings, decimals, or integers but none found`); +} + + +class ExtendedAttributeParameters extends _base_js__WEBPACK_IMPORTED_MODULE_0__["Base"] { + /** + * @param {import("../tokeniser").Tokeniser} tokeniser + */ + static parse(tokeniser) { + const tokens = { assign: tokeniser.consume("=") }; + const ret = Object(_helpers_js__WEBPACK_IMPORTED_MODULE_3__["autoParenter"])(new ExtendedAttributeParameters({ source: tokeniser.source, tokens })); + if (tokens.assign) { + tokens.secondaryName = tokeniser.consume(...extAttrValueSyntax); + } + tokens.open = tokeniser.consume("("); + if (tokens.open) { + ret.list = ret.rhsIsList ? + // [Exposed=(Window,Worker)] + extAttrListItems(tokeniser) : + // [LegacyFactoryFunction=Audio(DOMString src)] or [Constructor(DOMString str)] + Object(_helpers_js__WEBPACK_IMPORTED_MODULE_3__["argument_list"])(tokeniser); + tokens.close = tokeniser.consume(")") || tokeniser.error("Unexpected token in extended attribute argument list"); + } else if (ret.hasRhs && !tokens.secondaryName) { + tokeniser.error("No right hand side to extended attribute assignment"); + } + return ret.this; + } - function definition() { - return callback() || - interface_(false) || - partial() || - dictionary(false) || - enum_() || - typedef() || - implements_() || - includes() || - namespace(false); + get rhsIsList() { + return this.tokens.assign && !this.tokens.secondaryName; + } + + get rhsType() { + if (this.rhsIsList) { + return this.list[0].tokens.value.type + "-list"; + } + if (this.tokens.secondaryName) { + return this.tokens.secondaryName.type; + } + return null; + } +} + +class SimpleExtendedAttribute extends _base_js__WEBPACK_IMPORTED_MODULE_0__["Base"] { + /** + * @param {import("../tokeniser").Tokeniser} tokeniser + */ + static parse(tokeniser) { + const name = tokeniser.consume("identifier"); + if (name) { + return new SimpleExtendedAttribute({ + source: tokeniser.source, + tokens: { name }, + params: ExtendedAttributeParameters.parse(tokeniser) + }); } + } - function definitions() { - if (!tokens.length) return []; - const defs = []; - while (true) { - const ea = extended_attrs(); - const def = definition(); - if (!def) { - if (ea.length) error("Stray extended attributes"); - break; - } - def.extAttrs = ea; - defs.push(def); - } - return defs; + constructor({ source, tokens, params }) { + super({ source, tokens }); + params.parent = this; + Object.defineProperty(this, "params", { value: params }); + } + + get type() { + return "extended-attribute"; + } + get name() { + return this.tokens.name.value; + } + get rhs() { + const { rhsType: type, tokens, list } = this.params; + if (!type) { + return null; + } + const value = this.params.rhsIsList ? list : Object(_helpers_js__WEBPACK_IMPORTED_MODULE_3__["unescape"])(tokens.secondaryName.value); + return { type, value }; + } + get arguments() { + const { rhsIsList, list } = this.params; + if (!list || rhsIsList) { + return []; } - const res = definitions(); - if (consume_position < tokens.length) error("Unrecognised tokens"); - return res; + return list; } - const obj = { - parse(str) { - const tokens = tokenise(str); - return parse(tokens); + *validate(defs) { + const { name } = this; + if (name === "LegacyNoInterfaceObject") { + const message = `\`[LegacyNoInterfaceObject]\` extended attribute is an \ +undesirable feature that may be removed from Web IDL in the future. Refer to the \ +[relevant upstream PR](https://github.com/heycam/webidl/pull/609) for more \ +information.`; + yield Object(_error_js__WEBPACK_IMPORTED_MODULE_4__["validationError"])(this.tokens.name, this, "no-nointerfaceobject", message, { level: "warning" }); + } else if (renamedLegacies.has(name)) { + const message = `\`[${name}]\` extended attribute is a legacy feature \ +that is now renamed to \`[${renamedLegacies.get(name)}]\`. Refer to the \ +[relevant upstream PR](https://github.com/heycam/webidl/pull/870) for more \ +information.`; + yield Object(_error_js__WEBPACK_IMPORTED_MODULE_4__["validationError"])(this.tokens.name, this, "renamed-legacy", message, { + level: "warning", + autofix: renameLegacyExtendedAttribute(this) + }); + } + for (const arg of this.arguments) { + yield* arg.validate(defs); + } + } +} + +/** + * @param {SimpleExtendedAttribute} extAttr + */ +function renameLegacyExtendedAttribute(extAttr) { + return () => { + const { name } = extAttr; + extAttr.tokens.name.value = renamedLegacies.get(name); + if (name === "TreatNullAs") { + extAttr.params.tokens = {}; } }; +} + +// Note: we parse something simpler than the official syntax. It's all that ever +// seems to be used +class ExtendedAttributes extends _array_base_js__WEBPACK_IMPORTED_MODULE_1__["ArrayBase"] { + /** + * @param {import("../tokeniser").Tokeniser} tokeniser + */ + static parse(tokeniser) { + const tokens = {}; + tokens.open = tokeniser.consume("["); + if (!tokens.open) return new ExtendedAttributes({}); + const ret = new ExtendedAttributes({ source: tokeniser.source, tokens }); + ret.push(...Object(_helpers_js__WEBPACK_IMPORTED_MODULE_3__["list"])(tokeniser, { + parser: SimpleExtendedAttribute.parse, + listName: "extended attribute" + })); + tokens.close = tokeniser.consume("]") || tokeniser.error("Unexpected closing token of extended attribute"); + if (!ret.length) { + tokeniser.error("Found an empty extended attribute"); + } + if (tokeniser.probe("[")) { + tokeniser.error("Illegal double extended attribute lists, consider merging them"); + } + return ret; + } - if (typeof module !== 'undefined' && typeof module.exports !== 'undefined') { - module.exports = obj; - } else if (typeof define === 'function' && define.amd) { - define([], () => obj); - } else { - (self || window).WebIDL2 = obj; + *validate(defs) { + for (const extAttr of this) { + yield* extAttr.validate(defs); + } } -})(); +} + + +/***/ }), +/* 9 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "ArrayBase", function() { return ArrayBase; }); +// @ts-check + +class ArrayBase extends Array { + constructor({ source, tokens }) { + super(); + Object.defineProperties(this, { + source: { value: source }, + tokens: { value: tokens }, + parent: { value: null, writable: true } + }); + } +} + + +/***/ }), +/* 10 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "Token", function() { return Token; }); +/* harmony import */ var _base_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(6); +/* harmony import */ var _helpers_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(4); +// @ts-check + + + + +class Token extends _base_js__WEBPACK_IMPORTED_MODULE_0__["Base"] { + /** + * @param {import("../tokeniser").Tokeniser} tokeniser + * @param {string} type + */ + static parser(tokeniser, type) { + return () => { + const value = tokeniser.consume(type); + if (value) { + return new Token({ source: tokeniser.source, tokens: { value } }); + } + }; + } + + get value() { + return Object(_helpers_js__WEBPACK_IMPORTED_MODULE_1__["unescape"])(this.tokens.value.value); + } +} + + +/***/ }), +/* 11 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "Argument", function() { return Argument; }); +/* harmony import */ var _base_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(6); +/* harmony import */ var _default_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(12); +/* harmony import */ var _extended_attributes_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(8); +/* harmony import */ var _helpers_js__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(4); +/* harmony import */ var _tokeniser_js__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(2); +/* harmony import */ var _error_js__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(3); +/* harmony import */ var _validators_helpers_js__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(7); +// @ts-check + + + + + + + + + +class Argument extends _base_js__WEBPACK_IMPORTED_MODULE_0__["Base"] { + /** + * @param {import("../tokeniser").Tokeniser} tokeniser + */ + static parse(tokeniser) { + const start_position = tokeniser.position; + /** @type {Base["tokens"]} */ + const tokens = {}; + const ret = Object(_helpers_js__WEBPACK_IMPORTED_MODULE_3__["autoParenter"])(new Argument({ source: tokeniser.source, tokens })); + ret.extAttrs = _extended_attributes_js__WEBPACK_IMPORTED_MODULE_2__["ExtendedAttributes"].parse(tokeniser); + tokens.optional = tokeniser.consume("optional"); + ret.idlType = Object(_helpers_js__WEBPACK_IMPORTED_MODULE_3__["type_with_extended_attributes"])(tokeniser, "argument-type"); + if (!ret.idlType) { + return tokeniser.unconsume(start_position); + } + if (!tokens.optional) { + tokens.variadic = tokeniser.consume("..."); + } + tokens.name = tokeniser.consume("identifier", ..._tokeniser_js__WEBPACK_IMPORTED_MODULE_4__["argumentNameKeywords"]); + if (!tokens.name) { + return tokeniser.unconsume(start_position); + } + ret.default = tokens.optional ? _default_js__WEBPACK_IMPORTED_MODULE_1__["Default"].parse(tokeniser) : null; + return ret.this; + } + + get type() { + return "argument"; + } + get optional() { + return !!this.tokens.optional; + } + get variadic() { + return !!this.tokens.variadic; + } + get name() { + return Object(_helpers_js__WEBPACK_IMPORTED_MODULE_3__["unescape"])(this.tokens.name.value); + } + + /** + * @param {import("../validator.js").Definitions} defs + */ + *validate(defs) { + yield* this.idlType.validate(defs); + const result = Object(_validators_helpers_js__WEBPACK_IMPORTED_MODULE_6__["idlTypeIncludesDictionary"])(this.idlType, defs, { useNullableInner: true }); + if (result) { + if (this.idlType.nullable) { + const message = `Dictionary arguments cannot be nullable.`; + yield Object(_error_js__WEBPACK_IMPORTED_MODULE_5__["validationError"])(this.tokens.name, this, "no-nullable-dict-arg", message); + } else if (!this.optional) { + if (this.parent && !Object(_validators_helpers_js__WEBPACK_IMPORTED_MODULE_6__["dictionaryIncludesRequiredField"])(result.dictionary, defs) && isLastRequiredArgument(this)) { + const message = `Dictionary argument must be optional if it has no required fields`; + yield Object(_error_js__WEBPACK_IMPORTED_MODULE_5__["validationError"])(this.tokens.name, this, "dict-arg-optional", message, { + autofix: autofixDictionaryArgumentOptionality(this) + }); + } + } else if (!this.default) { + const message = `Optional dictionary arguments must have a default value of \`{}\`.`; + yield Object(_error_js__WEBPACK_IMPORTED_MODULE_5__["validationError"])(this.tokens.name, this, "dict-arg-default", message, { + autofix: autofixOptionalDictionaryDefaultValue(this) + }); + } + } + } +} + +/** + * @param {Argument} arg + */ +function isLastRequiredArgument(arg) { + const list = arg.parent.arguments || arg.parent.list; + const index = list.indexOf(arg); + const requiredExists = list.slice(index + 1).some(a => !a.optional); + return !requiredExists; +} + +/** + * @param {Argument} arg + */ +function autofixDictionaryArgumentOptionality(arg) { + return () => { + const firstToken = Object(_helpers_js__WEBPACK_IMPORTED_MODULE_3__["getFirstToken"])(arg.idlType); + arg.tokens.optional = { type: "optional", value: "optional", trivia: firstToken.trivia }; + firstToken.trivia = " "; + autofixOptionalDictionaryDefaultValue(arg)(); + }; +} + +/** + * @param {Argument} arg + */ +function autofixOptionalDictionaryDefaultValue(arg) { + return () => { + arg.default = _default_js__WEBPACK_IMPORTED_MODULE_1__["Default"].parse(new _tokeniser_js__WEBPACK_IMPORTED_MODULE_4__["Tokeniser"](" = {}")); + }; +} + + +/***/ }), +/* 12 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "Default", function() { return Default; }); +/* harmony import */ var _base_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(6); +/* harmony import */ var _helpers_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(4); + + + +class Default extends _base_js__WEBPACK_IMPORTED_MODULE_0__["Base"] { + /** + * @param {import("../tokeniser").Tokeniser} tokeniser + */ + static parse(tokeniser) { + const assign = tokeniser.consume("="); + if (!assign) { + return null; + } + const def = Object(_helpers_js__WEBPACK_IMPORTED_MODULE_1__["const_value"])(tokeniser) || tokeniser.consume("string", "null", "[", "{") || tokeniser.error("No value for default"); + const expression = [def]; + if (def.type === "[") { + const close = tokeniser.consume("]") || tokeniser.error("Default sequence value must be empty"); + expression.push(close); + } else if (def.type === "{") { + const close = tokeniser.consume("}") || tokeniser.error("Default dictionary value must be empty"); + expression.push(close); + } + return new Default({ source: tokeniser.source, tokens: { assign }, expression }); + } + + constructor({ source, tokens, expression }) { + super({ source, tokens }); + expression.parent = this; + Object.defineProperty(this, "expression", { value: expression }); + } + + get type() { + return Object(_helpers_js__WEBPACK_IMPORTED_MODULE_1__["const_data"])(this.expression[0]).type; + } + get value() { + return Object(_helpers_js__WEBPACK_IMPORTED_MODULE_1__["const_data"])(this.expression[0]).value; + } + get negative() { + return Object(_helpers_js__WEBPACK_IMPORTED_MODULE_1__["const_data"])(this.expression[0]).negative; + } +} + + +/***/ }), +/* 13 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "Operation", function() { return Operation; }); +/* harmony import */ var _base_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(6); +/* harmony import */ var _helpers_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(4); +/* harmony import */ var _error_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(3); + + + + +class Operation extends _base_js__WEBPACK_IMPORTED_MODULE_0__["Base"] { + /** + * @typedef {import("../tokeniser.js").Token} Token + * + * @param {import("../tokeniser.js").Tokeniser} tokeniser + * @param {object} [options] + * @param {Token} [options.special] + * @param {Token} [options.regular] + */ + static parse(tokeniser, { special, regular } = {}) { + const tokens = { special }; + const ret = Object(_helpers_js__WEBPACK_IMPORTED_MODULE_1__["autoParenter"])(new Operation({ source: tokeniser.source, tokens })); + if (special && special.value === "stringifier") { + tokens.termination = tokeniser.consume(";"); + if (tokens.termination) { + ret.arguments = []; + return ret; + } + } + if (!special && !regular) { + tokens.special = tokeniser.consume("getter", "setter", "deleter"); + } + ret.idlType = Object(_helpers_js__WEBPACK_IMPORTED_MODULE_1__["return_type"])(tokeniser) || tokeniser.error("Missing return type"); + tokens.name = tokeniser.consume("identifier", "includes"); + tokens.open = tokeniser.consume("(") || tokeniser.error("Invalid operation"); + ret.arguments = Object(_helpers_js__WEBPACK_IMPORTED_MODULE_1__["argument_list"])(tokeniser); + tokens.close = tokeniser.consume(")") || tokeniser.error("Unterminated operation"); + tokens.termination = tokeniser.consume(";") || tokeniser.error("Unterminated operation, expected `;`"); + return ret.this; + } + + get type() { + return "operation"; + } + get name() { + const { name } = this.tokens; + if (!name) { + return ""; + } + return Object(_helpers_js__WEBPACK_IMPORTED_MODULE_1__["unescape"])(name.value); + } + get special() { + if (!this.tokens.special) { + return ""; + } + return this.tokens.special.value; + } + + *validate(defs) { + if (!this.name && ["", "static"].includes(this.special)) { + const message = `Regular or static operations must have both a return type and an identifier.`; + yield Object(_error_js__WEBPACK_IMPORTED_MODULE_2__["validationError"])(this.tokens.open, this, "incomplete-op", message); + } + if (this.idlType) { + yield* this.idlType.validate(defs); + } + for (const argument of this.arguments) { + yield* argument.validate(defs); + } + } +} + + +/***/ }), +/* 14 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "Attribute", function() { return Attribute; }); +/* harmony import */ var _base_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(6); +/* harmony import */ var _helpers_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(4); + + + +class Attribute extends _base_js__WEBPACK_IMPORTED_MODULE_0__["Base"] { + /** + * @param {import("../tokeniser.js").Tokeniser} tokeniser + */ + static parse(tokeniser, { special, noInherit = false, readonly = false } = {}) { + const start_position = tokeniser.position; + const tokens = { special }; + const ret = Object(_helpers_js__WEBPACK_IMPORTED_MODULE_1__["autoParenter"])(new Attribute({ source: tokeniser.source, tokens })); + if (!special && !noInherit) { + tokens.special = tokeniser.consume("inherit"); + } + if (ret.special === "inherit" && tokeniser.probe("readonly")) { + tokeniser.error("Inherited attributes cannot be read-only"); + } + tokens.readonly = tokeniser.consume("readonly"); + if (readonly && !tokens.readonly && tokeniser.probe("attribute")) { + tokeniser.error("Attributes must be readonly in this context"); + } + tokens.base = tokeniser.consume("attribute"); + if (!tokens.base) { + tokeniser.unconsume(start_position); + return; + } + ret.idlType = Object(_helpers_js__WEBPACK_IMPORTED_MODULE_1__["type_with_extended_attributes"])(tokeniser, "attribute-type") || tokeniser.error("Attribute lacks a type"); + switch (ret.idlType.generic) { + case "sequence": + case "record": tokeniser.error(`Attributes cannot accept ${ret.idlType.generic} types`); + } + tokens.name = tokeniser.consume("identifier", "async", "required") || tokeniser.error("Attribute lacks a name"); + tokens.termination = tokeniser.consume(";") || tokeniser.error("Unterminated attribute, expected `;`"); + return ret.this; + } + + get type() { + return "attribute"; + } + get special() { + if (!this.tokens.special) { + return ""; + } + return this.tokens.special.value; + } + get readonly() { + return !!this.tokens.readonly; + } + get name() { + return Object(_helpers_js__WEBPACK_IMPORTED_MODULE_1__["unescape"])(this.tokens.name.value); + } + + *validate(defs) { + yield* this.extAttrs.validate(defs); + yield* this.idlType.validate(defs); + } +} + + +/***/ }), +/* 15 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "Enum", function() { return Enum; }); +/* harmony import */ var _helpers_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(4); +/* harmony import */ var _token_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(10); +/* harmony import */ var _base_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(6); + + + + +class EnumValue extends _token_js__WEBPACK_IMPORTED_MODULE_1__["Token"] { + /** + * @param {import("../tokeniser").Tokeniser} tokeniser + */ + static parse(tokeniser) { + const value = tokeniser.consume("string"); + if (value) { + return new EnumValue({ source: tokeniser.source, tokens: { value } }); + } + } + + get type() { + return "enum-value"; + } + get value() { + return super.value.slice(1, -1); + } +} + +class Enum extends _base_js__WEBPACK_IMPORTED_MODULE_2__["Base"] { + /** + * @param {import("../tokeniser").Tokeniser} tokeniser + */ + static parse(tokeniser) { + /** @type {Base["tokens"]} */ + const tokens = {}; + tokens.base = tokeniser.consume("enum"); + if (!tokens.base) { + return; + } + tokens.name = tokeniser.consume("identifier") || tokeniser.error("No name for enum"); + const ret = Object(_helpers_js__WEBPACK_IMPORTED_MODULE_0__["autoParenter"])(new Enum({ source: tokeniser.source, tokens })); + tokeniser.current = ret.this; + tokens.open = tokeniser.consume("{") || tokeniser.error("Bodyless enum"); + ret.values = Object(_helpers_js__WEBPACK_IMPORTED_MODULE_0__["list"])(tokeniser, { + parser: EnumValue.parse, + allowDangler: true, + listName: "enumeration" + }); + if (tokeniser.probe("string")) { + tokeniser.error("No comma between enum values"); + } + tokens.close = tokeniser.consume("}") || tokeniser.error("Unexpected value in enum"); + if (!ret.values.length) { + tokeniser.error("No value in enum"); + } + tokens.termination = tokeniser.consume(";") || tokeniser.error("No semicolon after enum"); + return ret.this; + } + + get type() { + return "enum"; + } + get name() { + return Object(_helpers_js__WEBPACK_IMPORTED_MODULE_0__["unescape"])(this.tokens.name.value); + } +} + + +/***/ }), +/* 16 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "Includes", function() { return Includes; }); +/* harmony import */ var _base_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(6); +/* harmony import */ var _helpers_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(4); +// @ts-check + + + + +class Includes extends _base_js__WEBPACK_IMPORTED_MODULE_0__["Base"] { + /** + * @param {import("../tokeniser").Tokeniser} tokeniser + */ + static parse(tokeniser) { + const target = tokeniser.consume("identifier"); + if (!target) { + return; + } + const tokens = { target }; + tokens.includes = tokeniser.consume("includes"); + if (!tokens.includes) { + tokeniser.unconsume(target.index); + return; + } + tokens.mixin = tokeniser.consume("identifier") || tokeniser.error("Incomplete includes statement"); + tokens.termination = tokeniser.consume(";") || tokeniser.error("No terminating ; for includes statement"); + return new Includes({ source: tokeniser.source, tokens }); + } + + get type() { + return "includes"; + } + get target() { + return Object(_helpers_js__WEBPACK_IMPORTED_MODULE_1__["unescape"])(this.tokens.target.value); + } + get includes() { + return Object(_helpers_js__WEBPACK_IMPORTED_MODULE_1__["unescape"])(this.tokens.mixin.value); + } +} + + +/***/ }), +/* 17 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "Typedef", function() { return Typedef; }); +/* harmony import */ var _base_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(6); +/* harmony import */ var _helpers_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(4); + + + +class Typedef extends _base_js__WEBPACK_IMPORTED_MODULE_0__["Base"] { + /** + * @param {import("../tokeniser").Tokeniser} tokeniser + */ + static parse(tokeniser) { + /** @type {Base["tokens"]} */ + const tokens = {}; + const ret = Object(_helpers_js__WEBPACK_IMPORTED_MODULE_1__["autoParenter"])(new Typedef({ source: tokeniser.source, tokens })); + tokens.base = tokeniser.consume("typedef"); + if (!tokens.base) { + return; + } + ret.idlType = Object(_helpers_js__WEBPACK_IMPORTED_MODULE_1__["type_with_extended_attributes"])(tokeniser, "typedef-type") || tokeniser.error("Typedef lacks a type"); + tokens.name = tokeniser.consume("identifier") || tokeniser.error("Typedef lacks a name"); + tokeniser.current = ret.this; + tokens.termination = tokeniser.consume(";") || tokeniser.error("Unterminated typedef, expected `;`"); + return ret.this; + } + + get type() { + return "typedef"; + } + get name() { + return Object(_helpers_js__WEBPACK_IMPORTED_MODULE_1__["unescape"])(this.tokens.name.value); + } + + *validate(defs) { + yield* this.idlType.validate(defs); + } +} + + +/***/ }), +/* 18 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "CallbackFunction", function() { return CallbackFunction; }); +/* harmony import */ var _base_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(6); +/* harmony import */ var _helpers_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(4); + + + +class CallbackFunction extends _base_js__WEBPACK_IMPORTED_MODULE_0__["Base"] { + /** + * @param {import("../tokeniser.js").Tokeniser} tokeniser + */ + static parse(tokeniser, base) { + const tokens = { base }; + const ret = Object(_helpers_js__WEBPACK_IMPORTED_MODULE_1__["autoParenter"])(new CallbackFunction({ source: tokeniser.source, tokens })); + tokens.name = tokeniser.consume("identifier") || tokeniser.error("Callback lacks a name"); + tokeniser.current = ret.this; + tokens.assign = tokeniser.consume("=") || tokeniser.error("Callback lacks an assignment"); + ret.idlType = Object(_helpers_js__WEBPACK_IMPORTED_MODULE_1__["return_type"])(tokeniser) || tokeniser.error("Callback lacks a return type"); + tokens.open = tokeniser.consume("(") || tokeniser.error("Callback lacks parentheses for arguments"); + ret.arguments = Object(_helpers_js__WEBPACK_IMPORTED_MODULE_1__["argument_list"])(tokeniser); + tokens.close = tokeniser.consume(")") || tokeniser.error("Unterminated callback"); + tokens.termination = tokeniser.consume(";") || tokeniser.error("Unterminated callback, expected `;`"); + return ret.this; + } + + get type() { + return "callback"; + } + get name() { + return Object(_helpers_js__WEBPACK_IMPORTED_MODULE_1__["unescape"])(this.tokens.name.value); + } + + *validate(defs) { + yield* this.extAttrs.validate(defs); + yield* this.idlType.validate(defs); + } +} + + +/***/ }), +/* 19 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "Interface", function() { return Interface; }); +/* harmony import */ var _container_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(20); +/* harmony import */ var _attribute_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(14); +/* harmony import */ var _operation_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(13); +/* harmony import */ var _constant_js__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(21); +/* harmony import */ var _iterable_js__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(22); +/* harmony import */ var _helpers_js__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(4); +/* harmony import */ var _error_js__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(3); +/* harmony import */ var _validators_interface_js__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(23); +/* harmony import */ var _constructor_js__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__(24); +/* harmony import */ var _tokeniser_js__WEBPACK_IMPORTED_MODULE_9__ = __webpack_require__(2); +/* harmony import */ var _extended_attributes_js__WEBPACK_IMPORTED_MODULE_10__ = __webpack_require__(8); + + + + + + + + + + + + +/** + * @param {import("../tokeniser").Tokeniser} tokeniser + */ +function static_member(tokeniser) { + const special = tokeniser.consume("static"); + if (!special) return; + const member = _attribute_js__WEBPACK_IMPORTED_MODULE_1__["Attribute"].parse(tokeniser, { special }) || + _operation_js__WEBPACK_IMPORTED_MODULE_2__["Operation"].parse(tokeniser, { special }) || + tokeniser.error("No body in static member"); + return member; +} + +class Interface extends _container_js__WEBPACK_IMPORTED_MODULE_0__["Container"] { + /** + * @param {import("../tokeniser").Tokeniser} tokeniser + */ + static parse(tokeniser, base, { partial = null } = {}) { + const tokens = { partial, base }; + return _container_js__WEBPACK_IMPORTED_MODULE_0__["Container"].parse(tokeniser, new Interface({ source: tokeniser.source, tokens }), { + type: "interface", + inheritable: !partial, + allowedMembers: [ + [_constant_js__WEBPACK_IMPORTED_MODULE_3__["Constant"].parse], + [_constructor_js__WEBPACK_IMPORTED_MODULE_8__["Constructor"].parse], + [static_member], + [_helpers_js__WEBPACK_IMPORTED_MODULE_5__["stringifier"]], + [_iterable_js__WEBPACK_IMPORTED_MODULE_4__["IterableLike"].parse], + [_attribute_js__WEBPACK_IMPORTED_MODULE_1__["Attribute"].parse], + [_operation_js__WEBPACK_IMPORTED_MODULE_2__["Operation"].parse] + ] + }); + } + + get type() { + return "interface"; + } + + *validate(defs) { + yield* this.extAttrs.validate(defs); + if ( + !this.partial && + this.extAttrs.every(extAttr => extAttr.name !== "Exposed") && + this.extAttrs.every(extAttr => extAttr.name !== "LegacyNoInterfaceObject") + ) { + const message = `Interfaces must have \`[Exposed]\` extended attribute. \ +To fix, add, for example, \`[Exposed=Window]\`. Please also consider carefully \ +if your interface should also be exposed in a Worker scope. Refer to the \ +[WebIDL spec section on Exposed](https://heycam.github.io/webidl/#Exposed) \ +for more information.`; + yield Object(_error_js__WEBPACK_IMPORTED_MODULE_6__["validationError"])(this.tokens.name, this, "require-exposed", message, { + autofix: Object(_helpers_js__WEBPACK_IMPORTED_MODULE_5__["autofixAddExposedWindow"])(this) + }); + } + const oldConstructors = this.extAttrs.filter(extAttr => extAttr.name === "Constructor"); + for (const constructor of oldConstructors) { + const message = `Constructors should now be represented as a \`constructor()\` operation on the interface \ +instead of \`[Constructor]\` extended attribute. Refer to the \ +[WebIDL spec section on constructor operations](https://heycam.github.io/webidl/#idl-constructors) \ +for more information.`; + yield Object(_error_js__WEBPACK_IMPORTED_MODULE_6__["validationError"])(constructor.tokens.name, this, "constructor-member", message, { + autofix: autofixConstructor(this, constructor) + }); + } + + const isGlobal = this.extAttrs.some(extAttr => extAttr.name === "Global"); + if (isGlobal) { + const factoryFunctions = this.extAttrs.filter(extAttr => extAttr.name === "LegacyFactoryFunction"); + for (const named of factoryFunctions) { + const message = `Interfaces marked as \`[Global]\` cannot have factory functions.`; + yield Object(_error_js__WEBPACK_IMPORTED_MODULE_6__["validationError"])(named.tokens.name, this, "no-constructible-global", message); + } + + const constructors = this.members.filter(member => member.type === "constructor"); + for (const named of constructors) { + const message = `Interfaces marked as \`[Global]\` cannot have constructors.`; + yield Object(_error_js__WEBPACK_IMPORTED_MODULE_6__["validationError"])(named.tokens.base, this, "no-constructible-global", message); + } + } + + yield* super.validate(defs); + if (!this.partial) { + yield* Object(_validators_interface_js__WEBPACK_IMPORTED_MODULE_7__["checkInterfaceMemberDuplication"])(defs, this); + } + } +} + +function autofixConstructor(interfaceDef, constructorExtAttr) { + interfaceDef = Object(_helpers_js__WEBPACK_IMPORTED_MODULE_5__["autoParenter"])(interfaceDef); + return () => { + const indentation = Object(_helpers_js__WEBPACK_IMPORTED_MODULE_5__["getLastIndentation"])(interfaceDef.extAttrs.tokens.open.trivia); + const memberIndent = interfaceDef.members.length ? + Object(_helpers_js__WEBPACK_IMPORTED_MODULE_5__["getLastIndentation"])(Object(_helpers_js__WEBPACK_IMPORTED_MODULE_5__["getFirstToken"])(interfaceDef.members[0]).trivia) : + Object(_helpers_js__WEBPACK_IMPORTED_MODULE_5__["getMemberIndentation"])(indentation); + const constructorOp = _constructor_js__WEBPACK_IMPORTED_MODULE_8__["Constructor"].parse(new _tokeniser_js__WEBPACK_IMPORTED_MODULE_9__["Tokeniser"](`\n${memberIndent}constructor();`)); + constructorOp.extAttrs = new _extended_attributes_js__WEBPACK_IMPORTED_MODULE_10__["ExtendedAttributes"]({}); + Object(_helpers_js__WEBPACK_IMPORTED_MODULE_5__["autoParenter"])(constructorOp).arguments = constructorExtAttr.arguments; + + const existingIndex = Object(_helpers_js__WEBPACK_IMPORTED_MODULE_5__["findLastIndex"])(interfaceDef.members, m => m.type === "constructor"); + interfaceDef.members.splice(existingIndex + 1, 0, constructorOp); + + const { close } = interfaceDef.tokens; + if (!close.trivia.includes("\n")) { + close.trivia += `\n${indentation}`; + } + + const { extAttrs } = interfaceDef; + const index = extAttrs.indexOf(constructorExtAttr); + const removed = extAttrs.splice(index, 1); + if (!extAttrs.length) { + extAttrs.tokens.open = extAttrs.tokens.close = undefined; + } else if (extAttrs.length === index) { + extAttrs[index - 1].tokens.separator = undefined; + } else if (!extAttrs[index].tokens.name.trivia.trim()) { + extAttrs[index].tokens.name.trivia = removed[0].tokens.name.trivia; + } + }; +} + + +/***/ }), +/* 20 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "Container", function() { return Container; }); +/* harmony import */ var _base_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(6); +/* harmony import */ var _extended_attributes_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(8); +/* harmony import */ var _helpers_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(4); + + + + +/** + * @param {import("../tokeniser.js").Tokeniser} tokeniser + */ +function inheritance(tokeniser) { + const colon = tokeniser.consume(":"); + if (!colon) { + return {}; + } + const inheritance = tokeniser.consume("identifier") || tokeniser.error("Inheritance lacks a type"); + return { colon, inheritance }; +} + +class Container extends _base_js__WEBPACK_IMPORTED_MODULE_0__["Base"] { + /** + * @template T + * @param {import("../tokeniser.js").Tokeniser} tokeniser + * @param {T} instance + * @param {*} args + */ + static parse(tokeniser, instance, { type, inheritable, allowedMembers }) { + const { tokens } = instance; + tokens.name = tokeniser.consume("identifier") || tokeniser.error(`Missing name in ${instance.type}`); + tokeniser.current = instance; + instance = Object(_helpers_js__WEBPACK_IMPORTED_MODULE_2__["autoParenter"])(instance); + if (inheritable) { + Object.assign(tokens, inheritance(tokeniser)); + } + tokens.open = tokeniser.consume("{") || tokeniser.error(`Bodyless ${type}`); + instance.members = []; + while (true) { + tokens.close = tokeniser.consume("}"); + if (tokens.close) { + tokens.termination = tokeniser.consume(";") || tokeniser.error(`Missing semicolon after ${type}`); + return instance.this; + } + const ea = _extended_attributes_js__WEBPACK_IMPORTED_MODULE_1__["ExtendedAttributes"].parse(tokeniser); + let mem; + for (const [parser, ...args] of allowedMembers) { + mem = Object(_helpers_js__WEBPACK_IMPORTED_MODULE_2__["autoParenter"])(parser(tokeniser, ...args)); + if (mem) { + break; + } + } + if (!mem) { + tokeniser.error("Unknown member"); + } + mem.extAttrs = ea; + instance.members.push(mem.this); + } + } + + get partial() { + return !!this.tokens.partial; + } + get name() { + return Object(_helpers_js__WEBPACK_IMPORTED_MODULE_2__["unescape"])(this.tokens.name.value); + } + get inheritance() { + if (!this.tokens.inheritance) { + return null; + } + return Object(_helpers_js__WEBPACK_IMPORTED_MODULE_2__["unescape"])(this.tokens.inheritance.value); + } + + *validate(defs) { + for (const member of this.members) { + if (member.validate) { + yield* member.validate(defs); + } + } + } + } + + +/***/ }), +/* 21 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "Constant", function() { return Constant; }); +/* harmony import */ var _base_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(6); +/* harmony import */ var _type_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(5); +/* harmony import */ var _helpers_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(4); + + + + +class Constant extends _base_js__WEBPACK_IMPORTED_MODULE_0__["Base"] { + /** + * @param {import("../tokeniser.js").Tokeniser} tokeniser + */ + static parse(tokeniser) { + /** @type {Base["tokens"]} */ + const tokens = {}; + tokens.base = tokeniser.consume("const"); + if (!tokens.base) { + return; + } + let idlType = Object(_helpers_js__WEBPACK_IMPORTED_MODULE_2__["primitive_type"])(tokeniser); + if (!idlType) { + const base = tokeniser.consume("identifier") || tokeniser.error("Const lacks a type"); + idlType = new _type_js__WEBPACK_IMPORTED_MODULE_1__["Type"]({ source: tokeniser.source, tokens: { base } }); + } + if (tokeniser.probe("?")) { + tokeniser.error("Unexpected nullable constant type"); + } + idlType.type = "const-type"; + tokens.name = tokeniser.consume("identifier") || tokeniser.error("Const lacks a name"); + tokens.assign = tokeniser.consume("=") || tokeniser.error("Const lacks value assignment"); + tokens.value = Object(_helpers_js__WEBPACK_IMPORTED_MODULE_2__["const_value"])(tokeniser) || tokeniser.error("Const lacks a value"); + tokens.termination = tokeniser.consume(";") || tokeniser.error("Unterminated const, expected `;`"); + const ret = new Constant({ source: tokeniser.source, tokens }); + Object(_helpers_js__WEBPACK_IMPORTED_MODULE_2__["autoParenter"])(ret).idlType = idlType; + return ret; + } + + get type() { + return "const"; + } + get name() { + return Object(_helpers_js__WEBPACK_IMPORTED_MODULE_2__["unescape"])(this.tokens.name.value); + } + get value() { + return Object(_helpers_js__WEBPACK_IMPORTED_MODULE_2__["const_data"])(this.tokens.value); + } +} + + +/***/ }), +/* 22 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "IterableLike", function() { return IterableLike; }); +/* harmony import */ var _base_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(6); +/* harmony import */ var _helpers_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(4); + + + +class IterableLike extends _base_js__WEBPACK_IMPORTED_MODULE_0__["Base"] { + /** + * @param {import("../tokeniser.js").Tokeniser} tokeniser + */ + static parse(tokeniser) { + const start_position = tokeniser.position; + const tokens = {}; + const ret = Object(_helpers_js__WEBPACK_IMPORTED_MODULE_1__["autoParenter"])(new IterableLike({ source: tokeniser.source, tokens })); + tokens.readonly = tokeniser.consume("readonly"); + if (!tokens.readonly) { + tokens.async = tokeniser.consume("async"); + } + tokens.base = + tokens.readonly ? tokeniser.consume("maplike", "setlike") : + tokens.async ? tokeniser.consume("iterable") : + tokeniser.consume("iterable", "maplike", "setlike"); + if (!tokens.base) { + tokeniser.unconsume(start_position); + return; + } + + const { type } = ret; + const secondTypeRequired = type === "maplike"; + const secondTypeAllowed = secondTypeRequired || type === "iterable"; + const argumentAllowed = ret.async && type === "iterable"; + + tokens.open = tokeniser.consume("<") || tokeniser.error(`Missing less-than sign \`<\` in ${type} declaration`); + const first = Object(_helpers_js__WEBPACK_IMPORTED_MODULE_1__["type_with_extended_attributes"])(tokeniser) || tokeniser.error(`Missing a type argument in ${type} declaration`); + ret.idlType = [first]; + ret.arguments = []; + + if (secondTypeAllowed) { + first.tokens.separator = tokeniser.consume(","); + if (first.tokens.separator) { + ret.idlType.push(Object(_helpers_js__WEBPACK_IMPORTED_MODULE_1__["type_with_extended_attributes"])(tokeniser)); + } + else if (secondTypeRequired) { + tokeniser.error(`Missing second type argument in ${type} declaration`); + } + } + + tokens.close = tokeniser.consume(">") || tokeniser.error(`Missing greater-than sign \`>\` in ${type} declaration`); + + if (tokeniser.probe("(")) { + if (argumentAllowed) { + tokens.argsOpen = tokeniser.consume("("); + ret.arguments.push(...Object(_helpers_js__WEBPACK_IMPORTED_MODULE_1__["argument_list"])(tokeniser)); + tokens.argsClose = tokeniser.consume(")") || tokeniser.error("Unterminated async iterable argument list"); + } else { + tokeniser.error(`Arguments are only allowed for \`async iterable\``); + } + } + + tokens.termination = tokeniser.consume(";") || tokeniser.error(`Missing semicolon after ${type} declaration`); + + return ret.this; + } + + get type() { + return this.tokens.base.value; + } + get readonly() { + return !!this.tokens.readonly; + } + get async() { + return !!this.tokens.async; + } + + *validate(defs) { + for (const type of this.idlType) { + yield* type.validate(defs); + } + for (const argument of this.arguments) { + yield* argument.validate(defs); + } + } +} + + +/***/ }), +/* 23 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "checkInterfaceMemberDuplication", function() { return checkInterfaceMemberDuplication; }); +/* harmony import */ var _error_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(3); +// @ts-check + + + +function* checkInterfaceMemberDuplication(defs, i) { + const opNames = new Set(getOperations(i).map(op => op.name)); + const partials = defs.partials.get(i.name) || []; + const mixins = defs.mixinMap.get(i.name) || []; + for (const ext of [...partials, ...mixins]) { + const additions = getOperations(ext); + yield* forEachExtension(additions, opNames, ext, i); + for (const addition of additions) { + opNames.add(addition.name); + } + } + + function* forEachExtension(additions, existings, ext, base) { + for (const addition of additions) { + const { name } = addition; + if (name && existings.has(name)) { + const message = `The operation "${name}" has already been defined for the base interface "${base.name}" either in itself or in a mixin`; + yield Object(_error_js__WEBPACK_IMPORTED_MODULE_0__["validationError"])(addition.tokens.name, ext, "no-cross-overload", message); + } + } + } + + function getOperations(i) { + return i.members + .filter(({type}) => type === "operation"); + } +} + + +/***/ }), +/* 24 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "Constructor", function() { return Constructor; }); +/* harmony import */ var _base_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(6); +/* harmony import */ var _helpers_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(4); + + + +class Constructor extends _base_js__WEBPACK_IMPORTED_MODULE_0__["Base"] { + /** + * @param {import("../tokeniser").Tokeniser} tokeniser + */ + static parse(tokeniser) { + const base = tokeniser.consume("constructor"); + if (!base) { + return; + } + /** @type {Base["tokens"]} */ + const tokens = { base }; + tokens.open = tokeniser.consume("(") || tokeniser.error("No argument list in constructor"); + const args = Object(_helpers_js__WEBPACK_IMPORTED_MODULE_1__["argument_list"])(tokeniser); + tokens.close = tokeniser.consume(")") || tokeniser.error("Unterminated constructor"); + tokens.termination = tokeniser.consume(";") || tokeniser.error("No semicolon after constructor"); + const ret = new Constructor({ source: tokeniser.source, tokens }); + Object(_helpers_js__WEBPACK_IMPORTED_MODULE_1__["autoParenter"])(ret).arguments = args; + return ret; + } + + get type() { + return "constructor"; + } + + *validate(defs) { + if (this.idlType) { + yield* this.idlType.validate(defs); + } + for (const argument of this.arguments) { + yield* argument.validate(defs); + } + } +} + + +/***/ }), +/* 25 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "Mixin", function() { return Mixin; }); +/* harmony import */ var _container_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(20); +/* harmony import */ var _constant_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(21); +/* harmony import */ var _attribute_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(14); +/* harmony import */ var _operation_js__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(13); +/* harmony import */ var _helpers_js__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(4); + + + + + + +class Mixin extends _container_js__WEBPACK_IMPORTED_MODULE_0__["Container"] { + /** + * @typedef {import("../tokeniser.js").Token} Token + * + * @param {import("../tokeniser.js").Tokeniser} tokeniser + * @param {Token} base + * @param {object} [options] + * @param {Token} [options.partial] + */ + static parse(tokeniser, base, { partial } = {}) { + const tokens = { partial, base }; + tokens.mixin = tokeniser.consume("mixin"); + if (!tokens.mixin) { + return; + } + return _container_js__WEBPACK_IMPORTED_MODULE_0__["Container"].parse(tokeniser, new Mixin({ source: tokeniser.source, tokens }), { + type: "interface mixin", + allowedMembers: [ + [_constant_js__WEBPACK_IMPORTED_MODULE_1__["Constant"].parse], + [_helpers_js__WEBPACK_IMPORTED_MODULE_4__["stringifier"]], + [_attribute_js__WEBPACK_IMPORTED_MODULE_2__["Attribute"].parse, { noInherit: true }], + [_operation_js__WEBPACK_IMPORTED_MODULE_3__["Operation"].parse, { regular: true }] + ] + }); + } + + get type() { + return "interface mixin"; + } +} + + +/***/ }), +/* 26 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "Dictionary", function() { return Dictionary; }); +/* harmony import */ var _container_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(20); +/* harmony import */ var _field_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(27); +// @ts-check + + + + +class Dictionary extends _container_js__WEBPACK_IMPORTED_MODULE_0__["Container"] { + /** + * @param {import("../tokeniser").Tokeniser} tokeniser + * @param {object} [options] + * @param {import("../tokeniser.js").Token} [options.partial] + */ + static parse(tokeniser, { partial } = {}) { + const tokens = { partial }; + tokens.base = tokeniser.consume("dictionary"); + if (!tokens.base) { + return; + } + return _container_js__WEBPACK_IMPORTED_MODULE_0__["Container"].parse(tokeniser, new Dictionary({ source: tokeniser.source, tokens }), { + type: "dictionary", + inheritable: !partial, + allowedMembers: [ + [_field_js__WEBPACK_IMPORTED_MODULE_1__["Field"].parse], + ] + }); + } + + get type() { + return "dictionary"; + } +} + + +/***/ }), +/* 27 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "Field", function() { return Field; }); +/* harmony import */ var _base_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(6); +/* harmony import */ var _helpers_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(4); +/* harmony import */ var _extended_attributes_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(8); +/* harmony import */ var _default_js__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(12); + + + + + +class Field extends _base_js__WEBPACK_IMPORTED_MODULE_0__["Base"] { + /** + * @param {import("../tokeniser").Tokeniser} tokeniser + */ + static parse(tokeniser) { + /** @type {Base["tokens"]} */ + const tokens = {}; + const ret = Object(_helpers_js__WEBPACK_IMPORTED_MODULE_1__["autoParenter"])(new Field({ source: tokeniser.source, tokens })); + ret.extAttrs = _extended_attributes_js__WEBPACK_IMPORTED_MODULE_2__["ExtendedAttributes"].parse(tokeniser); + tokens.required = tokeniser.consume("required"); + ret.idlType = Object(_helpers_js__WEBPACK_IMPORTED_MODULE_1__["type_with_extended_attributes"])(tokeniser, "dictionary-type") || tokeniser.error("Dictionary member lacks a type"); + tokens.name = tokeniser.consume("identifier") || tokeniser.error("Dictionary member lacks a name"); + ret.default = _default_js__WEBPACK_IMPORTED_MODULE_3__["Default"].parse(tokeniser); + if (tokens.required && ret.default) tokeniser.error("Required member must not have a default"); + tokens.termination = tokeniser.consume(";") || tokeniser.error("Unterminated dictionary member, expected `;`"); + return ret.this; + } + + get type() { + return "field"; + } + get name() { + return Object(_helpers_js__WEBPACK_IMPORTED_MODULE_1__["unescape"])(this.tokens.name.value); + } + get required() { + return !!this.tokens.required; + } + + *validate(defs) { + yield* this.idlType.validate(defs); + } +} + + +/***/ }), +/* 28 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "Namespace", function() { return Namespace; }); +/* harmony import */ var _container_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(20); +/* harmony import */ var _attribute_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(14); +/* harmony import */ var _operation_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(13); +/* harmony import */ var _error_js__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(3); +/* harmony import */ var _helpers_js__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(4); + + + + + + +class Namespace extends _container_js__WEBPACK_IMPORTED_MODULE_0__["Container"] { + /** + * @param {import("../tokeniser").Tokeniser} tokeniser + * @param {object} [options] + * @param {import("../tokeniser.js").Token} [options.partial] + */ + static parse(tokeniser, { partial } = {}) { + const tokens = { partial }; + tokens.base = tokeniser.consume("namespace"); + if (!tokens.base) { + return; + } + return _container_js__WEBPACK_IMPORTED_MODULE_0__["Container"].parse(tokeniser, new Namespace({ source: tokeniser.source, tokens }), { + type: "namespace", + allowedMembers: [ + [_attribute_js__WEBPACK_IMPORTED_MODULE_1__["Attribute"].parse, { noInherit: true, readonly: true }], + [_operation_js__WEBPACK_IMPORTED_MODULE_2__["Operation"].parse, { regular: true }] + ] + }); + } + + get type() { + return "namespace"; + } + + *validate(defs) { + if (!this.partial && this.extAttrs.every(extAttr => extAttr.name !== "Exposed")) { + const message = `Namespaces must have [Exposed] extended attribute. \ +To fix, add, for example, [Exposed=Window]. Please also consider carefully \ +if your namespace should also be exposed in a Worker scope. Refer to the \ +[WebIDL spec section on Exposed](https://heycam.github.io/webidl/#Exposed) \ +for more information.`; + yield Object(_error_js__WEBPACK_IMPORTED_MODULE_3__["validationError"])(this.tokens.name, this, "require-exposed", message, { + autofix: Object(_helpers_js__WEBPACK_IMPORTED_MODULE_4__["autofixAddExposedWindow"])(this) + }); + } + yield* super.validate(defs); + } +} + + +/***/ }), +/* 29 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "CallbackInterface", function() { return CallbackInterface; }); +/* harmony import */ var _container_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(20); +/* harmony import */ var _operation_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(13); +/* harmony import */ var _constant_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(21); +// @ts-check + + + + + +class CallbackInterface extends _container_js__WEBPACK_IMPORTED_MODULE_0__["Container"] { + /** + * @param {import("../tokeniser").Tokeniser} tokeniser + */ + static parse(tokeniser, callback, { partial = null } = {}) { + const tokens = { callback }; + tokens.base = tokeniser.consume("interface"); + if (!tokens.base) { + return; + } + return _container_js__WEBPACK_IMPORTED_MODULE_0__["Container"].parse(tokeniser, new CallbackInterface({ source: tokeniser.source, tokens }), { + type: "callback interface", + inheritable: !partial, + allowedMembers: [ + [_constant_js__WEBPACK_IMPORTED_MODULE_2__["Constant"].parse], + [_operation_js__WEBPACK_IMPORTED_MODULE_1__["Operation"].parse, { regular: true }] + ] + }); + } + + get type() { + return "callback interface"; + } +} + + +/***/ }), +/* 30 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "write", function() { return write; }); + + +function noop(arg) { + return arg; +} + +const templates = { + wrap: items => items.join(""), + trivia: noop, + name: noop, + reference: noop, + type: noop, + generic: noop, + nameless: noop, + inheritance: noop, + definition: noop, + extendedAttribute: noop, + extendedAttributeReference: noop +}; + +function write(ast, { templates: ts = templates } = {}) { + ts = Object.assign({}, templates, ts); + + function reference(raw, { unescaped, context }) { + if (!unescaped) { + unescaped = raw.startsWith("_") ? raw.slice(1) : raw; + } + return ts.reference(raw, unescaped, context); + } + + function token(t, wrapper = noop, ...args) { + if (!t) { + return ""; + } + const value = wrapper(t.value, ...args); + return ts.wrap([ts.trivia(t.trivia), value]); + } + + function reference_token(t, context) { + return token(t, reference, { context }); + } + + function name_token(t, arg) { + return token(t, ts.name, arg); + } + + function type_body(it) { + if (it.union || it.generic) { + return ts.wrap([ + token(it.tokens.base, ts.generic), + token(it.tokens.open), + ...it.subtype.map(type), + token(it.tokens.close) + ]); + } + const firstToken = it.tokens.prefix || it.tokens.base; + const prefix = it.tokens.prefix ? [ + it.tokens.prefix.value, + ts.trivia(it.tokens.base.trivia) + ] : []; + const ref = reference(ts.wrap([ + ...prefix, + it.tokens.base.value, + token(it.tokens.postfix) + ]), { unescaped: it.idlType, context: it }); + return ts.wrap([ts.trivia(firstToken.trivia), ref]); + } + function type(it) { + return ts.wrap([ + extended_attributes(it.extAttrs), + type_body(it), + token(it.tokens.nullable), + token(it.tokens.separator) + ]); + } + function default_(def) { + if (!def) { + return ""; + } + return ts.wrap([ + token(def.tokens.assign), + ...def.expression.map(t => token(t)) + ]); + } + function argument(arg) { + return ts.wrap([ + extended_attributes(arg.extAttrs), + token(arg.tokens.optional), + ts.type(type(arg.idlType)), + token(arg.tokens.variadic), + name_token(arg.tokens.name, { data: arg }), + default_(arg.default), + token(arg.tokens.separator) + ]); + } + function extended_attribute_listitem(str) { + return ts.wrap([ + token(str.tokens.value), + token(str.tokens.separator) + ]); + } + function identifier(id, context) { + return ts.wrap([ + reference_token(id.tokens.value, context), + token(id.tokens.separator) + ]); + } + function make_ext_at(it) { + const { rhsType } = it.params; + return ts.wrap([ + ts.trivia(it.tokens.name.trivia), + ts.extendedAttribute(ts.wrap([ + ts.extendedAttributeReference(it.name), + token(it.params.tokens.assign), + reference_token(it.params.tokens.secondaryName, it), + token(it.params.tokens.open), + ...!it.params.list ? [] : + it.params.list.map( + rhsType === "identifier-list" ? id => identifier(id, it) : + rhsType && rhsType.endsWith("-list") ? extended_attribute_listitem : + argument + ), + token(it.params.tokens.close) + ])), + token(it.tokens.separator) + ]); + } + function extended_attributes(eats) { + if (!eats.length) return ""; + return ts.wrap([ + token(eats.tokens.open), + ...eats.map(make_ext_at), + token(eats.tokens.close) + ]); + } + + function operation(it, parent) { + const body = it.idlType ? [ + ts.type(type(it.idlType)), + name_token(it.tokens.name, { data: it, parent }), + token(it.tokens.open), + ts.wrap(it.arguments.map(argument)), + token(it.tokens.close), + ] : []; + return ts.definition(ts.wrap([ + extended_attributes(it.extAttrs), + it.tokens.name ? token(it.tokens.special) : token(it.tokens.special, ts.nameless, { data: it, parent }), + ...body, + token(it.tokens.termination) + ]), { data: it, parent }); + } + + function attribute(it, parent) { + return ts.definition(ts.wrap([ + extended_attributes(it.extAttrs), + token(it.tokens.special), + token(it.tokens.readonly), + token(it.tokens.base), + ts.type(type(it.idlType)), + name_token(it.tokens.name, { data: it, parent }), + token(it.tokens.termination) + ]), { data: it, parent }); + } + + function constructor(it, parent) { + return ts.definition(ts.wrap([ + extended_attributes(it.extAttrs), + token(it.tokens.base, ts.nameless, { data: it, parent }), + token(it.tokens.open), + ts.wrap(it.arguments.map(argument)), + token(it.tokens.close), + token(it.tokens.termination) + ]), { data: it, parent }); + } + + function inheritance(inh) { + if (!inh.tokens.inheritance) { + return ""; + } + return ts.wrap([ + token(inh.tokens.colon), + ts.trivia(inh.tokens.inheritance.trivia), + ts.inheritance(reference(inh.tokens.inheritance.value, { context: inh })) + ]); + } + + function container(it) { + return ts.definition(ts.wrap([ + extended_attributes(it.extAttrs), + token(it.tokens.callback), + token(it.tokens.partial), + token(it.tokens.base), + token(it.tokens.mixin), + name_token(it.tokens.name, { data: it }), + inheritance(it), + token(it.tokens.open), + iterate(it.members, it), + token(it.tokens.close), + token(it.tokens.termination) + ]), { data: it }); + } + + function field(it, parent) { + return ts.definition(ts.wrap([ + extended_attributes(it.extAttrs), + token(it.tokens.required), + ts.type(type(it.idlType)), + name_token(it.tokens.name, { data: it, parent }), + default_(it.default), + token(it.tokens.termination) + ]), { data: it, parent }); + } + function const_(it, parent) { + return ts.definition(ts.wrap([ + extended_attributes(it.extAttrs), + token(it.tokens.base), + ts.type(type(it.idlType)), + name_token(it.tokens.name, { data: it, parent }), + token(it.tokens.assign), + token(it.tokens.value), + token(it.tokens.termination) + ]), { data: it, parent }); + } + function typedef(it) { + return ts.definition(ts.wrap([ + extended_attributes(it.extAttrs), + token(it.tokens.base), + ts.type(type(it.idlType)), + name_token(it.tokens.name, { data: it }), + token(it.tokens.termination) + ]), { data: it }); + } + function includes(it) { + return ts.definition(ts.wrap([ + extended_attributes(it.extAttrs), + reference_token(it.tokens.target, it), + token(it.tokens.includes), + reference_token(it.tokens.mixin, it), + token(it.tokens.termination) + ]), { data: it }); + } + function callback(it) { + return ts.definition(ts.wrap([ + extended_attributes(it.extAttrs), + token(it.tokens.base), + name_token(it.tokens.name, { data: it }), + token(it.tokens.assign), + ts.type(type(it.idlType)), + token(it.tokens.open), + ...it.arguments.map(argument), + token(it.tokens.close), + token(it.tokens.termination), + ]), { data: it }); + } + function enum_(it) { + return ts.definition(ts.wrap([ + extended_attributes(it.extAttrs), + token(it.tokens.base), + name_token(it.tokens.name, { data: it }), + token(it.tokens.open), + iterate(it.values, it), + token(it.tokens.close), + token(it.tokens.termination) + ]), { data: it }); + } + function enum_value(v, parent) { + return ts.wrap([ + ts.trivia(v.tokens.value.trivia), + ts.definition( + ts.wrap(['"', ts.name(v.value, { data: v, parent }), '"']), + { data: v, parent } + ), + token(v.tokens.separator) + ]); + } + function iterable_like(it, parent) { + return ts.definition(ts.wrap([ + extended_attributes(it.extAttrs), + token(it.tokens.readonly), + token(it.tokens.async), + token(it.tokens.base, ts.generic), + token(it.tokens.open), + ts.wrap(it.idlType.map(type)), + token(it.tokens.close), + token(it.tokens.argsOpen), + ts.wrap(it.arguments.map(argument)), + token(it.tokens.argsClose), + token(it.tokens.termination) + ]), { data: it, parent }); + } + function eof(it) { + return ts.trivia(it.trivia); + } + + const table = { + interface: container, + "interface mixin": container, + namespace: container, + operation, + attribute, + constructor, + dictionary: container, + field, + const: const_, + typedef, + includes, + callback, + enum: enum_, + "enum-value": enum_value, + iterable: iterable_like, + maplike: iterable_like, + setlike: iterable_like, + "callback interface": container, + eof + }; + function dispatch(it, parent) { + const dispatcher = table[it.type]; + if (!dispatcher) { + throw new Error(`Type "${it.type}" is unsupported`); + } + return table[it.type](it, parent); + } + function iterate(things, parent) { + if (!things) return; + const results = things.map(thing => dispatch(thing, parent)); + return ts.wrap(results); + } + return iterate(ast); +} + + +/***/ }), +/* 31 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "validate", function() { return validate; }); +/* harmony import */ var _error_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(3); + + + + +function getMixinMap(all, unique) { + const map = new Map(); + const includes = all.filter(def => def.type === "includes"); + for (const include of includes) { + const mixin = unique.get(include.includes); + if (!mixin) { + continue; + } + const array = map.get(include.target); + if (array) { + array.push(mixin); + } else { + map.set(include.target, [mixin]); + } + } + return map; +} + +/** + * @typedef {ReturnType} Definitions + */ +function groupDefinitions(all) { + const unique = new Map(); + const duplicates = new Set(); + const partials = new Map(); + for (const def of all) { + if (def.partial) { + const array = partials.get(def.name); + if (array) { + array.push(def); + } else { + partials.set(def.name, [def]); + } + continue; + } + if (!def.name) { + continue; + } + if (!unique.has(def.name)) { + unique.set(def.name, def); + } else { + duplicates.add(def); + } + } + return { + all, + unique, + partials, + duplicates, + mixinMap: getMixinMap(all, unique), + cache: { + typedefIncludesDictionary: new WeakMap(), + dictionaryIncludesRequiredField: new WeakMap() + }, + }; +} + +function* checkDuplicatedNames({ unique, duplicates }) { + for (const dup of duplicates) { + const { name } = dup; + const message = `The name "${name}" of type "${unique.get(name).type}" was already seen`; + yield Object(_error_js__WEBPACK_IMPORTED_MODULE_0__["validationError"])(dup.tokens.name, dup, "no-duplicate", message); + } +} + +function* validateIterable(ast) { + const defs = groupDefinitions(ast); + for (const def of defs.all) { + if (def.validate) { + yield* def.validate(defs); + } + } + yield* checkDuplicatedNames(defs); +} + +// Remove this once all of our support targets expose `.flat()` by default +function flatten(array) { + if (array.flat) { + return array.flat(); + } + return [].concat(...array); +} + +/** + * @param {*} ast AST or array of ASTs + */ +function validate(ast) { + return [...validateIterable(flatten(ast))]; +} + + +/***/ }) +/******/ ]); +}); +//# sourceMappingURL=webidl2.js.map \ No newline at end of file diff --git a/test/fixtures/wpt/url/failure.html b/test/fixtures/wpt/url/failure.html index bebdb3dcc4df6b..8f3d0299a40fdb 100644 --- a/test/fixtures/wpt/url/failure.html +++ b/test/fixtures/wpt/url/failure.html @@ -20,29 +20,29 @@ self.test(() => { // URL's constructor's first argument is tested by url-constructor.html // If a URL fails to parse with any valid base, it must also fail to parse with no base, i.e. // when used as a base URL itself. - assert_throws(new TypeError(), () => new URL("about:blank", test.input)); + assert_throws_js(TypeError, () => new URL("about:blank", test.input)); }, "URL's constructor's base argument: " + name) self.test(() => { const url = new URL("about:blank") - assert_throws(new TypeError, () => url.href = test.input) + assert_throws_js(TypeError, () => url.href = test.input) }, "URL's href: " + name) self.test(() => { const client = new XMLHttpRequest() - assert_throws("SyntaxError", () => client.open("GET", test.input)) + assert_throws_dom("SyntaxError", () => client.open("GET", test.input)) }, "XHR: " + name) self.test(() => { - assert_throws(new TypeError, () => self.navigator.sendBeacon(test.input)) + assert_throws_js(TypeError, () => self.navigator.sendBeacon(test.input)) }, "sendBeacon(): " + name) self.test(() => { - assert_throws(new TypeError, () => self[0].location = test.input) + assert_throws_js(self[0].TypeError, () => self[0].location = test.input) }, "Location's href: " + name) self.test(() => { - assert_throws("SyntaxError", () => self.open(test.input).close()) + assert_throws_dom("SyntaxError", () => self.open(test.input).close()) }, "window.open(): " + name) } } diff --git a/test/fixtures/wpt/url/historical.any.js b/test/fixtures/wpt/url/historical.any.js index 407e118f3a05f9..c3067dfd730123 100644 --- a/test/fixtures/wpt/url/historical.any.js +++ b/test/fixtures/wpt/url/historical.any.js @@ -15,7 +15,7 @@ if(self.GLOBAL.isWindow()) { test(function() { var url = new URL("./foo", "http://www.example.org"); assert_equals(url.href, "http://www.example.org/foo"); - assert_throws(new TypeError(), function() { + assert_throws_js(TypeError, function() { url.href = "./bar"; }); }, "Setting URL's href attribute and base URLs"); diff --git a/test/fixtures/wpt/url/resources/setters_tests.json b/test/fixtures/wpt/url/resources/setters_tests.json index 6b7d19b10164a3..6c011e2f9acae4 100644 --- a/test/fixtures/wpt/url/resources/setters_tests.json +++ b/test/fixtures/wpt/url/resources/setters_tests.json @@ -686,6 +686,17 @@ "port": "2" } }, + { + "comment": "IPv6 literal address with port, crbug.com/1012416", + "href": "http://example.net", + "new_value": "[2001:db8::2]:4002", + "expected": { + "href": "http://[2001:db8::2]:4002/", + "host": "[2001:db8::2]:4002", + "hostname": "[2001:db8::2]", + "port": "4002" + } + }, { "comment": "Default port number is removed", "href": "http://example.net", @@ -1837,12 +1848,30 @@ } }, { - "comment": "Simple percent-encoding; nuls, tabs, and newlines are removed", + "comment": "Simple percent-encoding; tabs and newlines are removed", "href": "a:/", "new_value": "\u0000\u0001\t\n\r\u001f !\"#$%&'()*+,-./09:;<=>?@AZ[\\]^_`az{|}~\u007f\u0080\u0081Éé", "expected": { - "href": "a:/#%01%1F%20!%22#$%&'()*+,-./09:;%3C=%3E?@AZ[\\]^_%60az{|}~%7F%C2%80%C2%81%C3%89%C3%A9", - "hash": "#%01%1F%20!%22#$%&'()*+,-./09:;%3C=%3E?@AZ[\\]^_%60az{|}~%7F%C2%80%C2%81%C3%89%C3%A9" + "href": "a:/#%00%01%1F%20!%22#$%&'()*+,-./09:;%3C=%3E?@AZ[\\]^_%60az{|}~%7F%C2%80%C2%81%C3%89%C3%A9", + "hash": "#%00%01%1F%20!%22#$%&'()*+,-./09:;%3C=%3E?@AZ[\\]^_%60az{|}~%7F%C2%80%C2%81%C3%89%C3%A9" + } + }, + { + "comment": "Percent-encode NULLs in fragment", + "href": "http://example.net", + "new_value": "a\u0000b", + "expected": { + "href": "http://example.net/#a%00b", + "hash": "#a%00b" + } + }, + { + "comment": "Percent-encode NULLs in fragment", + "href": "non-spec:/", + "new_value": "a\u0000b", + "expected": { + "href": "non-spec:/#a%00b", + "hash": "#a%00b" } }, { diff --git a/test/fixtures/wpt/url/resources/toascii.json b/test/fixtures/wpt/url/resources/toascii.json index 814f06e794866d..1fb57673816e43 100644 --- a/test/fixtures/wpt/url/resources/toascii.json +++ b/test/fixtures/wpt/url/resources/toascii.json @@ -145,5 +145,27 @@ { "input": "01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.0123456789012345678901234567890123456789012345678.β", "output": "01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.0123456789012345678901234567890123456789012345678.xn--nxa" + }, + { + "comment": "IDNA ignored code points", + "input": "a\u00ADb", + "output": "ab" + }, + { + "input": "a%C2%ADb", + "output": "ab" + }, + { + "comment": "Empty host after domain to ASCII", + "input": "\u00AD", + "output": null + }, + { + "input": "%C2%AD", + "output": null + }, + { + "input": "xn--", + "output": null } ] diff --git a/test/fixtures/wpt/url/resources/urltestdata.json b/test/fixtures/wpt/url/resources/urltestdata.json index 61249e14996297..1ed95583c12f3d 100644 --- a/test/fixtures/wpt/url/resources/urltestdata.json +++ b/test/fixtures/wpt/url/resources/urltestdata.json @@ -5910,6 +5910,130 @@ "search": "", "hash": "#frag" }, + "# file: drive letter cases from https://crbug.com/1078698", + { + "input": "file:///Y:", + "base": "about:blank", + "href": "file:///Y:", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/Y:", + "search": "", + "hash": "" + }, + { + "input": "file:///Y:/", + "base": "about:blank", + "href": "file:///Y:/", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/Y:/", + "search": "", + "hash": "" + }, + { + "input": "file:///./Y", + "base": "about:blank", + "href": "file:///Y", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/Y", + "search": "", + "hash": "" + }, + { + "input": "file:///./Y:", + "base": "about:blank", + "href": "file:///Y:", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/Y:", + "search": "", + "hash": "" + }, + { + "input": "\\\\\\.\\Y:", + "base": "about:blank", + "failure": true + }, + "# file: drive letter cases from https://crbug.com/1078698 but lowercased", + { + "input": "file:///y:", + "base": "about:blank", + "href": "file:///y:", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/y:", + "search": "", + "hash": "" + }, + { + "input": "file:///y:/", + "base": "about:blank", + "href": "file:///y:/", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/y:/", + "search": "", + "hash": "" + }, + { + "input": "file:///./y", + "base": "about:blank", + "href": "file:///y", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/y", + "search": "", + "hash": "" + }, + { + "input": "file:///./y:", + "base": "about:blank", + "href": "file:///y:", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/y:", + "search": "", + "hash": "" + }, + { + "input": "\\\\\\.\\y:", + "base": "about:blank", + "failure": true + }, "# IPv6 tests", { "input": "http://[1:0::]", @@ -6719,7 +6843,7 @@ { "input": "http://example.org/test?a#b\u0000c", "base": "about:blank", - "href": "http://example.org/test?a#bc", + "href": "http://example.org/test?a#b%00c", "protocol": "http:", "username": "", "password": "", @@ -6728,6 +6852,124 @@ "port": "", "pathname": "/test", "search": "?a", - "hash": "#bc" + "hash": "#b%00c" + }, + { + "input": "non-spec://example.org/test?a#b\u0000c", + "base": "about:blank", + "href": "non-spec://example.org/test?a#b%00c", + "protocol": "non-spec:", + "username": "", + "password": "", + "host": "example.org", + "hostname": "example.org", + "port": "", + "pathname": "/test", + "search": "?a", + "hash": "#b%00c" + }, + { + "input": "non-spec:/test?a#b\u0000c", + "base": "about:blank", + "href": "non-spec:/test?a#b%00c", + "protocol": "non-spec:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/test", + "search": "?a", + "hash": "#b%00c" + }, + "First scheme char - not allowed: https://github.com/whatwg/url/issues/464", + { + "input": "10.0.0.7:8080/foo.html", + "base": "file:///some/dir/bar.html", + "href": "file:///some/dir/10.0.0.7:8080/foo.html", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/some/dir/10.0.0.7:8080/foo.html", + "search": "", + "hash": "" + }, + "Subsequent scheme chars - not allowed", + { + "input": "a!@$*=/foo.html", + "base": "file:///some/dir/bar.html", + "href": "file:///some/dir/a!@$*=/foo.html", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/some/dir/a!@$*=/foo.html", + "search": "", + "hash": "" + }, + "First and subsequent scheme chars - allowed", + { + "input": "a1234567890-+.:foo/bar", + "base": "http://example.com/dir/file", + "href": "a1234567890-+.:foo/bar", + "protocol": "a1234567890-+.:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "foo/bar", + "search": "", + "hash": "" + }, + "IDNA ignored code points in file URLs hosts", + { + "input": "file://a\u00ADb/p", + "base": "about:blank", + "href": "file://ab/p", + "protocol": "file:", + "username": "", + "password": "", + "host": "ab", + "hostname": "ab", + "port": "", + "pathname": "/p", + "search": "", + "hash": "" + }, + { + "input": "file://a%C2%ADb/p", + "base": "about:blank", + "href": "file://ab/p", + "protocol": "file:", + "username": "", + "password": "", + "host": "ab", + "hostname": "ab", + "port": "", + "pathname": "/p", + "search": "", + "hash": "" + }, + "Empty host after the domain to ASCII", + { + "input": "file://\u00ad/p", + "base": "about:blank", + "failure": true + }, + { + "input": "file://%C2%AD/p", + "base": "about:blank", + "failure": true + }, + { + "input": "file://xn--/p", + "base": "about:blank", + "failure": true } ] diff --git a/test/fixtures/wpt/url/toascii.window.js b/test/fixtures/wpt/url/toascii.window.js index b28c664479a26a..cdc488ec28526f 100644 --- a/test/fixtures/wpt/url/toascii.window.js +++ b/test/fixtures/wpt/url/toascii.window.js @@ -28,7 +28,7 @@ function runTests(tests) { assert_equals(url.href, "https://" + hostTest.output + "/x") } else { if(type === "url") { - assert_throws(new TypeError, () => makeURL("url", hostTest.input)) + assert_throws_js(TypeError, () => makeURL("url", hostTest.input)) } else { const url = makeURL(type, hostTest.input) assert_equals(url.host, "") diff --git a/test/fixtures/wpt/url/url-constructor.html b/test/fixtures/wpt/url/url-constructor.html index cb4c0db3571d2f..7ddcdc88170c20 100644 --- a/test/fixtures/wpt/url/url-constructor.html +++ b/test/fixtures/wpt/url/url-constructor.html @@ -15,7 +15,7 @@ test(function() { if (expected.failure) { - assert_throws(new TypeError(), function() { + assert_throws_js(TypeError, function() { bURL(expected.input, expected.base) }) return diff --git a/test/fixtures/wpt/url/url-searchparams.any.js b/test/fixtures/wpt/url/url-searchparams.any.js index c55ae58d3b1e5b..34d4a4b0689831 100644 --- a/test/fixtures/wpt/url/url-searchparams.any.js +++ b/test/fixtures/wpt/url/url-searchparams.any.js @@ -29,7 +29,7 @@ function runURLSearchParamTests() { 'use strict' var urlString = 'http://example.org' var url = bURL(urlString) - assert_throws(TypeError(), function() { url.searchParams = new URLSearchParams(urlString) }) + assert_throws_js(TypeError, function() { url.searchParams = new URLSearchParams(urlString) }) }, 'URL.searchParams setter, invalid values') test(function() { diff --git a/test/fixtures/wpt/url/url-setters-stripping.any.js b/test/fixtures/wpt/url/url-setters-stripping.any.js new file mode 100644 index 00000000000000..c706ce47d0b5b2 --- /dev/null +++ b/test/fixtures/wpt/url/url-setters-stripping.any.js @@ -0,0 +1,125 @@ +function urlString({ scheme = "https", + username = "username", + password = "password", + host = "host", + port = "8000", + pathname = "path", + search = "query", + hash = "fragment" }) { + return `${scheme}://${username}:${password}@${host}:${port}/${pathname}?${search}#${hash}`; +} + +function urlRecord(scheme) { + return new URL(urlString({ scheme })); +} + +for(const scheme of ["https", "wpt++"]) { + for(let i = 0; i < 0x20; i++) { + const stripped = i === 0x09 || i === 0x0A || i === 0x0D; + + // It turns out that user agents are surprisingly similar for these ranges so generate fewer + // tests. If this is changed also change the logic for host below. + if (i !== 0 && i !== 0x1F && !stripped) { + continue; + } + + const cpString = String.fromCodePoint(i); + const cpReference = "U+" + i.toString(16).toUpperCase().padStart(4, "0"); + + test(() => { + const expected = scheme === "https" ? (stripped ? "http" : "https") : (stripped ? "wpt--" : "wpt++"); + const url = urlRecord(scheme); + url.protocol = String.fromCodePoint(i) + (scheme === "https" ? "http" : "wpt--"); + assert_equals(url.protocol, expected + ":", "property"); + assert_equals(url.href, urlString({ scheme: expected }), "href"); + }, `Setting protocol with leading ${cpReference} (${scheme}:)`); + + test(() => { + const expected = scheme === "https" ? (stripped ? "http" : "https") : (stripped ? "wpt--" : "wpt++"); + const url = urlRecord(scheme); + url.protocol = (scheme === "https" ? "http" : "wpt--") + String.fromCodePoint(i); + assert_equals(url.protocol, expected + ":", "property"); + assert_equals(url.href, urlString({ scheme: expected }), "href"); + }, `Setting protocol with ${cpReference} before inserted colon (${scheme}:)`); + + // Cannot test protocol with trailing as the algorithm inserts a colon before proceeding + + // These do no stripping + for (const property of ["username", "password"]) { + for (const [type, expected, input] of [ + ["leading", encodeURIComponent(cpString) + "test", String.fromCodePoint(i) + "test"], + ["middle", "te" + encodeURIComponent(cpString) + "st", "te" + String.fromCodePoint(i) + "st"], + ["trailing", "test" + encodeURIComponent(cpString), "test" + String.fromCodePoint(i)] + ]) { + test(() => { + const url = urlRecord(scheme); + url[property] = input; + assert_equals(url[property], expected, "property"); + assert_equals(url.href, urlString({ scheme, [property]: expected }), "href"); + }, `Setting ${property} with ${type} ${cpReference} (${scheme}:)`); + } + } + + for (const [type, expectedPart, input] of [ + ["leading", (scheme === "https" ? cpString : encodeURIComponent(cpString)) + "test", String.fromCodePoint(i) + "test"], + ["middle", "te" + (scheme === "https" ? cpString : encodeURIComponent(cpString)) + "st", "te" + String.fromCodePoint(i) + "st"], + ["trailing", "test" + (scheme === "https" ? cpString : encodeURIComponent(cpString)), "test" + String.fromCodePoint(i)] + ]) { + test(() => { + const expected = i === 0x00 ? "host" : stripped ? "test" : expectedPart; + const url = urlRecord(scheme); + url.host = input; + assert_equals(url.host, expected + ":8000", "property"); + assert_equals(url.href, urlString({ scheme, host: expected }), "href"); + }, `Setting host with ${type} ${cpReference} (${scheme}:)`); + + test(() => { + const expected = i === 0x00 ? "host" : stripped ? "test" : expectedPart; + const url = urlRecord(scheme); + url.hostname = input; + assert_equals(url.hostname, expected, "property"); + assert_equals(url.href, urlString({ scheme, host: expected }), "href"); + }, `Setting hostname with ${type} ${cpReference} (${scheme}:)`); + } + + test(() => { + const expected = stripped ? "9000" : "8000"; + const url = urlRecord(scheme); + url.port = String.fromCodePoint(i) + "9000"; + assert_equals(url.port, expected, "property"); + assert_equals(url.href, urlString({ scheme, port: expected }), "href"); + }, `Setting port with leading ${cpReference} (${scheme}:)`); + + test(() => { + const expected = stripped ? "9000" : "90"; + const url = urlRecord(scheme); + url.port = "90" + String.fromCodePoint(i) + "00"; + assert_equals(url.port, expected, "property"); + assert_equals(url.href, urlString({ scheme, port: expected }), "href"); + }, `Setting port with middle ${cpReference} (${scheme}:)`); + + test(() => { + const expected = "9000"; + const url = urlRecord(scheme); + url.port = "9000" + String.fromCodePoint(i); + assert_equals(url.port, expected, "property"); + assert_equals(url.href, urlString({ scheme, port: expected }), "href"); + }, `Setting port with trailing ${cpReference} (${scheme}:)`); + + for (const [property, separator] of [["pathname", "/"], ["search", "?"], ["hash", "#"]]) { + for (const [type, expectedPart, input] of [ + ["leading", encodeURIComponent(cpString) + "test", String.fromCodePoint(i) + "test"], + ["middle", "te" + encodeURIComponent(cpString) + "st", "te" + String.fromCodePoint(i) + "st"], + ["trailing", "test" + encodeURIComponent(cpString), "test" + String.fromCodePoint(i)] + ]) { + test(() => { + const expected = stripped ? "test" : expectedPart; + const url = urlRecord(scheme); + url[property] = input; + assert_equals(url[property], separator + expected, "property" + ' ' + cpString); + assert_equals(url.href, urlString({ scheme, [property]: expected }), "href"); + }, `Setting ${property} with ${type} ${cpReference} (${scheme}:)`); + } + } + } +} diff --git a/test/fixtures/wpt/url/urlencoded-parser.any.js b/test/fixtures/wpt/url/urlencoded-parser.any.js index 65e894b94c26b0..46b932bb014364 100644 --- a/test/fixtures/wpt/url/urlencoded-parser.any.js +++ b/test/fixtures/wpt/url/urlencoded-parser.any.js @@ -28,7 +28,11 @@ { "input": '%a=a', "output": [['%a', 'a']] }, { "input": '%a_=a', "output": [['%a_', 'a']] }, { "input": '%61=a', "output": [['a', 'a']] }, - { "input": '%61+%4d%4D=', "output": [['a MM', '']] } + { "input": '%61+%4d%4D=', "output": [['a MM', '']] }, + { "input": "id=0&value=%", "output": [['id', '0'], ['value', '%']] }, + { "input": "b=%2sf%2a", "output": [['b', '%2sf*']]}, + { "input": "b=%2%2af%2a", "output": [['b', '%2*f*']]}, + { "input": "b=%%2a", "output": [['b', '%*']]} ].forEach((val) => { test(() => { let sp = new URLSearchParams(val.input), diff --git a/test/fixtures/wpt/url/urlsearchparams-constructor.any.js b/test/fixtures/wpt/url/urlsearchparams-constructor.any.js index 398021dde2f35f..1135d5d3dbbfa3 100644 --- a/test/fixtures/wpt/url/urlsearchparams-constructor.any.js +++ b/test/fixtures/wpt/url/urlsearchparams-constructor.any.js @@ -22,7 +22,7 @@ test(function () { test(() => { var params = new URLSearchParams(DOMException); assert_equals(params.toString(), "INDEX_SIZE_ERR=1&DOMSTRING_SIZE_ERR=2&HIERARCHY_REQUEST_ERR=3&WRONG_DOCUMENT_ERR=4&INVALID_CHARACTER_ERR=5&NO_DATA_ALLOWED_ERR=6&NO_MODIFICATION_ALLOWED_ERR=7&NOT_FOUND_ERR=8&NOT_SUPPORTED_ERR=9&INUSE_ATTRIBUTE_ERR=10&INVALID_STATE_ERR=11&SYNTAX_ERR=12&INVALID_MODIFICATION_ERR=13&NAMESPACE_ERR=14&INVALID_ACCESS_ERR=15&VALIDATION_ERR=16&TYPE_MISMATCH_ERR=17&SECURITY_ERR=18&NETWORK_ERR=19&ABORT_ERR=20&URL_MISMATCH_ERR=21"A_EXCEEDED_ERR=22&TIMEOUT_ERR=23&INVALID_NODE_TYPE_ERR=24&DATA_CLONE_ERR=25") - assert_throws(new TypeError(), () => new URLSearchParams(DOMException.prototype), + assert_throws_js(TypeError, () => new URLSearchParams(DOMException.prototype), "Constructing a URLSearchParams from DOMException.prototype should throw due to branding checks"); }, "URLSearchParams constructor, DOMException as argument") @@ -56,6 +56,28 @@ test(function() { assert_false(params.has('c'), 'Search params object did not have the name "c"'); assert_true(params.has(' c'), 'Search params object has name " c"'); assert_true(params.has('møø'), 'Search params object has name "møø"'); + + params = new URLSearchParams('id=0&value=%'); + assert_true(params != null, 'constructor returned non-null value.'); + assert_true(params.has('id'), 'Search params object has name "id"'); + assert_true(params.has('value'), 'Search params object has name "value"'); + assert_equals(params.get('id'), '0'); + assert_equals(params.get('value'), '%'); + + params = new URLSearchParams('b=%2sf%2a'); + assert_true(params != null, 'constructor returned non-null value.'); + assert_true(params.has('b'), 'Search params object has name "b"'); + assert_equals(params.get('b'), '%2sf*'); + + params = new URLSearchParams('b=%2%2af%2a'); + assert_true(params != null, 'constructor returned non-null value.'); + assert_true(params.has('b'), 'Search params object has name "b"'); + assert_equals(params.get('b'), '%2*f*'); + + params = new URLSearchParams('b=%%2a'); + assert_true(params != null, 'constructor returned non-null value.'); + assert_true(params.has('b'), 'Search params object has name "b"'); + assert_equals(params.get('b'), '%*'); }, 'URLSearchParams constructor, string.'); test(function() { @@ -73,6 +95,23 @@ test(function() { assert_false(seed.has('g')); }, 'URLSearchParams constructor, object.'); +test(function() { + var formData = new FormData() + formData.append('a', 'b') + formData.append('c', 'd') + var params = new URLSearchParams(formData); + assert_true(params != null, 'constructor returned non-null value.'); + assert_equals(params.get('a'), 'b'); + assert_equals(params.get('c'), 'd'); + assert_false(params.has('d')); + // The name-value pairs are copied when created; later updates + // should not be observable. + formData.append('e', 'f'); + assert_false(params.has('e')); + params.append('g', 'h'); + assert_false(formData.has('g')); +}, 'URLSearchParams constructor, FormData.'); + test(function() { var params = new URLSearchParams('a=b+c'); assert_equals(params.get('a'), 'b c'); @@ -153,8 +192,8 @@ test(function() { params = new URLSearchParams([['a', 'b'], ['c', 'd']]); assert_equals(params.get("a"), "b"); assert_equals(params.get("c"), "d"); - assert_throws(new TypeError(), function() { new URLSearchParams([[1]]); }); - assert_throws(new TypeError(), function() { new URLSearchParams([[1,2,3]]); }); + assert_throws_js(TypeError, function() { new URLSearchParams([[1]]); }); + assert_throws_js(TypeError, function() { new URLSearchParams([[1,2,3]]); }); }, "Constructor with sequence of sequences of strings"); [ diff --git a/test/fixtures/wpt/url/urlsearchparams-stringifier.any.js b/test/fixtures/wpt/url/urlsearchparams-stringifier.any.js index ef95c1434c7964..bc7bedd533f58d 100644 --- a/test/fixtures/wpt/url/urlsearchparams-stringifier.any.js +++ b/test/fixtures/wpt/url/urlsearchparams-stringifier.any.js @@ -78,6 +78,9 @@ test(function() { params.delete('a'); params.append('a%b', 'c'); assert_equals(params + '', 'a%25b=c'); + + params = new URLSearchParams('id=0&value=%') + assert_equals(params + '', 'id=0&value=%25') }, 'Serialize %'); test(function() { @@ -107,6 +110,15 @@ test(function() { // The lone '=' _does_ survive the roundtrip. params = new URLSearchParams('a=&a=b'); assert_equals(params.toString(), 'a=&a=b'); + + params = new URLSearchParams('b=%2sf%2a'); + assert_equals(params.toString(), 'b=%252sf*'); + + params = new URLSearchParams('b=%2%2af%2a'); + assert_equals(params.toString(), 'b=%252*f*'); + + params = new URLSearchParams('b=%%2a'); + assert_equals(params.toString(), 'b=%25*'); }, 'URLSearchParams.toString'); test(() => { diff --git a/test/fixtures/wpt/versions.json b/test/fixtures/wpt/versions.json index 047ac440d8e5d7..8b823b01ef3491 100644 --- a/test/fixtures/wpt/versions.json +++ b/test/fixtures/wpt/versions.json @@ -8,15 +8,15 @@ "path": "encoding" }, "url": { - "commit": "43feb7f612fe9160639e09a47933a29834904d69", + "commit": "c79fe5612ae8730bcc8e732801c0d1ec6a371243", "path": "url" }, "resources": { - "commit": "e1fddfbf801e7cce9cac5645e992194e4059ef56", + "commit": "1ab7a4ebcf4a9c7eae8fad7ba8565ca3ae15ca27", "path": "resources" }, "interfaces": { - "commit": "8ada332aeac03764f73ec7ff66f682c5c0b454b4", + "commit": "876f188904878548dfd099df25e0ac88005b1de8", "path": "interfaces" }, "html/webappapis/microtask-queuing": { diff --git a/test/wpt/status/url.json b/test/wpt/status/url.json index afd5acdcbfc455..fc244e95d02923 100644 --- a/test/wpt/status/url.json +++ b/test/wpt/status/url.json @@ -11,5 +11,8 @@ }, "idlharness.any.js": { "fail": "getter/setter names are wrong, etc." - } + }, + "urlsearchparams-constructor.any.js": { + "fail": "FormData is not defined" + } }