@depup/jsdom 28.1.0-depup.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE.txt +22 -0
- package/README.md +38 -0
- package/changes.json +38 -0
- package/lib/api.js +373 -0
- package/lib/generated/event-sets.js +115 -0
- package/lib/generated/idl/AbortController.js +143 -0
- package/lib/generated/idl/AbortSignal.js +249 -0
- package/lib/generated/idl/AbstractRange.js +171 -0
- package/lib/generated/idl/AddEventListenerOptions.js +53 -0
- package/lib/generated/idl/AssignedNodesOptions.js +28 -0
- package/lib/generated/idl/Attr.js +219 -0
- package/lib/generated/idl/BarProp.js +117 -0
- package/lib/generated/idl/BeforeUnloadEvent.js +139 -0
- package/lib/generated/idl/BinaryType.js +12 -0
- package/lib/generated/idl/Blob.js +253 -0
- package/lib/generated/idl/BlobCallback.js +30 -0
- package/lib/generated/idl/BlobEvent.js +157 -0
- package/lib/generated/idl/BlobEventInit.js +43 -0
- package/lib/generated/idl/BlobPropertyBag.js +42 -0
- package/lib/generated/idl/CDATASection.js +109 -0
- package/lib/generated/idl/CanPlayTypeResult.js +12 -0
- package/lib/generated/idl/CharacterData.js +455 -0
- package/lib/generated/idl/CloseEvent.js +168 -0
- package/lib/generated/idl/CloseEventInit.js +65 -0
- package/lib/generated/idl/Comment.js +120 -0
- package/lib/generated/idl/CompositionEvent.js +219 -0
- package/lib/generated/idl/CompositionEventInit.js +32 -0
- package/lib/generated/idl/Crypto.js +148 -0
- package/lib/generated/idl/CustomElementConstructor.js +34 -0
- package/lib/generated/idl/CustomElementRegistry.js +269 -0
- package/lib/generated/idl/CustomEvent.js +206 -0
- package/lib/generated/idl/CustomEventInit.js +32 -0
- package/lib/generated/idl/DOMException.js +222 -0
- package/lib/generated/idl/DOMImplementation.js +237 -0
- package/lib/generated/idl/DOMParser.js +140 -0
- package/lib/generated/idl/DOMRect.js +276 -0
- package/lib/generated/idl/DOMRectInit.js +76 -0
- package/lib/generated/idl/DOMRectReadOnly.js +285 -0
- package/lib/generated/idl/DOMStringMap.js +299 -0
- package/lib/generated/idl/DOMTokenList.js +539 -0
- package/lib/generated/idl/DeviceMotionEvent.js +183 -0
- package/lib/generated/idl/DeviceMotionEventAcceleration.js +145 -0
- package/lib/generated/idl/DeviceMotionEventAccelerationInit.js +61 -0
- package/lib/generated/idl/DeviceMotionEventInit.js +70 -0
- package/lib/generated/idl/DeviceMotionEventRotationRate.js +145 -0
- package/lib/generated/idl/DeviceMotionEventRotationRateInit.js +61 -0
- package/lib/generated/idl/DeviceOrientationEvent.js +183 -0
- package/lib/generated/idl/DeviceOrientationEventInit.js +80 -0
- package/lib/generated/idl/Document.js +4511 -0
- package/lib/generated/idl/DocumentFragment.js +336 -0
- package/lib/generated/idl/DocumentReadyState.js +12 -0
- package/lib/generated/idl/DocumentType.js +254 -0
- package/lib/generated/idl/Element.js +3720 -0
- package/lib/generated/idl/ElementCreationOptions.js +26 -0
- package/lib/generated/idl/ElementDefinitionOptions.js +29 -0
- package/lib/generated/idl/ElementInternals.js +2152 -0
- package/lib/generated/idl/EndingType.js +12 -0
- package/lib/generated/idl/ErrorEvent.js +192 -0
- package/lib/generated/idl/ErrorEventInit.js +92 -0
- package/lib/generated/idl/Event.js +430 -0
- package/lib/generated/idl/EventHandlerNonNull.js +36 -0
- package/lib/generated/idl/EventInit.js +58 -0
- package/lib/generated/idl/EventListener.js +35 -0
- package/lib/generated/idl/EventListenerOptions.js +28 -0
- package/lib/generated/idl/EventModifierInit.js +221 -0
- package/lib/generated/idl/EventTarget.js +259 -0
- package/lib/generated/idl/External.js +130 -0
- package/lib/generated/idl/File.js +185 -0
- package/lib/generated/idl/FileList.js +298 -0
- package/lib/generated/idl/FilePropertyBag.js +33 -0
- package/lib/generated/idl/FileReader.js +468 -0
- package/lib/generated/idl/FocusEvent.js +144 -0
- package/lib/generated/idl/FocusEventInit.js +36 -0
- package/lib/generated/idl/FormData.js +468 -0
- package/lib/generated/idl/Function.js +42 -0
- package/lib/generated/idl/GetRootNodeOptions.js +31 -0
- package/lib/generated/idl/HTMLAnchorElement.js +1026 -0
- package/lib/generated/idl/HTMLAreaElement.js +825 -0
- package/lib/generated/idl/HTMLAudioElement.js +111 -0
- package/lib/generated/idl/HTMLBRElement.js +156 -0
- package/lib/generated/idl/HTMLBaseElement.js +196 -0
- package/lib/generated/idl/HTMLBodyElement.js +880 -0
- package/lib/generated/idl/HTMLButtonElement.js +525 -0
- package/lib/generated/idl/HTMLCanvasElement.js +307 -0
- package/lib/generated/idl/HTMLCollection.js +352 -0
- package/lib/generated/idl/HTMLDListElement.js +159 -0
- package/lib/generated/idl/HTMLDataElement.js +156 -0
- package/lib/generated/idl/HTMLDataListElement.js +126 -0
- package/lib/generated/idl/HTMLDetailsElement.js +159 -0
- package/lib/generated/idl/HTMLDialogElement.js +159 -0
- package/lib/generated/idl/HTMLDirectoryElement.js +159 -0
- package/lib/generated/idl/HTMLDivElement.js +156 -0
- package/lib/generated/idl/HTMLElement.js +3492 -0
- package/lib/generated/idl/HTMLEmbedElement.js +381 -0
- package/lib/generated/idl/HTMLFieldSetElement.js +332 -0
- package/lib/generated/idl/HTMLFontElement.js +239 -0
- package/lib/generated/idl/HTMLFormControlsCollection.js +318 -0
- package/lib/generated/idl/HTMLFormElement.js +661 -0
- package/lib/generated/idl/HTMLFrameElement.js +513 -0
- package/lib/generated/idl/HTMLFrameSetElement.js +711 -0
- package/lib/generated/idl/HTMLHRElement.js +323 -0
- package/lib/generated/idl/HTMLHeadElement.js +111 -0
- package/lib/generated/idl/HTMLHeadingElement.js +156 -0
- package/lib/generated/idl/HTMLHtmlElement.js +156 -0
- package/lib/generated/idl/HTMLIFrameElement.js +689 -0
- package/lib/generated/idl/HTMLImageElement.js +906 -0
- package/lib/generated/idl/HTMLInputElement.js +1931 -0
- package/lib/generated/idl/HTMLLIElement.js +204 -0
- package/lib/generated/idl/HTMLLabelElement.js +182 -0
- package/lib/generated/idl/HTMLLegendElement.js +169 -0
- package/lib/generated/idl/HTMLLinkElement.js +555 -0
- package/lib/generated/idl/HTMLMapElement.js +171 -0
- package/lib/generated/idl/HTMLMarqueeElement.js +558 -0
- package/lib/generated/idl/HTMLMediaElement.js +900 -0
- package/lib/generated/idl/HTMLMenuElement.js +159 -0
- package/lib/generated/idl/HTMLMetaElement.js +279 -0
- package/lib/generated/idl/HTMLMeterElement.js +368 -0
- package/lib/generated/idl/HTMLModElement.js +217 -0
- package/lib/generated/idl/HTMLOListElement.js +284 -0
- package/lib/generated/idl/HTMLObjectElement.js +945 -0
- package/lib/generated/idl/HTMLOptGroupElement.js +200 -0
- package/lib/generated/idl/HTMLOptionElement.js +379 -0
- package/lib/generated/idl/HTMLOptionsCollection.js +513 -0
- package/lib/generated/idl/HTMLOutputElement.js +395 -0
- package/lib/generated/idl/HTMLParagraphElement.js +156 -0
- package/lib/generated/idl/HTMLParamElement.js +279 -0
- package/lib/generated/idl/HTMLPictureElement.js +111 -0
- package/lib/generated/idl/HTMLPreElement.js +163 -0
- package/lib/generated/idl/HTMLProgressElement.js +232 -0
- package/lib/generated/idl/HTMLQuoteElement.js +176 -0
- package/lib/generated/idl/HTMLScriptElement.js +472 -0
- package/lib/generated/idl/HTMLSelectElement.js +993 -0
- package/lib/generated/idl/HTMLSlotElement.js +195 -0
- package/lib/generated/idl/HTMLSourceElement.js +340 -0
- package/lib/generated/idl/HTMLSpanElement.js +111 -0
- package/lib/generated/idl/HTMLStyleElement.js +210 -0
- package/lib/generated/idl/HTMLTableCaptionElement.js +156 -0
- package/lib/generated/idl/HTMLTableCellElement.js +733 -0
- package/lib/generated/idl/HTMLTableColElement.js +376 -0
- package/lib/generated/idl/HTMLTableElement.js +802 -0
- package/lib/generated/idl/HTMLTableRowElement.js +417 -0
- package/lib/generated/idl/HTMLTableSectionElement.js +349 -0
- package/lib/generated/idl/HTMLTemplateElement.js +124 -0
- package/lib/generated/idl/HTMLTextAreaElement.js +1210 -0
- package/lib/generated/idl/HTMLTimeElement.js +156 -0
- package/lib/generated/idl/HTMLTitleElement.js +155 -0
- package/lib/generated/idl/HTMLTrackElement.js +366 -0
- package/lib/generated/idl/HTMLUListElement.js +200 -0
- package/lib/generated/idl/HTMLUnknownElement.js +109 -0
- package/lib/generated/idl/HTMLVideoElement.js +344 -0
- package/lib/generated/idl/HashChangeEvent.js +157 -0
- package/lib/generated/idl/HashChangeEventInit.js +50 -0
- package/lib/generated/idl/Headers.js +418 -0
- package/lib/generated/idl/History.js +266 -0
- package/lib/generated/idl/InputEvent.js +168 -0
- package/lib/generated/idl/InputEventInit.js +68 -0
- package/lib/generated/idl/KeyboardEvent.js +445 -0
- package/lib/generated/idl/KeyboardEventInit.js +116 -0
- package/lib/generated/idl/Location.js +404 -0
- package/lib/generated/idl/MessageEvent.js +317 -0
- package/lib/generated/idl/MessageEventInit.js +100 -0
- package/lib/generated/idl/MimeType.js +156 -0
- package/lib/generated/idl/MimeTypeArray.js +326 -0
- package/lib/generated/idl/MouseEvent.js +595 -0
- package/lib/generated/idl/MouseEventInit.js +189 -0
- package/lib/generated/idl/MutationCallback.js +34 -0
- package/lib/generated/idl/MutationObserver.js +178 -0
- package/lib/generated/idl/MutationObserverInit.js +121 -0
- package/lib/generated/idl/MutationRecord.js +229 -0
- package/lib/generated/idl/NamedNodeMap.js +529 -0
- package/lib/generated/idl/Navigator.js +326 -0
- package/lib/generated/idl/Node.js +765 -0
- package/lib/generated/idl/NodeFilter.js +75 -0
- package/lib/generated/idl/NodeIterator.js +207 -0
- package/lib/generated/idl/NodeList.js +302 -0
- package/lib/generated/idl/OnBeforeUnloadEventHandlerNonNull.js +42 -0
- package/lib/generated/idl/OnErrorEventHandlerNonNull.js +56 -0
- package/lib/generated/idl/PageTransitionEvent.js +144 -0
- package/lib/generated/idl/PageTransitionEventInit.js +35 -0
- package/lib/generated/idl/Performance.js +142 -0
- package/lib/generated/idl/Plugin.js +359 -0
- package/lib/generated/idl/PluginArray.js +336 -0
- package/lib/generated/idl/PointerEvent.js +324 -0
- package/lib/generated/idl/PointerEventInit.js +241 -0
- package/lib/generated/idl/PopStateEvent.js +144 -0
- package/lib/generated/idl/PopStateEventInit.js +32 -0
- package/lib/generated/idl/ProcessingInstruction.js +122 -0
- package/lib/generated/idl/ProgressEvent.js +170 -0
- package/lib/generated/idl/ProgressEventInit.js +65 -0
- package/lib/generated/idl/PromiseRejectionEvent.js +157 -0
- package/lib/generated/idl/PromiseRejectionEventInit.js +42 -0
- package/lib/generated/idl/RadioNodeList.js +296 -0
- package/lib/generated/idl/Range.js +643 -0
- package/lib/generated/idl/SVGAnimatedPreserveAspectRatio.js +136 -0
- package/lib/generated/idl/SVGAnimatedRect.js +136 -0
- package/lib/generated/idl/SVGAnimatedString.js +149 -0
- package/lib/generated/idl/SVGBoundingBoxOptions.js +64 -0
- package/lib/generated/idl/SVGDefsElement.js +109 -0
- package/lib/generated/idl/SVGDescElement.js +109 -0
- package/lib/generated/idl/SVGElement.js +3157 -0
- package/lib/generated/idl/SVGGElement.js +109 -0
- package/lib/generated/idl/SVGGraphicsElement.js +139 -0
- package/lib/generated/idl/SVGMetadataElement.js +109 -0
- package/lib/generated/idl/SVGNumber.js +132 -0
- package/lib/generated/idl/SVGPreserveAspectRatio.js +196 -0
- package/lib/generated/idl/SVGRect.js +210 -0
- package/lib/generated/idl/SVGSVGElement.js +786 -0
- package/lib/generated/idl/SVGStringList.js +511 -0
- package/lib/generated/idl/SVGSwitchElement.js +109 -0
- package/lib/generated/idl/SVGSymbolElement.js +146 -0
- package/lib/generated/idl/SVGTitleElement.js +109 -0
- package/lib/generated/idl/Screen.js +180 -0
- package/lib/generated/idl/ScrollBehavior.js +12 -0
- package/lib/generated/idl/ScrollIntoViewOptions.js +45 -0
- package/lib/generated/idl/ScrollLogicalPosition.js +14 -0
- package/lib/generated/idl/ScrollOptions.js +30 -0
- package/lib/generated/idl/ScrollRestoration.js +12 -0
- package/lib/generated/idl/Selection.js +571 -0
- package/lib/generated/idl/SelectionMode.js +12 -0
- package/lib/generated/idl/ShadowRoot.js +189 -0
- package/lib/generated/idl/ShadowRootInit.js +30 -0
- package/lib/generated/idl/ShadowRootMode.js +12 -0
- package/lib/generated/idl/StaticRange.js +123 -0
- package/lib/generated/idl/StaticRangeInit.js +72 -0
- package/lib/generated/idl/Storage.js +397 -0
- package/lib/generated/idl/StorageEvent.js +318 -0
- package/lib/generated/idl/StorageEventInit.js +99 -0
- package/lib/generated/idl/StyleSheetList.js +300 -0
- package/lib/generated/idl/SubmitEvent.js +144 -0
- package/lib/generated/idl/SubmitEventInit.js +36 -0
- package/lib/generated/idl/SupportedType.js +18 -0
- package/lib/generated/idl/Text.js +170 -0
- package/lib/generated/idl/TextDecodeOptions.js +28 -0
- package/lib/generated/idl/TextDecoder.js +211 -0
- package/lib/generated/idl/TextDecoderOptions.js +43 -0
- package/lib/generated/idl/TextEncoder.js +176 -0
- package/lib/generated/idl/TextEncoderEncodeIntoResult.js +42 -0
- package/lib/generated/idl/TextTrackKind.js +12 -0
- package/lib/generated/idl/TouchEvent.js +222 -0
- package/lib/generated/idl/TouchEventInit.js +89 -0
- package/lib/generated/idl/TransitionEvent.js +170 -0
- package/lib/generated/idl/TransitionEventInit.js +65 -0
- package/lib/generated/idl/TreeWalker.js +255 -0
- package/lib/generated/idl/UIEvent.js +235 -0
- package/lib/generated/idl/UIEventInit.js +62 -0
- package/lib/generated/idl/ValidityState.js +249 -0
- package/lib/generated/idl/VisibilityState.js +12 -0
- package/lib/generated/idl/VoidFunction.js +26 -0
- package/lib/generated/idl/WebSocket.js +480 -0
- package/lib/generated/idl/WheelEvent.js +191 -0
- package/lib/generated/idl/WheelEventInit.js +71 -0
- package/lib/generated/idl/XMLDocument.js +109 -0
- package/lib/generated/idl/XMLHttpRequest.js +663 -0
- package/lib/generated/idl/XMLHttpRequestEventTarget.js +334 -0
- package/lib/generated/idl/XMLHttpRequestResponseType.js +14 -0
- package/lib/generated/idl/XMLHttpRequestUpload.js +109 -0
- package/lib/generated/idl/XMLSerializer.js +132 -0
- package/lib/generated/idl/utils.js +252 -0
- package/lib/generated/js-globals.json +332 -0
- package/lib/jsdom/browser/Window.js +952 -0
- package/lib/jsdom/browser/default-stylesheet.css +415 -0
- package/lib/jsdom/browser/not-implemented.js +20 -0
- package/lib/jsdom/browser/parser/html.js +208 -0
- package/lib/jsdom/browser/parser/index.js +37 -0
- package/lib/jsdom/browser/parser/xml.js +202 -0
- package/lib/jsdom/browser/resources/async-resource-queue.js +114 -0
- package/lib/jsdom/browser/resources/decompress-interceptor.js +184 -0
- package/lib/jsdom/browser/resources/jsdom-dispatcher.js +756 -0
- package/lib/jsdom/browser/resources/per-document-resource-loader.js +116 -0
- package/lib/jsdom/browser/resources/request-interceptor.js +171 -0
- package/lib/jsdom/browser/resources/request-manager.js +33 -0
- package/lib/jsdom/browser/resources/resource-queue.js +142 -0
- package/lib/jsdom/browser/resources/stream-handler.js +89 -0
- package/lib/jsdom/level2/style.js +65 -0
- package/lib/jsdom/level3/xpath.js +1875 -0
- package/lib/jsdom/living/aborting/AbortController-impl.js +17 -0
- package/lib/jsdom/living/aborting/AbortSignal-impl.js +131 -0
- package/lib/jsdom/living/attributes/Attr-impl.js +60 -0
- package/lib/jsdom/living/attributes/NamedNodeMap-impl.js +78 -0
- package/lib/jsdom/living/attributes.js +312 -0
- package/lib/jsdom/living/constraint-validation/DefaultConstraintValidation-impl.js +75 -0
- package/lib/jsdom/living/constraint-validation/ValidityState-impl.js +66 -0
- package/lib/jsdom/living/crypto/Crypto-impl.js +68 -0
- package/lib/jsdom/living/cssom/StyleSheetList-impl.js +38 -0
- package/lib/jsdom/living/custom-elements/CustomElementRegistry-impl.js +279 -0
- package/lib/jsdom/living/custom-elements/ElementInternals-impl.js +56 -0
- package/lib/jsdom/living/deviceorientation/DeviceMotionEventAcceleration-impl.js +7 -0
- package/lib/jsdom/living/deviceorientation/DeviceMotionEventRotationRate-impl.js +7 -0
- package/lib/jsdom/living/documents.js +15 -0
- package/lib/jsdom/living/domparsing/DOMParser-impl.js +59 -0
- package/lib/jsdom/living/domparsing/InnerHTML-impl.js +30 -0
- package/lib/jsdom/living/domparsing/XMLSerializer-impl.js +18 -0
- package/lib/jsdom/living/domparsing/parse5-adapter-serialization.js +63 -0
- package/lib/jsdom/living/domparsing/serialization.js +36 -0
- package/lib/jsdom/living/encoding/TextDecoder-impl.js +25 -0
- package/lib/jsdom/living/encoding/TextEncoder-impl.js +26 -0
- package/lib/jsdom/living/events/BeforeUnloadEvent-impl.js +12 -0
- package/lib/jsdom/living/events/BlobEvent-impl.js +14 -0
- package/lib/jsdom/living/events/CloseEvent-impl.js +10 -0
- package/lib/jsdom/living/events/CompositionEvent-impl.js +20 -0
- package/lib/jsdom/living/events/CustomEvent-impl.js +21 -0
- package/lib/jsdom/living/events/DeviceMotionEvent-impl.js +49 -0
- package/lib/jsdom/living/events/DeviceOrientationEvent-impl.js +10 -0
- package/lib/jsdom/living/events/ErrorEvent-impl.js +14 -0
- package/lib/jsdom/living/events/Event-impl.js +195 -0
- package/lib/jsdom/living/events/EventModifierMixin-impl.js +24 -0
- package/lib/jsdom/living/events/EventTarget-impl.js +438 -0
- package/lib/jsdom/living/events/FocusEvent-impl.js +9 -0
- package/lib/jsdom/living/events/HashChangeEvent-impl.js +14 -0
- package/lib/jsdom/living/events/InputEvent-impl.js +11 -0
- package/lib/jsdom/living/events/KeyboardEvent-impl.js +29 -0
- package/lib/jsdom/living/events/MessageEvent-impl.js +25 -0
- package/lib/jsdom/living/events/MouseEvent-impl.js +72 -0
- package/lib/jsdom/living/events/PageTransitionEvent-impl.js +20 -0
- package/lib/jsdom/living/events/PointerEvent-impl.js +21 -0
- package/lib/jsdom/living/events/PopStateEvent-impl.js +9 -0
- package/lib/jsdom/living/events/ProgressEvent-impl.js +14 -0
- package/lib/jsdom/living/events/PromiseRejectionEvent-impl.js +14 -0
- package/lib/jsdom/living/events/StorageEvent-impl.js +26 -0
- package/lib/jsdom/living/events/SubmitEvent-impl.js +13 -0
- package/lib/jsdom/living/events/TouchEvent-impl.js +14 -0
- package/lib/jsdom/living/events/TransitionEvent-impl.js +10 -0
- package/lib/jsdom/living/events/UIEvent-impl.js +43 -0
- package/lib/jsdom/living/events/WheelEvent-impl.js +12 -0
- package/lib/jsdom/living/fetch/Headers-impl.js +173 -0
- package/lib/jsdom/living/fetch/header-list.js +158 -0
- package/lib/jsdom/living/fetch/header-types.js +205 -0
- package/lib/jsdom/living/fetch/header-utils.js +26 -0
- package/lib/jsdom/living/file-api/Blob-impl.js +125 -0
- package/lib/jsdom/living/file-api/File-impl.js +12 -0
- package/lib/jsdom/living/file-api/FileList-impl.js +15 -0
- package/lib/jsdom/living/file-api/FileReader-impl.js +148 -0
- package/lib/jsdom/living/geometry/DOMRect-impl.js +39 -0
- package/lib/jsdom/living/geometry/DOMRectReadOnly-impl.js +72 -0
- package/lib/jsdom/living/helpers/binary-data.js +63 -0
- package/lib/jsdom/living/helpers/by-id-cache.js +67 -0
- package/lib/jsdom/living/helpers/colors.js +245 -0
- package/lib/jsdom/living/helpers/create-element.js +329 -0
- package/lib/jsdom/living/helpers/create-event-accessor.js +209 -0
- package/lib/jsdom/living/helpers/custom-elements.js +272 -0
- package/lib/jsdom/living/helpers/dates-and-times.js +270 -0
- package/lib/jsdom/living/helpers/details.js +15 -0
- package/lib/jsdom/living/helpers/encoding.js +60 -0
- package/lib/jsdom/living/helpers/events.js +24 -0
- package/lib/jsdom/living/helpers/focusing.js +104 -0
- package/lib/jsdom/living/helpers/form-controls.js +309 -0
- package/lib/jsdom/living/helpers/html-constructor.js +78 -0
- package/lib/jsdom/living/helpers/internal-constants.js +12 -0
- package/lib/jsdom/living/helpers/is-window.js +18 -0
- package/lib/jsdom/living/helpers/iterable-weak-set.js +48 -0
- package/lib/jsdom/living/helpers/json.js +7 -0
- package/lib/jsdom/living/helpers/mutation-observers.js +198 -0
- package/lib/jsdom/living/helpers/namespaces.js +16 -0
- package/lib/jsdom/living/helpers/node.js +108 -0
- package/lib/jsdom/living/helpers/number-and-date-inputs.js +195 -0
- package/lib/jsdom/living/helpers/ordered-set.js +104 -0
- package/lib/jsdom/living/helpers/page-transition-event.js +9 -0
- package/lib/jsdom/living/helpers/runtime-script-errors.js +75 -0
- package/lib/jsdom/living/helpers/shadow-dom.js +285 -0
- package/lib/jsdom/living/helpers/strings.js +168 -0
- package/lib/jsdom/living/helpers/style-rules.js +404 -0
- package/lib/jsdom/living/helpers/stylesheets.js +148 -0
- package/lib/jsdom/living/helpers/svg/basic-types.js +41 -0
- package/lib/jsdom/living/helpers/svg/render.js +46 -0
- package/lib/jsdom/living/helpers/system-colors.js +147 -0
- package/lib/jsdom/living/helpers/text.js +19 -0
- package/lib/jsdom/living/helpers/traversal.js +72 -0
- package/lib/jsdom/living/helpers/validate-names.js +65 -0
- package/lib/jsdom/living/hr-time/Performance-impl.js +22 -0
- package/lib/jsdom/living/interfaces.js +252 -0
- package/lib/jsdom/living/mutation-observer/MutationObserver-impl.js +95 -0
- package/lib/jsdom/living/mutation-observer/MutationRecord-impl.js +37 -0
- package/lib/jsdom/living/navigator/MimeType-impl.js +3 -0
- package/lib/jsdom/living/navigator/MimeTypeArray-impl.js +21 -0
- package/lib/jsdom/living/navigator/Navigator-impl.js +29 -0
- package/lib/jsdom/living/navigator/NavigatorConcurrentHardware-impl.js +8 -0
- package/lib/jsdom/living/navigator/NavigatorCookies-impl.js +7 -0
- package/lib/jsdom/living/navigator/NavigatorID-impl.js +37 -0
- package/lib/jsdom/living/navigator/NavigatorLanguage-impl.js +9 -0
- package/lib/jsdom/living/navigator/NavigatorOnLine-impl.js +7 -0
- package/lib/jsdom/living/navigator/NavigatorPlugins-impl.js +8 -0
- package/lib/jsdom/living/navigator/Plugin-impl.js +3 -0
- package/lib/jsdom/living/navigator/PluginArray-impl.js +23 -0
- package/lib/jsdom/living/node-document-position.js +10 -0
- package/lib/jsdom/living/node-type.js +16 -0
- package/lib/jsdom/living/node.js +331 -0
- package/lib/jsdom/living/nodes/CDATASection-impl.js +16 -0
- package/lib/jsdom/living/nodes/CharacterData-impl.js +118 -0
- package/lib/jsdom/living/nodes/ChildNode-impl.js +80 -0
- package/lib/jsdom/living/nodes/Comment-impl.js +20 -0
- package/lib/jsdom/living/nodes/DOMImplementation-impl.js +120 -0
- package/lib/jsdom/living/nodes/DOMStringMap-impl.js +64 -0
- package/lib/jsdom/living/nodes/DOMTokenList-impl.js +171 -0
- package/lib/jsdom/living/nodes/Document-impl.js +1000 -0
- package/lib/jsdom/living/nodes/DocumentFragment-impl.js +44 -0
- package/lib/jsdom/living/nodes/DocumentOrShadowRoot-impl.js +28 -0
- package/lib/jsdom/living/nodes/DocumentType-impl.js +24 -0
- package/lib/jsdom/living/nodes/Element-impl.js +584 -0
- package/lib/jsdom/living/nodes/ElementCSSInlineStyle-impl.js +22 -0
- package/lib/jsdom/living/nodes/ElementContentEditable-impl.js +7 -0
- package/lib/jsdom/living/nodes/GlobalEventHandlers-impl.js +81 -0
- package/lib/jsdom/living/nodes/HTMLAnchorElement-impl.js +50 -0
- package/lib/jsdom/living/nodes/HTMLAreaElement-impl.js +43 -0
- package/lib/jsdom/living/nodes/HTMLAudioElement-impl.js +9 -0
- package/lib/jsdom/living/nodes/HTMLBRElement-impl.js +9 -0
- package/lib/jsdom/living/nodes/HTMLBaseElement-impl.js +44 -0
- package/lib/jsdom/living/nodes/HTMLBodyElement-impl.js +12 -0
- package/lib/jsdom/living/nodes/HTMLButtonElement-impl.js +79 -0
- package/lib/jsdom/living/nodes/HTMLCanvasElement-impl.js +136 -0
- package/lib/jsdom/living/nodes/HTMLCollection-impl.js +96 -0
- package/lib/jsdom/living/nodes/HTMLDListElement-impl.js +9 -0
- package/lib/jsdom/living/nodes/HTMLDataElement-impl.js +9 -0
- package/lib/jsdom/living/nodes/HTMLDataListElement-impl.js +20 -0
- package/lib/jsdom/living/nodes/HTMLDetailsElement-impl.js +35 -0
- package/lib/jsdom/living/nodes/HTMLDialogElement-impl.js +9 -0
- package/lib/jsdom/living/nodes/HTMLDirectoryElement-impl.js +9 -0
- package/lib/jsdom/living/nodes/HTMLDivElement-impl.js +9 -0
- package/lib/jsdom/living/nodes/HTMLElement-impl.js +211 -0
- package/lib/jsdom/living/nodes/HTMLEmbedElement-impl.js +8 -0
- package/lib/jsdom/living/nodes/HTMLFieldSetElement-impl.js +43 -0
- package/lib/jsdom/living/nodes/HTMLFontElement-impl.js +9 -0
- package/lib/jsdom/living/nodes/HTMLFormControlsCollection-impl.js +33 -0
- package/lib/jsdom/living/nodes/HTMLFormElement-impl.js +248 -0
- package/lib/jsdom/living/nodes/HTMLFrameElement-impl.js +266 -0
- package/lib/jsdom/living/nodes/HTMLFrameSetElement-impl.js +12 -0
- package/lib/jsdom/living/nodes/HTMLHRElement-impl.js +9 -0
- package/lib/jsdom/living/nodes/HTMLHeadElement-impl.js +9 -0
- package/lib/jsdom/living/nodes/HTMLHeadingElement-impl.js +9 -0
- package/lib/jsdom/living/nodes/HTMLHtmlElement-impl.js +9 -0
- package/lib/jsdom/living/nodes/HTMLHyperlinkElementUtils-impl.js +368 -0
- package/lib/jsdom/living/nodes/HTMLIFrameElement-impl.js +9 -0
- package/lib/jsdom/living/nodes/HTMLImageElement-impl.js +127 -0
- package/lib/jsdom/living/nodes/HTMLInputElement-impl.js +1097 -0
- package/lib/jsdom/living/nodes/HTMLLIElement-impl.js +9 -0
- package/lib/jsdom/living/nodes/HTMLLabelElement-impl.js +94 -0
- package/lib/jsdom/living/nodes/HTMLLegendElement-impl.js +18 -0
- package/lib/jsdom/living/nodes/HTMLLinkElement-impl.js +107 -0
- package/lib/jsdom/living/nodes/HTMLMapElement-impl.js +13 -0
- package/lib/jsdom/living/nodes/HTMLMarqueeElement-impl.js +9 -0
- package/lib/jsdom/living/nodes/HTMLMediaElement-impl.js +138 -0
- package/lib/jsdom/living/nodes/HTMLMenuElement-impl.js +9 -0
- package/lib/jsdom/living/nodes/HTMLMetaElement-impl.js +9 -0
- package/lib/jsdom/living/nodes/HTMLMeterElement-impl.js +180 -0
- package/lib/jsdom/living/nodes/HTMLModElement-impl.js +9 -0
- package/lib/jsdom/living/nodes/HTMLOListElement-impl.js +22 -0
- package/lib/jsdom/living/nodes/HTMLObjectElement-impl.js +26 -0
- package/lib/jsdom/living/nodes/HTMLOptGroupElement-impl.js +9 -0
- package/lib/jsdom/living/nodes/HTMLOptionElement-impl.js +146 -0
- package/lib/jsdom/living/nodes/HTMLOptionsCollection-impl.js +110 -0
- package/lib/jsdom/living/nodes/HTMLOrSVGElement-impl.js +88 -0
- package/lib/jsdom/living/nodes/HTMLOutputElement-impl.js +88 -0
- package/lib/jsdom/living/nodes/HTMLParagraphElement-impl.js +9 -0
- package/lib/jsdom/living/nodes/HTMLParamElement-impl.js +9 -0
- package/lib/jsdom/living/nodes/HTMLPictureElement-impl.js +9 -0
- package/lib/jsdom/living/nodes/HTMLPreElement-impl.js +9 -0
- package/lib/jsdom/living/nodes/HTMLProgressElement-impl.js +72 -0
- package/lib/jsdom/living/nodes/HTMLQuoteElement-impl.js +9 -0
- package/lib/jsdom/living/nodes/HTMLScriptElement-impl.js +255 -0
- package/lib/jsdom/living/nodes/HTMLSelectElement-impl.js +283 -0
- package/lib/jsdom/living/nodes/HTMLSlotElement-impl.js +59 -0
- package/lib/jsdom/living/nodes/HTMLSourceElement-impl.js +8 -0
- package/lib/jsdom/living/nodes/HTMLSpanElement-impl.js +9 -0
- package/lib/jsdom/living/nodes/HTMLStyleElement-impl.js +76 -0
- package/lib/jsdom/living/nodes/HTMLTableCaptionElement-impl.js +9 -0
- package/lib/jsdom/living/nodes/HTMLTableCellElement-impl.js +73 -0
- package/lib/jsdom/living/nodes/HTMLTableColElement-impl.js +9 -0
- package/lib/jsdom/living/nodes/HTMLTableElement-impl.js +236 -0
- package/lib/jsdom/living/nodes/HTMLTableRowElement-impl.js +88 -0
- package/lib/jsdom/living/nodes/HTMLTableSectionElement-impl.js +61 -0
- package/lib/jsdom/living/nodes/HTMLTemplateElement-impl.js +67 -0
- package/lib/jsdom/living/nodes/HTMLTextAreaElement-impl.js +244 -0
- package/lib/jsdom/living/nodes/HTMLTimeElement-impl.js +9 -0
- package/lib/jsdom/living/nodes/HTMLTitleElement-impl.js +18 -0
- package/lib/jsdom/living/nodes/HTMLTrackElement-impl.js +13 -0
- package/lib/jsdom/living/nodes/HTMLUListElement-impl.js +9 -0
- package/lib/jsdom/living/nodes/HTMLUnknownElement-impl.js +9 -0
- package/lib/jsdom/living/nodes/HTMLVideoElement-impl.js +17 -0
- package/lib/jsdom/living/nodes/LinkStyle-impl.js +2 -0
- package/lib/jsdom/living/nodes/Node-impl.js +1179 -0
- package/lib/jsdom/living/nodes/NodeList-impl.js +43 -0
- package/lib/jsdom/living/nodes/NonDocumentTypeChildNode-impl.js +28 -0
- package/lib/jsdom/living/nodes/NonElementParentNode-impl.js +11 -0
- package/lib/jsdom/living/nodes/ParentNode-impl.js +90 -0
- package/lib/jsdom/living/nodes/ProcessingInstruction-impl.js +22 -0
- package/lib/jsdom/living/nodes/RadioNodeList-impl.js +49 -0
- package/lib/jsdom/living/nodes/SVGDefsElement-impl.js +9 -0
- package/lib/jsdom/living/nodes/SVGDescElement-impl.js +9 -0
- package/lib/jsdom/living/nodes/SVGElement-impl.js +64 -0
- package/lib/jsdom/living/nodes/SVGGElement-impl.js +9 -0
- package/lib/jsdom/living/nodes/SVGGraphicsElement-impl.js +16 -0
- package/lib/jsdom/living/nodes/SVGMetadataElement-impl.js +9 -0
- package/lib/jsdom/living/nodes/SVGSVGElement-impl.js +40 -0
- package/lib/jsdom/living/nodes/SVGSwitchElement-impl.js +9 -0
- package/lib/jsdom/living/nodes/SVGSymbolElement-impl.js +9 -0
- package/lib/jsdom/living/nodes/SVGTests-impl.js +42 -0
- package/lib/jsdom/living/nodes/SVGTitleElement-impl.js +9 -0
- package/lib/jsdom/living/nodes/ShadowRoot-impl.js +41 -0
- package/lib/jsdom/living/nodes/Slotable-impl.js +48 -0
- package/lib/jsdom/living/nodes/Text-impl.js +96 -0
- package/lib/jsdom/living/nodes/WindowEventHandlers-impl.js +14 -0
- package/lib/jsdom/living/nodes/XMLDocument-impl.js +4 -0
- package/lib/jsdom/living/range/AbstractRange-impl.js +43 -0
- package/lib/jsdom/living/range/Range-impl.js +902 -0
- package/lib/jsdom/living/range/StaticRange-impl.js +39 -0
- package/lib/jsdom/living/range/boundary-point.js +47 -0
- package/lib/jsdom/living/selection/Selection-impl.js +358 -0
- package/lib/jsdom/living/svg/SVGAnimatedPreserveAspectRatio-impl.js +24 -0
- package/lib/jsdom/living/svg/SVGAnimatedRect-impl.js +122 -0
- package/lib/jsdom/living/svg/SVGAnimatedString-impl.js +42 -0
- package/lib/jsdom/living/svg/SVGListBase.js +195 -0
- package/lib/jsdom/living/svg/SVGNumber-impl.js +48 -0
- package/lib/jsdom/living/svg/SVGPreserveAspectRatio-impl.js +100 -0
- package/lib/jsdom/living/svg/SVGRect-impl.js +143 -0
- package/lib/jsdom/living/svg/SVGStringList-impl.js +16 -0
- package/lib/jsdom/living/traversal/NodeIterator-impl.js +107 -0
- package/lib/jsdom/living/traversal/TreeWalker-impl.js +217 -0
- package/lib/jsdom/living/traversal/helpers.js +44 -0
- package/lib/jsdom/living/webidl/DOMException-impl.js +46 -0
- package/lib/jsdom/living/websockets/WebSocket-impl.js +211 -0
- package/lib/jsdom/living/webstorage/Storage-impl.js +102 -0
- package/lib/jsdom/living/window/BarProp-impl.js +10 -0
- package/lib/jsdom/living/window/External-impl.js +9 -0
- package/lib/jsdom/living/window/History-impl.js +148 -0
- package/lib/jsdom/living/window/Location-impl.js +227 -0
- package/lib/jsdom/living/window/Screen-impl.js +13 -0
- package/lib/jsdom/living/window/SessionHistory.js +163 -0
- package/lib/jsdom/living/window/navigation.js +87 -0
- package/lib/jsdom/living/window-properties.js +241 -0
- package/lib/jsdom/living/xhr/FormData-impl.js +191 -0
- package/lib/jsdom/living/xhr/XMLHttpRequest-impl.js +1057 -0
- package/lib/jsdom/living/xhr/XMLHttpRequestEventTarget-impl.js +17 -0
- package/lib/jsdom/living/xhr/XMLHttpRequestUpload-impl.js +4 -0
- package/lib/jsdom/living/xhr/multipart-form-data.js +99 -0
- package/lib/jsdom/living/xhr/xhr-sync-worker.js +51 -0
- package/lib/jsdom/living/xhr/xhr-utils.js +206 -0
- package/lib/jsdom/utils.js +105 -0
- package/lib/jsdom/virtual-console.js +46 -0
- package/package.json +143 -0
|
@@ -0,0 +1,756 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
const fs = require("fs");
|
|
3
|
+
const { Readable } = require("stream");
|
|
4
|
+
const { fileURLToPath } = require("url");
|
|
5
|
+
const { parseURL, serializeURL, serializeURLOrigin, serializePath } = require("whatwg-url");
|
|
6
|
+
const dataURLFromRecord = require("data-urls").fromURLRecord;
|
|
7
|
+
const { Dispatcher } = require("undici");
|
|
8
|
+
const WrapHandler = require("undici/lib/handler/wrap-handler.js");
|
|
9
|
+
const UnwrapHandler = require("undici/lib/handler/unwrap-handler.js");
|
|
10
|
+
const { toBase64 } = require("@exodus/bytes/base64.js");
|
|
11
|
+
const { utf8Encode } = require("../../living/helpers/encoding");
|
|
12
|
+
const { sendStreamResponse } = require("./stream-handler");
|
|
13
|
+
|
|
14
|
+
const packageVersion = require("../../../../package.json").version;
|
|
15
|
+
|
|
16
|
+
const DEFAULT_USER_AGENT = `Mozilla/5.0 (${process.platform || "unknown OS"}) AppleWebKit/537.36 ` +
|
|
17
|
+
`(KHTML, like Gecko) jsdom/${packageVersion}`;
|
|
18
|
+
const MAX_REDIRECTS = 20;
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* JSDOMDispatcher - Full undici Dispatcher implementation for jsdom.
|
|
22
|
+
*
|
|
23
|
+
* Handles:
|
|
24
|
+
* - data: URLs (decode and return)
|
|
25
|
+
* - file: URLs (read from filesystem)
|
|
26
|
+
* - HTTP(S) and web sockets: follows redirects manually, capturing cookies at each hop
|
|
27
|
+
*
|
|
28
|
+
* Callers should provide the expected opaque fields when possible, to ensure that various parts of the jsdom pipeline
|
|
29
|
+
* have enough information. See the `dispatch()` JSDoc for details.
|
|
30
|
+
*/
|
|
31
|
+
class JSDOMDispatcher extends Dispatcher {
|
|
32
|
+
#baseDispatcher;
|
|
33
|
+
#cookieJar;
|
|
34
|
+
#userAgent;
|
|
35
|
+
#userInterceptors;
|
|
36
|
+
|
|
37
|
+
constructor({ baseDispatcher, cookieJar, userAgent, userInterceptors = [] }) {
|
|
38
|
+
super();
|
|
39
|
+
this.#baseDispatcher = baseDispatcher;
|
|
40
|
+
this.#cookieJar = cookieJar;
|
|
41
|
+
this.#userAgent = userAgent || DEFAULT_USER_AGENT;
|
|
42
|
+
this.#userInterceptors = userInterceptors;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Dispatch a request through the jsdom resource loading pipeline.
|
|
47
|
+
*
|
|
48
|
+
* Vaguely corresponds to:
|
|
49
|
+
* - https://fetch.spec.whatwg.org/#concept-fetch: in theory, all jsdom fetches should go through here, like all web
|
|
50
|
+
* platform fetches go through #concept-fetch.
|
|
51
|
+
* - https://fetch.spec.whatwg.org/#concept-scheme-fetch: the code is more like skipping straight to scheme fetch.
|
|
52
|
+
*
|
|
53
|
+
* @param {object} opts - undici dispatch options
|
|
54
|
+
* @param {object} [opts.opaque] - jsdom-specific request context (may be undefined for WebSocket upgrades)
|
|
55
|
+
* @param {Element|null} opts.opaque.element - DOM element that triggered the request
|
|
56
|
+
* @param {string} opts.opaque.url - Full request URL (since we cannot reconstruct it from `opts.origin + opts.path`
|
|
57
|
+
* for `file:` URLs). If given, `opts.origin`, `opts.path`, and `opts.query` are ignored.
|
|
58
|
+
* @param {string} [opts.opaque.origin] - Request origin for CORS (used by XHR)
|
|
59
|
+
* @param {boolean} [opts.opaque.corsMode] - Enable CORS validation during redirects (used by XHR)
|
|
60
|
+
* @param {boolean} [opts.opaque.withCredentials] - Include cookies cross-origin (used by XHR)
|
|
61
|
+
* @param {Object} [opts.opaque.auth] - Auth credentials {user, pass} for 401 Basic auth handling
|
|
62
|
+
* @param {Object} [opts.opaque.preflight] - If present, do CORS preflight before main request
|
|
63
|
+
* @param {string[]} [opts.opaque.preflight.unsafeHeaders] - Non-simple headers that need to be allowed
|
|
64
|
+
* @param {object} handler - undici handler
|
|
65
|
+
*/
|
|
66
|
+
dispatch(opts, handler) {
|
|
67
|
+
// Wrap handler to normalize OLD API (onConnect/onHeaders/onData/onComplete/onError) to NEW API
|
|
68
|
+
// (onRequestStart/onResponseStart/onResponseData/onResponseEnd/onResponseError). This is necessary because undici's
|
|
69
|
+
// internals call the old API a lot, despite it being undocumented:
|
|
70
|
+
// * https://github.com/nodejs/undici/issues/4771
|
|
71
|
+
// * https://github.com/nodejs/undici/issues/4780
|
|
72
|
+
const wrappedHandler = WrapHandler.wrap(handler);
|
|
73
|
+
|
|
74
|
+
// Get URL from opaque if present (required for file: URLs since they have origin "null"),
|
|
75
|
+
// otherwise reconstruct from opts.origin + opts.path (works for http/https/ws/wss)
|
|
76
|
+
const urlString = opts.opaque?.url || (opts.origin + opts.path);
|
|
77
|
+
const urlRecord = parseURL(urlString);
|
|
78
|
+
|
|
79
|
+
if (urlRecord === null) {
|
|
80
|
+
wrappedHandler.onResponseError?.(null, new TypeError(`Invalid URL: ${urlString}`));
|
|
81
|
+
return false;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (urlRecord.scheme === "data") {
|
|
85
|
+
return this.#dispatchDataURL(urlRecord, wrappedHandler);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (urlRecord.scheme === "file") {
|
|
89
|
+
return this.#dispatchFileURL(urlRecord, wrappedHandler);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// HTTP(S) - handles redirects, CORS, preflight, and WebSocket upgrades.
|
|
93
|
+
// #dispatchHTTP is async but we can't await it here (dispatch() must be sync per the undici Dispatcher API).
|
|
94
|
+
// Catch rejections so that errors (e.g. from the undici dispatch chain throwing) are forwarded to the handler
|
|
95
|
+
// instead of becoming unhandled rejections that silently prevent the response promise from ever resolving.
|
|
96
|
+
this.#dispatchHTTP(urlRecord, wrappedHandler, opts).catch(err => {
|
|
97
|
+
wrappedHandler.onResponseError?.(null, err);
|
|
98
|
+
});
|
|
99
|
+
return true;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Handle `data:` URLs by decoding them and returning the body.
|
|
104
|
+
*
|
|
105
|
+
* Corresponds fairly directly to https://fetch.spec.whatwg.org/#concept-scheme-fetch's "data" scheme case.
|
|
106
|
+
*/
|
|
107
|
+
#dispatchDataURL(urlRecord, handler) {
|
|
108
|
+
const dataURL = dataURLFromRecord(urlRecord);
|
|
109
|
+
if (dataURL === null) {
|
|
110
|
+
const error = new TypeError("Invalid data: URL");
|
|
111
|
+
handler.onResponseError?.(null, error);
|
|
112
|
+
return false;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const stream = Readable.from([dataURL.body]);
|
|
116
|
+
|
|
117
|
+
sendStreamResponse(handler, stream, {
|
|
118
|
+
status: 200,
|
|
119
|
+
statusText: "OK",
|
|
120
|
+
headers: { "content-type": dataURL.mimeType.toString() },
|
|
121
|
+
context: { finalURL: urlRecord }
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
return true;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Handle `file:` URLs by reading from the filesystem.
|
|
129
|
+
*
|
|
130
|
+
* Corresponds fairly directly to https://fetch.spec.whatwg.org/#concept-scheme-fetch's "file" scheme case.
|
|
131
|
+
*/
|
|
132
|
+
#dispatchFileURL(urlRecord, handler) {
|
|
133
|
+
const filePath = fileURLToPath(serializeURL(urlRecord));
|
|
134
|
+
const stream = fs.createReadStream(filePath);
|
|
135
|
+
|
|
136
|
+
sendStreamResponse(handler, stream, {
|
|
137
|
+
status: 200,
|
|
138
|
+
statusText: "OK",
|
|
139
|
+
context: { finalURL: urlRecord }
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
return true;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* High-level HTTP(S) fetch with redirect handling, CORS validation, and preflight.
|
|
147
|
+
*
|
|
148
|
+
* Corresponds roughly to https://fetch.spec.whatwg.org/#concept-http-fetch, although some parts of
|
|
149
|
+
* https://fetch.spec.whatwg.org/#concept-fetch also live here.
|
|
150
|
+
*/
|
|
151
|
+
async #dispatchHTTP(urlRecord, handler, opts) {
|
|
152
|
+
const { corsMode, origin, withCredentials, auth, preflight } = opts.opaque || {};
|
|
153
|
+
const requestFragment = urlRecord.fragment;
|
|
154
|
+
let currentURL = urlRecord;
|
|
155
|
+
let currentMethod = opts.method || "GET";
|
|
156
|
+
let currentBody = opts.body ?? null;
|
|
157
|
+
const currentHeaders = { ...this.#normalizeHeadersToObject(opts.headers) };
|
|
158
|
+
let effectiveOrigin = origin; // CORS tracking - may become "null" after cross-origin redirects
|
|
159
|
+
let receivedAuthChallenge = false;
|
|
160
|
+
const ctx = { finalURL: null };
|
|
161
|
+
|
|
162
|
+
// Create a proxy controller that forwards to the current underlying controller.
|
|
163
|
+
// This provides a stable reference across redirect hops.
|
|
164
|
+
let currentController;
|
|
165
|
+
let onRequestStartCalled = false;
|
|
166
|
+
const proxyController = {
|
|
167
|
+
abort(reason) {
|
|
168
|
+
currentController.abort(reason);
|
|
169
|
+
},
|
|
170
|
+
pause() {
|
|
171
|
+
currentController.pause();
|
|
172
|
+
},
|
|
173
|
+
resume() {
|
|
174
|
+
currentController.resume();
|
|
175
|
+
},
|
|
176
|
+
get paused() {
|
|
177
|
+
return currentController.paused;
|
|
178
|
+
},
|
|
179
|
+
get aborted() {
|
|
180
|
+
return currentController.aborted;
|
|
181
|
+
},
|
|
182
|
+
get reason() {
|
|
183
|
+
return currentController.reason;
|
|
184
|
+
}
|
|
185
|
+
};
|
|
186
|
+
|
|
187
|
+
// Callback for #doSingleRequest to invoke when a controller becomes available
|
|
188
|
+
function onControllerReady(controller) {
|
|
189
|
+
currentController = controller;
|
|
190
|
+
if (!onRequestStartCalled) {
|
|
191
|
+
onRequestStartCalled = true;
|
|
192
|
+
handler.onRequestStart?.(proxyController, ctx);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// Handle CORS preflight if needed
|
|
197
|
+
if (preflight) {
|
|
198
|
+
const preflightHeaders = {
|
|
199
|
+
Origin: origin
|
|
200
|
+
};
|
|
201
|
+
preflightHeaders["Access-Control-Request-Method"] = currentMethod;
|
|
202
|
+
if (preflight.unsafeHeaders?.length > 0) {
|
|
203
|
+
preflightHeaders["Access-Control-Request-Headers"] = preflight.unsafeHeaders.join(", ");
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
const preflightResult = await this.#doSingleRequest(
|
|
207
|
+
currentURL,
|
|
208
|
+
"OPTIONS",
|
|
209
|
+
preflightHeaders,
|
|
210
|
+
null,
|
|
211
|
+
{ ...opts.opaque, origin, withCredentials },
|
|
212
|
+
undefined, // no upgrade for preflight
|
|
213
|
+
opts,
|
|
214
|
+
onControllerReady
|
|
215
|
+
);
|
|
216
|
+
|
|
217
|
+
if (preflightResult.error) {
|
|
218
|
+
handler.onResponseError?.(null, preflightResult.error);
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// Validate preflight response status
|
|
223
|
+
if (preflightResult.status < 200 || preflightResult.status > 299) {
|
|
224
|
+
handler.onResponseError?.(null, new Error(
|
|
225
|
+
"Response for preflight has invalid HTTP status code " + preflightResult.status
|
|
226
|
+
));
|
|
227
|
+
return;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// CORS validation on preflight response
|
|
231
|
+
const acao = preflightResult.headers["access-control-allow-origin"];
|
|
232
|
+
if (acao !== "*" && acao !== origin) {
|
|
233
|
+
handler.onResponseError?.(null, new Error("Cross origin " + origin + " forbidden"));
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
if (withCredentials) {
|
|
237
|
+
const acac = preflightResult.headers["access-control-allow-credentials"];
|
|
238
|
+
if (acac !== "true") {
|
|
239
|
+
handler.onResponseError?.(null, new Error("Credentials forbidden"));
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// Validate allowed headers
|
|
245
|
+
const acahStr = preflightResult.headers["access-control-allow-headers"];
|
|
246
|
+
const acah = new Set(acahStr ? acahStr.toLowerCase().split(/,\s*/) : []);
|
|
247
|
+
if (!acah.has("*")) {
|
|
248
|
+
for (const unsafeHeader of preflight.unsafeHeaders || []) {
|
|
249
|
+
if (!acah.has(unsafeHeader.toLowerCase())) {
|
|
250
|
+
handler.onResponseError?.(null, new Error("Header " + unsafeHeader + " forbidden"));
|
|
251
|
+
return;
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// Redirect loop
|
|
258
|
+
for (let redirectCount = 0; redirectCount <= MAX_REDIRECTS; redirectCount++) {
|
|
259
|
+
ctx.finalURL = currentURL;
|
|
260
|
+
const currentOrigin = serializeURLOrigin(currentURL);
|
|
261
|
+
|
|
262
|
+
// Clone headers for this request
|
|
263
|
+
const requestHeaders = { ...currentHeaders };
|
|
264
|
+
|
|
265
|
+
// Add auth header if needed
|
|
266
|
+
if (receivedAuthChallenge && auth) {
|
|
267
|
+
const authString = `${auth.user || ""}:${auth.pass || ""}`;
|
|
268
|
+
requestHeaders.Authorization = "Basic " + toBase64(utf8Encode(authString));
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
const result = await this.#doSingleRequest(
|
|
272
|
+
currentURL,
|
|
273
|
+
currentMethod,
|
|
274
|
+
requestHeaders,
|
|
275
|
+
currentBody,
|
|
276
|
+
{ ...opts.opaque, origin, withCredentials },
|
|
277
|
+
opts.upgrade,
|
|
278
|
+
opts,
|
|
279
|
+
onControllerReady
|
|
280
|
+
);
|
|
281
|
+
|
|
282
|
+
// WebSocket upgrade
|
|
283
|
+
if (result.upgraded) {
|
|
284
|
+
handler.onRequestUpgrade?.(proxyController, result.statusCode, result.headers, result.socket);
|
|
285
|
+
return;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
if (result.error) {
|
|
289
|
+
handler.onResponseError?.(null, result.error);
|
|
290
|
+
return;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// Handle 401 auth challenge
|
|
294
|
+
if (result.status === 401 && auth && !receivedAuthChallenge) {
|
|
295
|
+
const wwwAuth = result.headers["www-authenticate"] || "";
|
|
296
|
+
if (/^Basic /i.test(wwwAuth)) {
|
|
297
|
+
receivedAuthChallenge = true;
|
|
298
|
+
continue;
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// Handle redirect
|
|
303
|
+
const { location } = result.headers;
|
|
304
|
+
if (result.status >= 300 && result.status < 400 && location) {
|
|
305
|
+
const targetURL = parseURL(location, { baseURL: currentURL });
|
|
306
|
+
if (!targetURL) {
|
|
307
|
+
handler.onResponseError?.(null, new TypeError("Invalid redirect URL"));
|
|
308
|
+
return;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
// Per fetch spec: if location's fragment is null, inherit from request
|
|
312
|
+
if (targetURL.fragment === null) {
|
|
313
|
+
targetURL.fragment = requestFragment;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
// Per fetch spec: if locationURL's scheme is not HTTP(S), return a network error
|
|
317
|
+
if (targetURL.scheme !== "http" && targetURL.scheme !== "https") {
|
|
318
|
+
handler.onResponseError?.(null, new Error("Cannot redirect to non-HTTP(S) URL"));
|
|
319
|
+
return;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
// Method change per fetch spec "HTTP-redirect fetch"
|
|
323
|
+
// 301/302 + POST → GET, 303 + non-GET/HEAD → GET
|
|
324
|
+
if (((result.status === 301 || result.status === 302) && currentMethod === "POST") ||
|
|
325
|
+
(result.status === 303 && !["GET", "HEAD"].includes(currentMethod))) {
|
|
326
|
+
currentMethod = "GET";
|
|
327
|
+
currentBody = null;
|
|
328
|
+
this.#deleteRequestHeader(currentHeaders, "content-encoding");
|
|
329
|
+
this.#deleteRequestHeader(currentHeaders, "content-language");
|
|
330
|
+
this.#deleteRequestHeader(currentHeaders, "content-location");
|
|
331
|
+
this.#deleteRequestHeader(currentHeaders, "content-type");
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
const targetOrigin = serializeURLOrigin(targetURL);
|
|
335
|
+
|
|
336
|
+
// Authorization header removal on cross-origin redirect
|
|
337
|
+
if (currentOrigin !== targetOrigin) {
|
|
338
|
+
this.#deleteRequestHeader(currentHeaders, "authorization");
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
// CORS handling for cross-origin redirects (only if origin is set, indicating XHR/fetch)
|
|
342
|
+
const targetIsCrossOrigin = origin !== undefined && origin !== targetOrigin;
|
|
343
|
+
if (corsMode || targetIsCrossOrigin) {
|
|
344
|
+
// CORS validation on redirect response (if source was cross-origin)
|
|
345
|
+
if (origin !== currentOrigin) {
|
|
346
|
+
const acao = result.headers["access-control-allow-origin"];
|
|
347
|
+
if (acao !== "*" && acao !== origin) {
|
|
348
|
+
handler.onResponseError?.(null, new Error("Cross origin " + origin + " forbidden"));
|
|
349
|
+
return;
|
|
350
|
+
}
|
|
351
|
+
if (withCredentials) {
|
|
352
|
+
const acac = result.headers["access-control-allow-credentials"];
|
|
353
|
+
if (acac !== "true") {
|
|
354
|
+
handler.onResponseError?.(null, new Error("Credentials forbidden"));
|
|
355
|
+
return;
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
// Userinfo check - forbid redirects to URLs with username/password
|
|
359
|
+
if (targetURL.username || targetURL.password) {
|
|
360
|
+
handler.onResponseError?.(null, new Error("Userinfo forbidden in cors redirect"));
|
|
361
|
+
return;
|
|
362
|
+
}
|
|
363
|
+
// Update effective origin - becomes "null" after cross-origin→cross-origin redirect
|
|
364
|
+
if (currentOrigin !== targetOrigin) {
|
|
365
|
+
effectiveOrigin = "null";
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
// Add Origin header for cross-origin target or if effective origin became "null"
|
|
370
|
+
if (targetIsCrossOrigin || effectiveOrigin === "null") {
|
|
371
|
+
currentHeaders.Origin = effectiveOrigin;
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
currentURL = targetURL;
|
|
376
|
+
continue;
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
// Final response - CORS validation (if destination is cross-origin or effective origin is "null")
|
|
380
|
+
if (origin !== undefined && (origin !== currentOrigin || effectiveOrigin === "null")) {
|
|
381
|
+
const acao = result.headers["access-control-allow-origin"];
|
|
382
|
+
if (acao !== "*" && acao !== effectiveOrigin) {
|
|
383
|
+
handler.onResponseError?.(null, new Error("Cross origin " + effectiveOrigin + " forbidden"));
|
|
384
|
+
return;
|
|
385
|
+
}
|
|
386
|
+
if (withCredentials) {
|
|
387
|
+
const acac = result.headers["access-control-allow-credentials"];
|
|
388
|
+
if (acac !== "true") {
|
|
389
|
+
handler.onResponseError?.(null, new Error("Credentials forbidden"));
|
|
390
|
+
return;
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
// Stream response to handler
|
|
396
|
+
handler.onResponseStart?.(proxyController, result.status, result.headers, result.statusText);
|
|
397
|
+
|
|
398
|
+
// Forward body chunks to handler
|
|
399
|
+
result.forwardBodyTo(handler);
|
|
400
|
+
return;
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
handler.onResponseError?.(null, new Error(`Too many redirects (max ${MAX_REDIRECTS})`));
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
/**
|
|
407
|
+
* Perform a single HTTP request (no redirects).
|
|
408
|
+
* Handles cookies based on cross-origin/credentials settings.
|
|
409
|
+
* Returns response metadata immediately, with a forwardBodyTo() method to stream the body later.
|
|
410
|
+
*
|
|
411
|
+
* For WebSocket upgrades, returns { upgraded: true, controller, statusCode, headers, socket }.
|
|
412
|
+
*
|
|
413
|
+
* Mostly corresponds to https://fetch.spec.whatwg.org/#concept-http-network-fetch.
|
|
414
|
+
*
|
|
415
|
+
* @param {object} url - URL record to request
|
|
416
|
+
* @param {string} method - HTTP method
|
|
417
|
+
* @param {object} headers - Request headers
|
|
418
|
+
* @param {*} body - Request body
|
|
419
|
+
* @param {object} opaque - jsdom opaque options
|
|
420
|
+
* @param {string} upgrade - Upgrade protocol (e.g., "websocket")
|
|
421
|
+
* @param {object} originalOpts - Original dispatch options to preserve extra undici options
|
|
422
|
+
* @param {function} onControllerReady - Callback invoked when controller is available
|
|
423
|
+
*/
|
|
424
|
+
async #doSingleRequest(url, method, headers, body, opaque, upgrade, originalOpts, onControllerReady) {
|
|
425
|
+
const { origin: requestOrigin, withCredentials } = opaque || {};
|
|
426
|
+
|
|
427
|
+
// Build headers with defaults
|
|
428
|
+
const requestHeaders = { ...headers };
|
|
429
|
+
this.#setDefaultHeaders(requestHeaders);
|
|
430
|
+
|
|
431
|
+
if (body === null && (method === "POST" || method === "PUT")) {
|
|
432
|
+
requestHeaders["Content-Length"] = "0";
|
|
433
|
+
} else if (body !== null && body.byteLength !== undefined) {
|
|
434
|
+
// The `body.byteLength !== undefined` check is equivalent to the spec case where httpRequest's body's length is
|
|
435
|
+
// null, because body is a stream.
|
|
436
|
+
requestHeaders["Content-Length"] = String(body.byteLength);
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
// Determine if this is cross-origin (for cookie handling)
|
|
440
|
+
const urlOrigin = serializeURLOrigin(url);
|
|
441
|
+
const crossOrigin = requestOrigin !== undefined && requestOrigin !== urlOrigin;
|
|
442
|
+
|
|
443
|
+
// Only handle cookies for same-origin requests, or cross-origin with credentials
|
|
444
|
+
// Don't send cookies for preflight requests
|
|
445
|
+
const isPreflight = method === "OPTIONS" &&
|
|
446
|
+
this.#hasRequestHeader(headers, "Access-Control-Request-Method");
|
|
447
|
+
const shouldHandleCookies = (!crossOrigin || withCredentials) && !isPreflight;
|
|
448
|
+
|
|
449
|
+
const urlSerialized = serializeURL(url);
|
|
450
|
+
|
|
451
|
+
if (shouldHandleCookies) {
|
|
452
|
+
const cookieString = this.#cookieJar.getCookieStringSync(urlSerialized);
|
|
453
|
+
if (cookieString) {
|
|
454
|
+
requestHeaders.Cookie = cookieString;
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
// Spread original opts to preserve extra undici options (e.g., idempotent, bodyTimeout),
|
|
459
|
+
// then override with our specific values.
|
|
460
|
+
// If opaque.url was provided, derive origin/path from it and null out query.
|
|
461
|
+
// Otherwise, pass through origin/path/query unchanged.
|
|
462
|
+
const hasOpaqueURL = opaque?.url !== undefined;
|
|
463
|
+
const dispatchOpts = {
|
|
464
|
+
...originalOpts,
|
|
465
|
+
origin: hasOpaqueURL ? urlOrigin : originalOpts.origin,
|
|
466
|
+
path: hasOpaqueURL ? serializePathForUndici(url) : originalOpts.path,
|
|
467
|
+
query: hasOpaqueURL ? null : originalOpts.query,
|
|
468
|
+
method,
|
|
469
|
+
headers: requestHeaders,
|
|
470
|
+
body,
|
|
471
|
+
upgrade,
|
|
472
|
+
opaque: { ...opaque, url: urlSerialized }
|
|
473
|
+
};
|
|
474
|
+
|
|
475
|
+
const innerDispatch = this.#buildDispatchChain();
|
|
476
|
+
|
|
477
|
+
return new Promise(resolve => {
|
|
478
|
+
let responseHeaders, streamError;
|
|
479
|
+
let bodyHandler = null;
|
|
480
|
+
let pendingChunks = [];
|
|
481
|
+
let ended = false;
|
|
482
|
+
let responseStarted = false;
|
|
483
|
+
|
|
484
|
+
innerDispatch(dispatchOpts, {
|
|
485
|
+
onRequestStart: controller => {
|
|
486
|
+
onControllerReady(controller);
|
|
487
|
+
},
|
|
488
|
+
onRequestUpgrade: (controller, statusCode, headersObj, socket) => {
|
|
489
|
+
if (controller.aborted) {
|
|
490
|
+
resolve({ error: controller.reason });
|
|
491
|
+
return;
|
|
492
|
+
}
|
|
493
|
+
if (shouldHandleCookies) {
|
|
494
|
+
this.#storeCookiesFromHeaders(headersObj, urlSerialized);
|
|
495
|
+
}
|
|
496
|
+
resolve({ upgraded: true, controller, statusCode, headers: headersObj, socket });
|
|
497
|
+
},
|
|
498
|
+
onResponseStart: (controller, statusCode, headersObj, statusText) => {
|
|
499
|
+
if (controller.aborted) {
|
|
500
|
+
resolve({ error: controller.reason });
|
|
501
|
+
return;
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
responseHeaders = headersObj;
|
|
505
|
+
responseStarted = true;
|
|
506
|
+
|
|
507
|
+
// Create a mechanism to forward body to handler later
|
|
508
|
+
function forwardBodyTo(fwdHandler) {
|
|
509
|
+
bodyHandler = fwdHandler;
|
|
510
|
+
// Forward any chunks that arrived before forwardBodyTo was called
|
|
511
|
+
for (const chunk of pendingChunks) {
|
|
512
|
+
fwdHandler.onResponseData?.(controller, chunk);
|
|
513
|
+
}
|
|
514
|
+
pendingChunks = null;
|
|
515
|
+
if (streamError) {
|
|
516
|
+
fwdHandler.onResponseError?.(controller, streamError);
|
|
517
|
+
} else if (ended) {
|
|
518
|
+
fwdHandler.onResponseEnd?.(controller, {});
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
resolve({
|
|
523
|
+
status: statusCode,
|
|
524
|
+
statusText: statusText || "",
|
|
525
|
+
headers: responseHeaders,
|
|
526
|
+
url,
|
|
527
|
+
forwardBodyTo
|
|
528
|
+
});
|
|
529
|
+
},
|
|
530
|
+
onResponseData: (controller, chunk) => {
|
|
531
|
+
if (controller.aborted) {
|
|
532
|
+
return;
|
|
533
|
+
}
|
|
534
|
+
if (bodyHandler) {
|
|
535
|
+
bodyHandler.onResponseData?.(controller, chunk);
|
|
536
|
+
} else {
|
|
537
|
+
pendingChunks.push(chunk);
|
|
538
|
+
}
|
|
539
|
+
},
|
|
540
|
+
onResponseEnd: (controller, trailers) => {
|
|
541
|
+
if (controller.aborted) {
|
|
542
|
+
if (bodyHandler) {
|
|
543
|
+
bodyHandler.onResponseError?.(controller, controller.reason);
|
|
544
|
+
} else {
|
|
545
|
+
streamError = controller.reason;
|
|
546
|
+
}
|
|
547
|
+
return;
|
|
548
|
+
}
|
|
549
|
+
if (shouldHandleCookies) {
|
|
550
|
+
this.#storeCookiesFromHeaders(responseHeaders, urlSerialized);
|
|
551
|
+
}
|
|
552
|
+
if (bodyHandler) {
|
|
553
|
+
bodyHandler.onResponseEnd?.(controller, trailers);
|
|
554
|
+
} else {
|
|
555
|
+
ended = true;
|
|
556
|
+
}
|
|
557
|
+
},
|
|
558
|
+
onResponseError: (controller, err) => {
|
|
559
|
+
if (responseStarted) {
|
|
560
|
+
// Error occurred mid-stream - forward to body handler
|
|
561
|
+
if (bodyHandler) {
|
|
562
|
+
bodyHandler.onResponseError?.(controller, err);
|
|
563
|
+
} else {
|
|
564
|
+
streamError = err;
|
|
565
|
+
}
|
|
566
|
+
} else {
|
|
567
|
+
resolve({ error: err });
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
});
|
|
571
|
+
});
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
/**
|
|
575
|
+
* Build the dispatch chain with user interceptors applied.
|
|
576
|
+
*/
|
|
577
|
+
#buildDispatchChain() {
|
|
578
|
+
// Convert handlers to old-style before passing to the base dispatcher. When Node's built-in fetch() has run,
|
|
579
|
+
// it registers a v6 undici Agent as the global dispatcher via the shared Symbol.for('undici.globalDispatcher.1').
|
|
580
|
+
// Our v7 code passes new-style handlers, which v6 rejects. Old-style handlers are accepted by both v6 and v7.
|
|
581
|
+
// See https://github.com/jsdom/jsdom/issues/4047
|
|
582
|
+
let innerDispatch = (opts, h) => {
|
|
583
|
+
return this.#baseDispatcher.dispatch(opts, UnwrapHandler.unwrap(h));
|
|
584
|
+
};
|
|
585
|
+
|
|
586
|
+
// Apply user interceptors from innermost to outermost
|
|
587
|
+
for (let i = this.#userInterceptors.length - 1; i >= 0; i--) {
|
|
588
|
+
const interceptor = this.#userInterceptors[i];
|
|
589
|
+
const nextDispatch = innerDispatch;
|
|
590
|
+
innerDispatch = (opts, h) => interceptor(nextDispatch)(opts, h);
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
return innerDispatch;
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
/**
|
|
597
|
+
* Normalize headers to an object format.
|
|
598
|
+
* Callers pass either HeaderList (iterable) or plain objects.
|
|
599
|
+
*/
|
|
600
|
+
#normalizeHeadersToObject(headers) {
|
|
601
|
+
if (!headers) {
|
|
602
|
+
return {};
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
// HeaderList has Symbol.iterator; plain objects don't
|
|
606
|
+
if (typeof headers[Symbol.iterator] === "function") {
|
|
607
|
+
const obj = {};
|
|
608
|
+
for (const [name, value] of headers) {
|
|
609
|
+
obj[name] = value;
|
|
610
|
+
}
|
|
611
|
+
return obj;
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
return { ...headers };
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
/**
|
|
618
|
+
* Check if a request header exists (case-insensitive).
|
|
619
|
+
* Request headers may have user-controlled casing.
|
|
620
|
+
*/
|
|
621
|
+
#hasRequestHeader(requestHeaders, name) {
|
|
622
|
+
const lowerName = name.toLowerCase();
|
|
623
|
+
return Object.keys(requestHeaders).some(key => key.toLowerCase() === lowerName);
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
/**
|
|
627
|
+
* Delete a request header (case-insensitive).
|
|
628
|
+
* Request headers may have user-controlled casing. Mutates the object in place.
|
|
629
|
+
*/
|
|
630
|
+
#deleteRequestHeader(requestHeaders, name) {
|
|
631
|
+
const lowerName = name.toLowerCase();
|
|
632
|
+
for (const key of Object.keys(requestHeaders)) {
|
|
633
|
+
if (key.toLowerCase() === lowerName) {
|
|
634
|
+
delete requestHeaders[key];
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
/**
|
|
640
|
+
* Set default request headers if not already present.
|
|
641
|
+
* Mutates the headers object in place.
|
|
642
|
+
*/
|
|
643
|
+
#setDefaultHeaders(requestHeaders) {
|
|
644
|
+
if (!this.#hasRequestHeader(requestHeaders, "User-Agent")) {
|
|
645
|
+
requestHeaders["User-Agent"] = this.#userAgent;
|
|
646
|
+
}
|
|
647
|
+
if (!this.#hasRequestHeader(requestHeaders, "Accept-Language")) {
|
|
648
|
+
requestHeaders["Accept-Language"] = "en";
|
|
649
|
+
}
|
|
650
|
+
if (!this.#hasRequestHeader(requestHeaders, "Accept")) {
|
|
651
|
+
requestHeaders.Accept = "*/*";
|
|
652
|
+
}
|
|
653
|
+
if (!this.#hasRequestHeader(requestHeaders, "Accept-Encoding")) {
|
|
654
|
+
requestHeaders["Accept-Encoding"] = "gzip, deflate";
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
/**
|
|
659
|
+
* Extract and store cookies from response headers.
|
|
660
|
+
*/
|
|
661
|
+
#storeCookiesFromHeaders(headers, url) {
|
|
662
|
+
if (!headers["set-cookie"]) {
|
|
663
|
+
return;
|
|
664
|
+
}
|
|
665
|
+
const cookies = Array.isArray(headers["set-cookie"]) ?
|
|
666
|
+
headers["set-cookie"] :
|
|
667
|
+
[headers["set-cookie"]];
|
|
668
|
+
for (const cookie of cookies) {
|
|
669
|
+
this.#cookieJar.setCookieSync(cookie, url, { ignoreError: true });
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
// Dispatcher API methods - forward close/destroy to base dispatcher
|
|
674
|
+
|
|
675
|
+
close(...args) {
|
|
676
|
+
return this.#baseDispatcher.close(...args);
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
destroy(...args) {
|
|
680
|
+
return this.#baseDispatcher.destroy(...args);
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
/**
|
|
684
|
+
* Create a new JSDOMDispatcher with additional interceptors.
|
|
685
|
+
* The new interceptors are added as the outermost (first to see requests, last to see responses).
|
|
686
|
+
*/
|
|
687
|
+
compose(...additionalInterceptors) {
|
|
688
|
+
return new JSDOMDispatcher({
|
|
689
|
+
baseDispatcher: this.#baseDispatcher,
|
|
690
|
+
cookieJar: this.#cookieJar,
|
|
691
|
+
userAgent: this.#userAgent,
|
|
692
|
+
userInterceptors: [...additionalInterceptors, ...this.#userInterceptors]
|
|
693
|
+
});
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
get closed() {
|
|
697
|
+
return this.#baseDispatcher.closed;
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
get destroyed() {
|
|
701
|
+
return this.#baseDispatcher.destroyed;
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
/**
|
|
706
|
+
* High-level GET fetch that collects the full response body. Used for subresources and `JSDOM.fromURL()`.
|
|
707
|
+
*
|
|
708
|
+
* @param {Dispatcher} dispatcher - The undici dispatcher to use
|
|
709
|
+
* @param {object} opts - Request options
|
|
710
|
+
* @param {string} opts.url - The URL to fetch
|
|
711
|
+
* @param {object} [opts.headers] - Request headers (include Referer if needed)
|
|
712
|
+
* @param {AbortSignal} [opts.signal] - Abort signal
|
|
713
|
+
* @param {Element} [opts.element] - The element initiating the request (default: null)
|
|
714
|
+
* @returns {Promise<{status: number, headers: object, body: Uint8Array, url: string, ok: boolean}>}
|
|
715
|
+
*/
|
|
716
|
+
async function fetchCollected(dispatcher, { url, headers, signal, element = null }) {
|
|
717
|
+
const urlRecord = parseURL(url);
|
|
718
|
+
if (!urlRecord) {
|
|
719
|
+
throw new TypeError(`Invalid URL: ${url}`);
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
const response = await dispatcher.request({
|
|
723
|
+
origin: serializeURLOrigin(urlRecord),
|
|
724
|
+
path: serializePathForUndici(urlRecord),
|
|
725
|
+
method: "GET",
|
|
726
|
+
headers,
|
|
727
|
+
signal,
|
|
728
|
+
opaque: { element, url }
|
|
729
|
+
});
|
|
730
|
+
|
|
731
|
+
const body = await response.body.bytes();
|
|
732
|
+
|
|
733
|
+
// Get final URL from context (set by dispatcher after handling redirects)
|
|
734
|
+
const finalURL = serializeURL(response.context.finalURL);
|
|
735
|
+
|
|
736
|
+
return {
|
|
737
|
+
status: response.statusCode,
|
|
738
|
+
headers: response.headers,
|
|
739
|
+
body,
|
|
740
|
+
url: finalURL,
|
|
741
|
+
ok: response.statusCode >= 200 && response.statusCode < 300
|
|
742
|
+
};
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
/**
|
|
746
|
+
* Serialize a URL record's path and query for undici's `path` option.
|
|
747
|
+
*/
|
|
748
|
+
function serializePathForUndici(urlRecord) {
|
|
749
|
+
return serializePath(urlRecord) + (urlRecord.query ? "?" + urlRecord.query : "");
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
module.exports = {
|
|
753
|
+
JSDOMDispatcher,
|
|
754
|
+
DEFAULT_USER_AGENT,
|
|
755
|
+
fetchCollected
|
|
756
|
+
};
|