From 0b586a00aedb3609a6ac43b567a731cafc12a474 Mon Sep 17 00:00:00 2001 From: Krisna Pranav <68631244+krishpranav@users.noreply.github.com> Date: Mon, 6 Jan 2025 18:49:56 +0530 Subject: [PATCH 1/8] fix: Finish implementing URL accessors like host, hostname, username, password #45030 --- packages/react-native/Libraries/Blob/URL.js | 46 ++++++++----------- .../Libraries/Blob/__tests__/URL-test.js | 16 +++++++ 2 files changed, 35 insertions(+), 27 deletions(-) diff --git a/packages/react-native/Libraries/Blob/URL.js b/packages/react-native/Libraries/Blob/URL.js index db4bb2792a1e3a..f937035e76fdf9 100644 --- a/packages/react-native/Libraries/Blob/URL.js +++ b/packages/react-native/Libraries/Blob/URL.js @@ -9,7 +9,6 @@ */ import type Blob from './Blob'; - import NativeBlobModule from './NativeBlobModule'; let BLOB_URL_PREFIX = null; @@ -19,8 +18,6 @@ if ( typeof NativeBlobModule.getConstants().BLOB_URI_SCHEME === 'string' ) { const constants = NativeBlobModule.getConstants(); - // $FlowFixMe[incompatible-type] asserted above - // $FlowFixMe[unsafe-addition] BLOB_URL_PREFIX = constants.BLOB_URI_SCHEME + ':'; if (typeof constants.BLOB_URI_HOST === 'string') { BLOB_URL_PREFIX += `//${constants.BLOB_URI_HOST}/`; @@ -51,19 +48,16 @@ if ( * * ``` */ - -export {URLSearchParams} from './URLSearchParams'; +export { URLSearchParams } from './URLSearchParams'; function validateBaseUrl(url: string) { - // from this MIT-licensed gist: https://gist.github.com/dperini/729294 - return /^(?:(?:(?:https?|ftp):)?\/\/)(?:(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:(?:[a-z0-9\u00a1-\uffff][a-z0-9\u00a1-\uffff_-]{0,62})?[a-z0-9\u00a1-\uffff]\.)*(?:[a-z\u00a1-\uffff]{2,}\.?))(?::\d{2,5})?(?:[/?#]\S*)?$/.test( - url, - ); + return /^(?:(?:(?:https?|ftp):)?\/\/)(?:(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:(?:[a-z0-9\u00a1-\uffff][a-z0-9\u00a1-\uffff_-]{0,62})?[a-z0-9\u00a1-\uffff]\.)*(?:[a-z\u00a1-\uffff]{2,}\.?))(?::\d{2,5})?(?:[/?#]\S*)?$/.test(url); } export class URL { _url: string; _searchParamsInstance: ?URLSearchParams = null; + _parsedUrl: URL; static createObjectURL(blob: Blob): string { if (BLOB_URL_PREFIX === null) { @@ -76,7 +70,6 @@ export class URL { // Do nothing. } - // $FlowFixMe[missing-local-annot] constructor(url: string, base: string | URL) { let baseUrl = null; if (!base || validateBaseUrl(url)) { @@ -104,18 +97,21 @@ export class URL { } this._url = `${baseUrl}${url}`; } + + // Parsing the URL to use for accessors + this._parsedUrl = new globalThis.URL(this._url); } get hash(): string { - throw new Error('URL.hash is not implemented'); + return this._parsedUrl.hash; } get host(): string { - throw new Error('URL.host is not implemented'); + return this._parsedUrl.host; } get hostname(): string { - throw new Error('URL.hostname is not implemented'); + return this._parsedUrl.hostname; } get href(): string { @@ -123,32 +119,32 @@ export class URL { } get origin(): string { - throw new Error('URL.origin is not implemented'); + return this._parsedUrl.origin; } get password(): string { - throw new Error('URL.password is not implemented'); + return this._parsedUrl.password; } get pathname(): string { - throw new Error('URL.pathname not implemented'); + return this._parsedUrl.pathname; } get port(): string { - throw new Error('URL.port is not implemented'); + return this._parsedUrl.port; } get protocol(): string { - throw new Error('URL.protocol is not implemented'); + return this._parsedUrl.protocol; } get search(): string { - throw new Error('URL.search is not implemented'); + return this._parsedUrl.search; } get searchParams(): URLSearchParams { if (this._searchParamsInstance == null) { - this._searchParamsInstance = new URLSearchParams(); + this._searchParamsInstance = new URLSearchParams(this._parsedUrl.search); } return this._searchParamsInstance; } @@ -158,16 +154,12 @@ export class URL { } toString(): string { - if (this._searchParamsInstance === null) { - return this._url; - } - // $FlowFixMe[incompatible-use] - const instanceString = this._searchParamsInstance.toString(); + const instanceString = this._searchParamsInstance ? this._searchParamsInstance.toString() : ''; const separator = this._url.indexOf('?') > -1 ? '&' : '?'; - return this._url + separator + instanceString; + return this._url + (instanceString ? separator + instanceString : ''); } get username(): string { - throw new Error('URL.username is not implemented'); + return this._parsedUrl.username; } } diff --git a/packages/react-native/Libraries/Blob/__tests__/URL-test.js b/packages/react-native/Libraries/Blob/__tests__/URL-test.js index c317217c8fe724..cd21230932e1da 100644 --- a/packages/react-native/Libraries/Blob/__tests__/URL-test.js +++ b/packages/react-native/Libraries/Blob/__tests__/URL-test.js @@ -41,4 +41,20 @@ describe('URL', function () { const k = new URL('en-US/docs', 'https://developer.mozilla.org'); expect(k.href).toBe('https://developer.mozilla.org/en-US/docs'); }); + + it('should implement host, hostname, username, and password accessors correctly', () => { + const url = new URL('https://username:password@developer.mozilla.org:8080/en-US/docs?query=test#fragment'); + + // Test host + expect(url.host).toBe('developer.mozilla.org:8080'); + + // Test hostname + expect(url.hostname).toBe('developer.mozilla.org'); + + // Test username + expect(url.username).toBe('username'); + + // Test password + expect(url.password).toBe('password'); + }); }); From b54a4a382401a8efb421fd36ae7f4a2ecde8e7a2 Mon Sep 17 00:00:00 2001 From: Krisna Pranav <68631244+krishpranav@users.noreply.github.com> Date: Tue, 7 Jan 2025 19:35:41 +0530 Subject: [PATCH 2/8] reverted comments --- packages/react-native/Libraries/Blob/URL.js | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/react-native/Libraries/Blob/URL.js b/packages/react-native/Libraries/Blob/URL.js index f937035e76fdf9..07edfc504ccaae 100644 --- a/packages/react-native/Libraries/Blob/URL.js +++ b/packages/react-native/Libraries/Blob/URL.js @@ -51,6 +51,7 @@ if ( export { URLSearchParams } from './URLSearchParams'; function validateBaseUrl(url: string) { + // from this MIT-licensed gist: https://gist.github.com/dperini/729294 return /^(?:(?:(?:https?|ftp):)?\/\/)(?:(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:(?:[a-z0-9\u00a1-\uffff][a-z0-9\u00a1-\uffff_-]{0,62})?[a-z0-9\u00a1-\uffff]\.)*(?:[a-z\u00a1-\uffff]{2,}\.?))(?::\d{2,5})?(?:[/?#]\S*)?$/.test(url); } From cb58a361134aa900dbad624f4ae0918dc4521699 Mon Sep 17 00:00:00 2001 From: Krisna Pranav <68631244+krishpranav@users.noreply.github.com> Date: Tue, 7 Jan 2025 19:36:38 +0530 Subject: [PATCH 3/8] revert comments --- packages/react-native/Libraries/Blob/URL.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/react-native/Libraries/Blob/URL.js b/packages/react-native/Libraries/Blob/URL.js index 07edfc504ccaae..556b8cfd008834 100644 --- a/packages/react-native/Libraries/Blob/URL.js +++ b/packages/react-native/Libraries/Blob/URL.js @@ -17,6 +17,8 @@ if ( NativeBlobModule && typeof NativeBlobModule.getConstants().BLOB_URI_SCHEME === 'string' ) { + // $FlowFixMe[incompatible-type] asserted above + // $FlowFixMe[unsafe-addition] const constants = NativeBlobModule.getConstants(); BLOB_URL_PREFIX = constants.BLOB_URI_SCHEME + ':'; if (typeof constants.BLOB_URI_HOST === 'string') { From f1d3fcb00d18fe19cf5f9ed54e976c26d325dcea Mon Sep 17 00:00:00 2001 From: Krisna Pranav <68631244+krishpranav@users.noreply.github.com> Date: Mon, 13 Jan 2025 12:06:23 +0530 Subject: [PATCH 4/8] react-native: RCTRefreshControl(iOS) + ReactSwipeRefreshLayout(Android) --- .../React/Views/RefreshControl/RCTRefreshControl.m | 9 +++++++++ .../views/swiperefresh/ReactSwipeRefreshLayout.kt | 13 ++++++++++--- 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/packages/react-native/React/Views/RefreshControl/RCTRefreshControl.m b/packages/react-native/React/Views/RefreshControl/RCTRefreshControl.m index 53bfd04703502d..dc7a227fe50006 100644 --- a/packages/react-native/React/Views/RefreshControl/RCTRefreshControl.m +++ b/packages/react-native/React/Views/RefreshControl/RCTRefreshControl.m @@ -221,4 +221,13 @@ - (void)refreshControlValueChanged } } +- (void)setTintColor:(UIColor *)tintColor { + if (_tintColor != tintColor) { + _tintColor = tintColor; + dispatch_async(dispatch_get_main_queue(), ^{ + self.refreshControl.tintColor = tintColor; + }); + } +} + @end diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/swiperefresh/ReactSwipeRefreshLayout.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/swiperefresh/ReactSwipeRefreshLayout.kt index e3201890f4c478..b6e255e1b80532 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/swiperefresh/ReactSwipeRefreshLayout.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/swiperefresh/ReactSwipeRefreshLayout.kt @@ -13,10 +13,11 @@ import androidx.swiperefreshlayout.widget.SwipeRefreshLayout import com.facebook.react.bridge.ReactContext import com.facebook.react.uimanager.PixelUtil import com.facebook.react.uimanager.events.NativeGestureUtil +import kotlin.math.abs /** Basic extension of [SwipeRefreshLayout] with ReactNative-specific functionality. */ public class ReactSwipeRefreshLayout(reactContext: ReactContext) : - SwipeRefreshLayout(reactContext) { + SwipeRefreshLayout(reactContext) { private var didLayout: Boolean = false private var refreshing: Boolean = false @@ -25,6 +26,7 @@ public class ReactSwipeRefreshLayout(reactContext: ReactContext) : private var prevTouchX: Float = 0f private var intercepted: Boolean = false private var nativeGestureStarted: Boolean = false + private var isBeingDraggedHorizontally: Boolean = false public override fun setRefreshing(refreshing: Boolean) { this.refreshing = refreshing @@ -111,12 +113,17 @@ public class ReactSwipeRefreshLayout(reactContext: ReactContext) : MotionEvent.ACTION_DOWN -> { prevTouchX = ev.x intercepted = false + isBeingDraggedHorizontally = false } MotionEvent.ACTION_MOVE -> { val eventX = ev.x - val xDiff = Math.abs(eventX - prevTouchX) + val xDiff = abs(eventX - prevTouchX) - if (intercepted || xDiff > touchSlop) { + if (xDiff > touchSlop) { + isBeingDraggedHorizontally = true + } + + if (intercepted || isBeingDraggedHorizontally) { intercepted = true return false } From 0cfc47cf76fb1b2a1b84a7329a806cc4a28e280f Mon Sep 17 00:00:00 2001 From: Krisna Pranav <68631244+krishpranav@users.noreply.github.com> Date: Mon, 13 Jan 2025 12:23:40 +0530 Subject: [PATCH 5/8] Update RefreshControl.js --- .../RefreshControl/RefreshControl.js | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/packages/react-native/Libraries/Components/RefreshControl/RefreshControl.js b/packages/react-native/Libraries/Components/RefreshControl/RefreshControl.js index 221df2405e256b..fa11415d09547b 100644 --- a/packages/react-native/Libraries/Components/RefreshControl/RefreshControl.js +++ b/packages/react-native/Libraries/Components/RefreshControl/RefreshControl.js @@ -20,7 +20,11 @@ import PullToRefreshViewNativeComponent, { const Platform = require('../../Utilities/Platform'); const React = require('react'); +const {useEffect, useRef} = React; +/** + * Type definitions for iOS-specific properties + */ type IOSProps = $ReadOnly<{| /** * The color of the refresh indicator. @@ -36,6 +40,9 @@ type IOSProps = $ReadOnly<{| title?: ?string, |}>; +/** + * Type definitions for Android-specific properties + */ type AndroidProps = $ReadOnly<{| /** * Whether the pull to refresh functionality is enabled. @@ -55,6 +62,9 @@ type AndroidProps = $ReadOnly<{| size?: ?('default' | 'large'), |}>; +/** + * The main RefreshControlProps type definition + */ export type RefreshControlProps = $ReadOnly<{| ...ViewProps, ...IOSProps, @@ -120,6 +130,7 @@ export type RefreshControlProps = $ReadOnly<{| * * __Note:__ `refreshing` is a controlled prop, this is why it needs to be set to true * in the `onRefresh` function otherwise the refresh indicator will stop immediately. + * RefreshControl Component */ class RefreshControl extends React.Component { _nativeRef: ?React.ElementRef< @@ -175,6 +186,8 @@ class RefreshControl extends React.Component { {...props} ref={this._setNativeRef} onRefresh={this._onRefresh} + onTouchStart={this._handleTouchStart} + onTouchMove={this._handleTouchMove} /> ); } @@ -199,6 +212,29 @@ class RefreshControl extends React.Component { ) => { this._nativeRef = ref; }; + + /** + * Horizontal Gesture Handling for Android + */ + _handleTouchStart = (event) => { + if (Platform.OS === 'android' && event.nativeEvent.touches.length === 1) { + this._nativeRef?.prevTouchX = event.nativeEvent.touches[0].pageX; + } + }; + + _handleTouchMove = (event) => { + if (Platform.OS === 'android' && event.nativeEvent.touches.length === 1) { + const touchX = event.nativeEvent.touches[0].pageX; + const prevTouchX = this._nativeRef?.prevTouchX || 0; + const xDiff = Math.abs(touchX - prevTouchX); + + if (xDiff > 5 && this._nativeRef) { + AndroidSwipeRefreshLayoutCommands.cancelRefreshGesture( + this._nativeRef, + ); + } + } + }; } module.exports = RefreshControl; From eb7a2e02fd5055a9bcaadb0b2d9712f5e8ff7556 Mon Sep 17 00:00:00 2001 From: Krisna Pranav <68631244+krishpranav@users.noreply.github.com> Date: Mon, 13 Jan 2025 14:46:09 +0530 Subject: [PATCH 6/8] Revert "react-native: RCTRefreshControl(iOS) + ReactSwipeRefreshLayout(Android)" This reverts commit f1d3fcb00d18fe19cf5f9ed54e976c26d325dcea. --- .../React/Views/RefreshControl/RCTRefreshControl.m | 9 --------- .../views/swiperefresh/ReactSwipeRefreshLayout.kt | 13 +++---------- 2 files changed, 3 insertions(+), 19 deletions(-) diff --git a/packages/react-native/React/Views/RefreshControl/RCTRefreshControl.m b/packages/react-native/React/Views/RefreshControl/RCTRefreshControl.m index dc7a227fe50006..53bfd04703502d 100644 --- a/packages/react-native/React/Views/RefreshControl/RCTRefreshControl.m +++ b/packages/react-native/React/Views/RefreshControl/RCTRefreshControl.m @@ -221,13 +221,4 @@ - (void)refreshControlValueChanged } } -- (void)setTintColor:(UIColor *)tintColor { - if (_tintColor != tintColor) { - _tintColor = tintColor; - dispatch_async(dispatch_get_main_queue(), ^{ - self.refreshControl.tintColor = tintColor; - }); - } -} - @end diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/swiperefresh/ReactSwipeRefreshLayout.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/swiperefresh/ReactSwipeRefreshLayout.kt index b6e255e1b80532..e3201890f4c478 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/swiperefresh/ReactSwipeRefreshLayout.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/swiperefresh/ReactSwipeRefreshLayout.kt @@ -13,11 +13,10 @@ import androidx.swiperefreshlayout.widget.SwipeRefreshLayout import com.facebook.react.bridge.ReactContext import com.facebook.react.uimanager.PixelUtil import com.facebook.react.uimanager.events.NativeGestureUtil -import kotlin.math.abs /** Basic extension of [SwipeRefreshLayout] with ReactNative-specific functionality. */ public class ReactSwipeRefreshLayout(reactContext: ReactContext) : - SwipeRefreshLayout(reactContext) { + SwipeRefreshLayout(reactContext) { private var didLayout: Boolean = false private var refreshing: Boolean = false @@ -26,7 +25,6 @@ public class ReactSwipeRefreshLayout(reactContext: ReactContext) : private var prevTouchX: Float = 0f private var intercepted: Boolean = false private var nativeGestureStarted: Boolean = false - private var isBeingDraggedHorizontally: Boolean = false public override fun setRefreshing(refreshing: Boolean) { this.refreshing = refreshing @@ -113,17 +111,12 @@ public class ReactSwipeRefreshLayout(reactContext: ReactContext) : MotionEvent.ACTION_DOWN -> { prevTouchX = ev.x intercepted = false - isBeingDraggedHorizontally = false } MotionEvent.ACTION_MOVE -> { val eventX = ev.x - val xDiff = abs(eventX - prevTouchX) + val xDiff = Math.abs(eventX - prevTouchX) - if (xDiff > touchSlop) { - isBeingDraggedHorizontally = true - } - - if (intercepted || isBeingDraggedHorizontally) { + if (intercepted || xDiff > touchSlop) { intercepted = true return false } From d190772fc577e65800573758c14cee0c852777c5 Mon Sep 17 00:00:00 2001 From: Krisna Pranav <68631244+krishpranav@users.noreply.github.com> Date: Mon, 13 Jan 2025 14:46:26 +0530 Subject: [PATCH 7/8] Revert "Update RefreshControl.js" This reverts commit 0cfc47cf76fb1b2a1b84a7329a806cc4a28e280f. --- .../RefreshControl/RefreshControl.js | 36 ------------------- 1 file changed, 36 deletions(-) diff --git a/packages/react-native/Libraries/Components/RefreshControl/RefreshControl.js b/packages/react-native/Libraries/Components/RefreshControl/RefreshControl.js index fa11415d09547b..221df2405e256b 100644 --- a/packages/react-native/Libraries/Components/RefreshControl/RefreshControl.js +++ b/packages/react-native/Libraries/Components/RefreshControl/RefreshControl.js @@ -20,11 +20,7 @@ import PullToRefreshViewNativeComponent, { const Platform = require('../../Utilities/Platform'); const React = require('react'); -const {useEffect, useRef} = React; -/** - * Type definitions for iOS-specific properties - */ type IOSProps = $ReadOnly<{| /** * The color of the refresh indicator. @@ -40,9 +36,6 @@ type IOSProps = $ReadOnly<{| title?: ?string, |}>; -/** - * Type definitions for Android-specific properties - */ type AndroidProps = $ReadOnly<{| /** * Whether the pull to refresh functionality is enabled. @@ -62,9 +55,6 @@ type AndroidProps = $ReadOnly<{| size?: ?('default' | 'large'), |}>; -/** - * The main RefreshControlProps type definition - */ export type RefreshControlProps = $ReadOnly<{| ...ViewProps, ...IOSProps, @@ -130,7 +120,6 @@ export type RefreshControlProps = $ReadOnly<{| * * __Note:__ `refreshing` is a controlled prop, this is why it needs to be set to true * in the `onRefresh` function otherwise the refresh indicator will stop immediately. - * RefreshControl Component */ class RefreshControl extends React.Component { _nativeRef: ?React.ElementRef< @@ -186,8 +175,6 @@ class RefreshControl extends React.Component { {...props} ref={this._setNativeRef} onRefresh={this._onRefresh} - onTouchStart={this._handleTouchStart} - onTouchMove={this._handleTouchMove} /> ); } @@ -212,29 +199,6 @@ class RefreshControl extends React.Component { ) => { this._nativeRef = ref; }; - - /** - * Horizontal Gesture Handling for Android - */ - _handleTouchStart = (event) => { - if (Platform.OS === 'android' && event.nativeEvent.touches.length === 1) { - this._nativeRef?.prevTouchX = event.nativeEvent.touches[0].pageX; - } - }; - - _handleTouchMove = (event) => { - if (Platform.OS === 'android' && event.nativeEvent.touches.length === 1) { - const touchX = event.nativeEvent.touches[0].pageX; - const prevTouchX = this._nativeRef?.prevTouchX || 0; - const xDiff = Math.abs(touchX - prevTouchX); - - if (xDiff > 5 && this._nativeRef) { - AndroidSwipeRefreshLayoutCommands.cancelRefreshGesture( - this._nativeRef, - ); - } - } - }; } module.exports = RefreshControl; From 784e9014f11b28e9cbeab8189a6bd514186b3f12 Mon Sep 17 00:00:00 2001 From: Krisna Pranav <68631244+krishpranav@users.noreply.github.com> Date: Tue, 14 Jan 2025 11:14:43 +0530 Subject: [PATCH 8/8] =?UTF-8?q?=F0=9F=9A=80=20Enhance=20URL=20Handling:=20?= =?UTF-8?q?Lazy=20Parsing=20&=20Improved=20Compatibility=20=F0=9F=8C=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/react-native/Libraries/Blob/URL.js | 29 +++++++++++++++++---- 1 file changed, 24 insertions(+), 5 deletions(-) diff --git a/packages/react-native/Libraries/Blob/URL.js b/packages/react-native/Libraries/Blob/URL.js index 556b8cfd008834..84641420e7a01a 100644 --- a/packages/react-native/Libraries/Blob/URL.js +++ b/packages/react-native/Libraries/Blob/URL.js @@ -17,9 +17,9 @@ if ( NativeBlobModule && typeof NativeBlobModule.getConstants().BLOB_URI_SCHEME === 'string' ) { + const constants = NativeBlobModule.getConstants(); // $FlowFixMe[incompatible-type] asserted above // $FlowFixMe[unsafe-addition] - const constants = NativeBlobModule.getConstants(); BLOB_URL_PREFIX = constants.BLOB_URI_SCHEME + ':'; if (typeof constants.BLOB_URI_HOST === 'string') { BLOB_URL_PREFIX += `//${constants.BLOB_URI_HOST}/`; @@ -54,13 +54,15 @@ export { URLSearchParams } from './URLSearchParams'; function validateBaseUrl(url: string) { // from this MIT-licensed gist: https://gist.github.com/dperini/729294 - return /^(?:(?:(?:https?|ftp):)?\/\/)(?:(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:(?:[a-z0-9\u00a1-\uffff][a-z0-9\u00a1-\uffff_-]{0,62})?[a-z0-9\u00a1-\uffff]\.)*(?:[a-z\u00a1-\uffff]{2,}\.?))(?::\d{2,5})?(?:[/?#]\S*)?$/.test(url); + return /^(?:(?:(?:https?|ftp):)?\/\/)(?:(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:(?:[a-z0-9\u00a1-\uffff][a-z0-9\u00a1-\uffff_-]{0,62})?[a-z0-9\u00a1-\uffff]\.)*(?:[a-z\u00a1-\uffff]{2,}\.?))(?::\d{2,5})?(?:[/?#]\S*)?$/.test( + url, + ); } export class URL { _url: string; _searchParamsInstance: ?URLSearchParams = null; - _parsedUrl: URL; + _parsedUrl: ?URL = null; static createObjectURL(blob: Blob): string { if (BLOB_URL_PREFIX === null) { @@ -73,6 +75,7 @@ export class URL { // Do nothing. } + // $FlowFixMe[missing-local-annot] constructor(url: string, base: string | URL) { let baseUrl = null; if (!base || validateBaseUrl(url)) { @@ -100,52 +103,66 @@ export class URL { } this._url = `${baseUrl}${url}`; } + } - // Parsing the URL to use for accessors - this._parsedUrl = new globalThis.URL(this._url); + _parseUrlIfNeeded() { + if (this._parsedUrl == null) { + this._parsedUrl = new (globalThis || window).URL(this._url); + } } get hash(): string { + this._parseUrlIfNeeded(); return this._parsedUrl.hash; } get host(): string { + this._parseUrlIfNeeded(); return this._parsedUrl.host; } get hostname(): string { + this._parseUrlIfNeeded(); return this._parsedUrl.hostname; } get href(): string { + this._parseUrlIfNeeded(); return this.toString(); } get origin(): string { + this._parseUrlIfNeeded(); return this._parsedUrl.origin; } get password(): string { + this._parseUrlIfNeeded(); return this._parsedUrl.password; } get pathname(): string { + this._parseUrlIfNeeded(); return this._parsedUrl.pathname; } get port(): string { + this._parseUrlIfNeeded(); return this._parsedUrl.port; } get protocol(): string { + this._parseUrlIfNeeded(); return this._parsedUrl.protocol; } get search(): string { + this._parseUrlIfNeeded(); return this._parsedUrl.search; } get searchParams(): URLSearchParams { + this._parseUrlIfNeeded(); if (this._searchParamsInstance == null) { this._searchParamsInstance = new URLSearchParams(this._parsedUrl.search); } @@ -157,12 +174,14 @@ export class URL { } toString(): string { + this._parseUrlIfNeeded(); const instanceString = this._searchParamsInstance ? this._searchParamsInstance.toString() : ''; const separator = this._url.indexOf('?') > -1 ? '&' : '?'; return this._url + (instanceString ? separator + instanceString : ''); } get username(): string { + this._parseUrlIfNeeded(); return this._parsedUrl.username; } }