From e7ab426ee2ac6d2224995ec7dd6653e58ae74482 Mon Sep 17 00:00:00 2001 From: Nathan Walker Date: Thu, 10 Jul 2025 15:47:29 -0700 Subject: [PATCH] refactor: circular deps part 14 --- .../src/navigation/custom-transition.ios.ts | 5 +- apps/automated/src/ui/image/image-tests.ts | 6 +- .../src/ui/view/view-tests-common.ts | 46 +- .../src/ui/view/view-tests.android.ts | 38 +- apps/automated/src/ui/view/view-tests.ios.ts | 46 +- .../core/application/application-common.ts | 12 +- .../core/application/application.android.ts | 30 +- packages/core/application/application.ios.ts | 21 +- packages/core/application/helpers-common.ts | 53 +- packages/core/application/helpers.android.ts | 5 + packages/core/css-value/reworkcss-value.d.ts | 8 - packages/core/css-value/reworkcss-value.js | 113 ---- packages/core/css-value/reworkcss-value.ts | 131 ++++ packages/core/css/css-tree-parser.js | 137 ---- packages/core/css/lib/parse/index.js | 602 ----------------- packages/core/css/lib/parse/index.ts | 592 +++++++++++++++++ packages/core/css/parser.spec.ts | 2 +- packages/core/css/reworkcss.js | 1 - .../core/css/{reworkcss.d.ts => reworkcss.ts} | 8 +- packages/core/debugger/dom-types.ts | 11 +- packages/core/debugger/webinspector-css.ts | 35 +- packages/core/debugger/webinspector-dom.ts | 27 +- .../debugger/webinspector-network.android.ts | 23 +- .../core/debugger/webinspector-network.ios.ts | 19 +- packages/core/file-system/index.ts | 6 +- packages/core/http/http-interfaces.ts | 98 +++ .../http/http-request/http-request-common.ts | 5 +- .../core/http/http-request/index.android.ts | 32 +- packages/core/http/http-request/index.d.ts | 2 +- packages/core/http/http-request/index.ios.ts | 44 +- packages/core/http/http-shared.ts | 12 + packages/core/http/index.d.ts | 13 +- packages/core/http/index.ts | 113 +--- packages/core/image-source/index.android.ts | 4 +- packages/core/image-source/index.ios.ts | 8 +- packages/core/inspector_modules.ts | 9 +- packages/core/js-libs/easysax/easysax.js | 8 +- packages/core/platform/screen/index.ios.ts | 2 +- packages/core/profiling/index.ts | 4 +- packages/core/project.json | 13 +- packages/core/ui/animation/index.android.ts | 4 +- .../ui/builder/component-builder/index.ts | 5 +- packages/core/ui/builder/index.ts | 562 +++++++++++++++- packages/core/ui/builder/xml2ui.ts | 552 ---------------- packages/core/ui/core/properties/index.ts | 5 +- packages/core/ui/dialogs/dialogs-common.ts | 6 +- .../ui/frame/callbacks/activity-callbacks.ts | 303 --------- .../ui/frame/callbacks/fragment-callbacks.ts | 310 --------- packages/core/ui/frame/fragment.android.ts | 3 +- .../ui/frame/fragment.transitions.android.ts | 2 +- packages/core/ui/frame/frame-common.ts | 2 +- packages/core/ui/frame/frame-interfaces.ts | 9 +- packages/core/ui/frame/frame-stack.ts | 1 - packages/core/ui/frame/index.android.ts | 614 +++++++++++++++++- packages/core/ui/frame/index.d.ts | 2 +- packages/core/ui/frame/index.ios.ts | 8 +- packages/core/ui/gestures/index.android.ts | 2 +- packages/core/ui/image-cache/index.ios.ts | 23 +- packages/core/ui/page/events.ts | 6 + packages/core/ui/page/index.android.ts | 2 +- packages/core/ui/page/index.d.ts | 2 +- packages/core/ui/page/index.ios.ts | 7 +- packages/core/ui/page/page-common.ts | 9 +- packages/core/ui/styling/background-common.ts | 1 - .../core/ui/styling/background.android.ts | 62 +- packages/core/ui/styling/background.ios.ts | 7 +- packages/core/ui/styling/style-scope.ts | 93 ++- .../core/ui/transition/fade-transition.ios.ts | 4 +- packages/core/ui/transition/index.ios.ts | 2 +- .../ui/transition/modal-transition.ios.ts | 2 +- .../core/ui/transition/page-transition.ios.ts | 2 +- .../shared-transition-helper.ios.ts | 12 +- .../core/ui/transition/shared-transition.ts | 3 +- .../ui/transition/slide-transition.ios.ts | 6 +- packages/core/utils/animation-helpers.ts | 51 ++ packages/core/utils/common.ts | 114 ---- packages/core/utils/debug-source.ts | 28 +- packages/core/utils/index.android.ts | 36 + packages/core/utils/index.d.ts | 16 +- packages/core/utils/index.ios.ts | 38 +- packages/core/utils/ios-helper.ts | 1 - packages/core/utils/native-helper.android.ts | 30 +- packages/core/utils/native-helper.ios.ts | 4 +- packages/core/utils/shared.ts | 40 ++ packages/core/xml/index.d.ts | 112 ---- packages/core/xml/index.js | 553 ---------------- packages/core/xml/index.ts | 70 +- 87 files changed, 2667 insertions(+), 3403 deletions(-) delete mode 100644 packages/core/css-value/reworkcss-value.d.ts delete mode 100644 packages/core/css-value/reworkcss-value.js create mode 100644 packages/core/css-value/reworkcss-value.ts delete mode 100644 packages/core/css/css-tree-parser.js delete mode 100644 packages/core/css/lib/parse/index.js create mode 100644 packages/core/css/lib/parse/index.ts delete mode 100644 packages/core/css/reworkcss.js rename packages/core/css/{reworkcss.d.ts => reworkcss.ts} (82%) create mode 100644 packages/core/http/http-interfaces.ts delete mode 100644 packages/core/ui/builder/xml2ui.ts delete mode 100644 packages/core/ui/frame/callbacks/activity-callbacks.ts delete mode 100644 packages/core/ui/frame/callbacks/fragment-callbacks.ts create mode 100644 packages/core/ui/page/events.ts create mode 100644 packages/core/utils/animation-helpers.ts delete mode 100644 packages/core/utils/ios-helper.ts create mode 100644 packages/core/utils/shared.ts delete mode 100644 packages/core/xml/index.d.ts delete mode 100644 packages/core/xml/index.js diff --git a/apps/automated/src/navigation/custom-transition.ios.ts b/apps/automated/src/navigation/custom-transition.ios.ts index 16a013889..644b9c474 100644 --- a/apps/automated/src/navigation/custom-transition.ios.ts +++ b/apps/automated/src/navigation/custom-transition.ios.ts @@ -1,5 +1,4 @@ import { PageTransition, SharedTransition, SharedTransitionAnimationType, SharedTransitionHelper, Transition, Utils } from '@nativescript/core'; -import { CORE_ANIMATION_DEFAULTS } from '@nativescript/core/utils'; export class CustomTransition extends Transition { constructor(duration: number, curve: any) { @@ -32,7 +31,7 @@ export class CustomTransition extends Transition { }, (finished) => { transitionContext.completeTransition(finished); - } + }, ); } } @@ -90,7 +89,7 @@ class PageTransitionController extends NSObject implements UIViewControllerAnima } } } - return CORE_ANIMATION_DEFAULTS.duration; + return Utils.CORE_ANIMATION_DEFAULTS.duration; } animateTransition(transitionContext: UIViewControllerContextTransitioning): void { diff --git a/apps/automated/src/ui/image/image-tests.ts b/apps/automated/src/ui/image/image-tests.ts index 70066fe12..875c889e5 100644 --- a/apps/automated/src/ui/image/image-tests.ts +++ b/apps/automated/src/ui/image/image-tests.ts @@ -15,7 +15,7 @@ import { ImageSource } from '@nativescript/core/image-source'; import * as ViewModule from '@nativescript/core/ui/core/view'; import * as helper from '../../ui-helper'; import * as color from '@nativescript/core/color'; -import * as backgroundModule from '@nativescript/core/ui/styling/background'; +import * as appHelpers from '@nativescript/core/application/helpers-common'; import { Application } from '@nativescript/core'; const imagePath = '~/assets/logo.png'; @@ -23,8 +23,8 @@ export function test_recycling() { helper.nativeView_recycling_test(() => new Image()); } -if (global.isAndroid) { - (backgroundModule).initImageCache(Application.android.startActivity, (backgroundModule).CacheMode.memory); // use memory cache only. +if (__ANDROID__) { + appHelpers.initImageCache(Application.android.startActivity, appHelpers.CacheMode.memory); // use memory cache only. } const expectLayoutRequest = __APPLE__ && Utils.SDK_VERSION >= 18; diff --git a/apps/automated/src/ui/view/view-tests-common.ts b/apps/automated/src/ui/view/view-tests-common.ts index efb2af430..9ed4b3d95 100644 --- a/apps/automated/src/ui/view/view-tests-common.ts +++ b/apps/automated/src/ui/view/view-tests-common.ts @@ -1,7 +1,6 @@ import * as TKUnit from '../../tk-unit'; import { View, eachDescendant, getViewById, InheritedProperty, CssProperty, CssAnimationProperty, ShorthandProperty, Property, Style, Frame, Page, Button, Label, Color, StackLayout, AbsoluteLayout, Observable, Utils, BindingOptions, isAndroid, LayoutBase } from '@nativescript/core'; import * as helper from '../../ui-helper'; -import { checkNativeBackgroundColor, checkNativeBackgroundImage } from './view-tests'; export function test_eachDescendant() { const test = function (views: Array) { @@ -491,7 +490,7 @@ export function test_NativeSetter_called_only_once_with_cssValue() { TKUnit.assertEqual(testView.cssAnimPropNativeValue, 'testCssAnimValue', 'Native value'); TKUnit.assertEqual(testView.viewPropNativeValue, 'testViewValue', 'Native value'); }, - { pageCss: pageCSS } + { pageCss: pageCSS }, ); } @@ -521,7 +520,7 @@ export function test_NativeSetter_called_only_once_with_cssValue_and_localValue( // View property set from CSS sets local value TKUnit.assertEqual(testView.viewPropNativeValue, 'testViewValueCSS', 'Native value'); }, - { pageCss: pageCSS } + { pageCss: pageCSS }, ); } @@ -909,7 +908,7 @@ export function test_binding_style_opacity() { property_binding_style_test('opacity', 0.5, 0.6); } -function _createLabelWithBorder(): View { +export function _createLabelWithBorder(): View { const lbl = new Label(); lbl.borderRadius = 10; lbl.borderWidth = 2; @@ -919,7 +918,7 @@ function _createLabelWithBorder(): View { return lbl; } -function _createBackgroundColorView(): View { +export function _createBackgroundColorView(): View { const lbl = new Label(); lbl.backgroundColor = new Color('#FFFF00'); return lbl; @@ -952,41 +951,6 @@ export function testSetInlineStyle() { }); } -export function testBackgroundColor() { - helper.buildUIAndRunTest(_createLabelWithBorder(), function (views: Array) { - const lbl = views[0]; - helper.waitUntilLayoutReady(lbl); - TKUnit.assertEqual(checkNativeBackgroundColor(lbl), true, 'BackgroundColor not applied correctly!'); - }); -} - -export function testBackgroundBorderColor() { - helper.buildUIAndRunTest(_createLabelWithBorder(), function (views: Array) { - const lbl = views[0]; - helper.waitUntilLayoutReady(lbl); - TKUnit.assertEqual(checkNativeBackgroundColor(lbl), true, 'BackgroundColor not applied correctly!'); - }); -} -export function testSetAndRemoveBackgroundColor() { - helper.buildUIAndRunTest(_createBackgroundColorView(), function (views: Array) { - const lbl = views[0]; - helper.waitUntilLayoutReady(lbl); - TKUnit.assertEqual(checkNativeBackgroundColor(lbl), true, 'BackgroundColor not applied correctly!'); - lbl.backgroundColor = null; - TKUnit.assertEqual(checkNativeBackgroundColor(lbl), true, 'BackgroundColor not applied correctly!'); - }); -} - -export function testBackgroundImage() { - const lbl = _createLabelWithBorder(); - lbl.className = 'myClass'; - helper.buildUIAndRunTest(lbl, function (views: Array) { - const page = views[1]; - page.css = ".myClass { background-image: url('~/assets/logo.png') }"; - TKUnit.assertEqual(checkNativeBackgroundImage(lbl), true, 'Style background-image not loaded correctly.'); - }); -} - export function testBackgroundShorthand_With_EmptyBorder() { // Related to issue https://github.com/NativeScript/NativeScript/issues/4415 const lbl = new Label(); @@ -998,7 +962,7 @@ export function testBackgroundShorthand_With_EmptyBorder() { (views: Array) => { helper.waitUntilLayoutReady(lbl); }, - { pageCss: css } + { pageCss: css }, ); } diff --git a/apps/automated/src/ui/view/view-tests.android.ts b/apps/automated/src/ui/view/view-tests.android.ts index 8c254f656..6c2ddd565 100644 --- a/apps/automated/src/ui/view/view-tests.android.ts +++ b/apps/automated/src/ui/view/view-tests.android.ts @@ -1,6 +1,7 @@ import * as TKUnit from '../../tk-unit'; import * as helper from '../../ui-helper'; -import { View, unsetValue, Button, StackLayout, Label, Color, isIOS, Trace, Utils } from '@nativescript/core'; +import { View, unsetValue, Button, StackLayout, Label, Color, isIOS, Trace, Utils, Page } from '@nativescript/core'; +import { _createBackgroundColorView, _createLabelWithBorder } from './view-tests-common'; // enable the trace, it is disabled by default Trace.enable(); @@ -297,3 +298,38 @@ export function checkNativeBackgroundImage(v: View): boolean { return bkg && !Utils.isNullOrUndefined(bkg.getBackgroundImage()); } + +export function testBackgroundColor() { + helper.buildUIAndRunTest(_createLabelWithBorder(), function (views: Array) { + const lbl = views[0]; + helper.waitUntilLayoutReady(lbl); + TKUnit.assertEqual(checkNativeBackgroundColor(lbl), true, 'BackgroundColor not applied correctly!'); + }); +} + +export function testBackgroundBorderColor() { + helper.buildUIAndRunTest(_createLabelWithBorder(), function (views: Array) { + const lbl = views[0]; + helper.waitUntilLayoutReady(lbl); + TKUnit.assertEqual(checkNativeBackgroundColor(lbl), true, 'BackgroundColor not applied correctly!'); + }); +} +export function testSetAndRemoveBackgroundColor() { + helper.buildUIAndRunTest(_createBackgroundColorView(), function (views: Array) { + const lbl = views[0]; + helper.waitUntilLayoutReady(lbl); + TKUnit.assertEqual(checkNativeBackgroundColor(lbl), true, 'BackgroundColor not applied correctly!'); + lbl.backgroundColor = null; + TKUnit.assertEqual(checkNativeBackgroundColor(lbl), true, 'BackgroundColor not applied correctly!'); + }); +} + +export function testBackgroundImage() { + const lbl = _createLabelWithBorder(); + lbl.className = 'myClass'; + helper.buildUIAndRunTest(lbl, function (views: Array) { + const page = views[1]; + page.css = ".myClass { background-image: url('~/assets/logo.png') }"; + TKUnit.assertEqual(checkNativeBackgroundImage(lbl), true, 'Style background-image not loaded correctly.'); + }); +} diff --git a/apps/automated/src/ui/view/view-tests.ios.ts b/apps/automated/src/ui/view/view-tests.ios.ts index 7471c0fdf..d15542065 100644 --- a/apps/automated/src/ui/view/view-tests.ios.ts +++ b/apps/automated/src/ui/view/view-tests.ios.ts @@ -1,10 +1,7 @@ -import { View } from '@nativescript/core/ui/core/view'; -import { Button } from '@nativescript/core/ui/button'; -import { GridLayout } from '@nativescript/core/ui/layouts/grid-layout'; -import { Color } from '@nativescript/core/color'; +import { View, Page, Button, GridLayout, Color, Utils } from '@nativescript/core'; import * as helper from '../../ui-helper'; import * as TKUnit from '../../tk-unit'; -import * as utils from '@nativescript/core/utils'; +import { _createBackgroundColorView, _createLabelWithBorder } from './view-tests-common'; export * from './view-tests-common'; @@ -18,7 +15,7 @@ class MyGrid extends GridLayout { } export function getUniformNativeBorderWidth(v: View): number { - return utils.layout.toDevicePixels((v.ios).layer.borderWidth); + return Utils.layout.toDevicePixels((v.ios).layer.borderWidth); } export function checkUniformNativeBorderColor(v: View): boolean { @@ -30,7 +27,42 @@ export function checkUniformNativeBorderColor(v: View): boolean { } export function getUniformNativeCornerRadius(v: View): number { - return utils.layout.toDevicePixels((v.ios).layer.cornerRadius); + return Utils.layout.toDevicePixels((v.ios).layer.cornerRadius); +} + +export function testBackgroundColor() { + helper.buildUIAndRunTest(_createLabelWithBorder(), function (views: Array) { + const lbl = views[0]; + helper.waitUntilLayoutReady(lbl); + TKUnit.assertEqual(checkNativeBackgroundColor(lbl), true, 'BackgroundColor not applied correctly!'); + }); +} + +export function testBackgroundBorderColor() { + helper.buildUIAndRunTest(_createLabelWithBorder(), function (views: Array) { + const lbl = views[0]; + helper.waitUntilLayoutReady(lbl); + TKUnit.assertEqual(checkNativeBackgroundColor(lbl), true, 'BackgroundColor not applied correctly!'); + }); +} +export function testSetAndRemoveBackgroundColor() { + helper.buildUIAndRunTest(_createBackgroundColorView(), function (views: Array) { + const lbl = views[0]; + helper.waitUntilLayoutReady(lbl); + TKUnit.assertEqual(checkNativeBackgroundColor(lbl), true, 'BackgroundColor not applied correctly!'); + lbl.backgroundColor = null; + TKUnit.assertEqual(checkNativeBackgroundColor(lbl), true, 'BackgroundColor not applied correctly!'); + }); +} + +export function testBackgroundImage() { + const lbl = _createLabelWithBorder(); + lbl.className = 'myClass'; + helper.buildUIAndRunTest(lbl, function (views: Array) { + const page = views[1]; + page.css = ".myClass { background-image: url('~/assets/logo.png') }"; + TKUnit.assertEqual(checkNativeBackgroundImage(lbl), true, 'Style background-image not loaded correctly.'); + }); } export function checkNativeBackgroundColor(v: View): boolean { diff --git a/packages/core/application/application-common.ts b/packages/core/application/application-common.ts index 6bc6dcd7a..439a34931 100644 --- a/packages/core/application/application-common.ts +++ b/packages/core/application/application-common.ts @@ -13,7 +13,7 @@ import type { StyleScope } from '../ui/styling/style-scope'; import type { AndroidApplication as AndroidApplicationType, iOSApplication as iOSApplicationType } from '.'; import type { ApplicationEventData, CssChangedEventData, DiscardedErrorEventData, FontScaleChangedEventData, InitRootViewEventData, LaunchEventData, LoadAppCSSEventData, NativeScriptError, OrientationChangedEventData, SystemAppearanceChangedEventData, UnhandledErrorEventData } from './application-interfaces'; import { readyInitAccessibilityCssHelper, readyInitFontScale } from '../accessibility/accessibility-common'; -import { isAppInBackground, setAppInBackground } from './helpers-common'; +import { getAppMainEntry, isAppInBackground, setAppInBackground, setAppMainEntry } from './helpers-common'; // prettier-ignore const ORIENTATION_CSS_CLASSES = [ @@ -310,13 +310,11 @@ export class ApplicationCommon { // implement in platform specific files (iOS only for now) } - protected mainEntry: NavigationEntry; - /** * @returns The main entry of the application */ getMainEntry() { - return this.mainEntry; + return getAppMainEntry(); } @profile @@ -350,11 +348,11 @@ export class ApplicationCommon { if (!rootView) { // try to navigate to the mainEntry (if specified) - if (!this.mainEntry) { + if (!getAppMainEntry()) { throw new Error('Main entry is missing. App cannot be started. Verify app bootstrap.'); } - rootView = Builder.createViewFromEntry(this.mainEntry); + rootView = Builder.createViewFromEntry(getAppMainEntry()); } } @@ -366,7 +364,7 @@ export class ApplicationCommon { } resetRootView(entry?: NavigationEntry | string) { - this.mainEntry = typeof entry === 'string' ? { moduleName: entry } : entry; + setAppMainEntry(typeof entry === 'string' ? { moduleName: entry } : entry); // rest of implementation is platform specific } diff --git a/packages/core/application/application.android.ts b/packages/core/application/application.android.ts index b9f3fd7ea..c8eeb5b60 100644 --- a/packages/core/application/application.android.ts +++ b/packages/core/application/application.android.ts @@ -44,7 +44,7 @@ import { setA11yEnabled, } from '../accessibility/accessibility-common'; import { androidGetForegroundActivity, androidGetStartActivity, androidPendingReceiverRegistrations, androidRegisterBroadcastReceiver, androidRegisteredReceivers, androidSetForegroundActivity, androidSetStartActivity, androidUnregisterBroadcastReceiver, applyContentDescription } from './helpers'; -import { getRootView, setA11yUpdatePropertiesCallback, setApplicationPropertiesCallback, setNativeApp, setRootView, setToggleApplicationEventListenersCallback } from './helpers-common'; +import { getImageFetcher, getRootView, initImageCache, setA11yUpdatePropertiesCallback, setApplicationPropertiesCallback, setAppMainEntry, setNativeApp, setRootView, setToggleApplicationEventListenersCallback } from './helpers-common'; declare namespace com { namespace tns { @@ -406,7 +406,7 @@ export class AndroidApplication extends ApplicationCommon { } this.started = true; - this.mainEntry = typeof entry === 'string' ? { moduleName: entry } : entry; + setAppMainEntry(typeof entry === 'string' ? { moduleName: entry } : entry); if (!this.nativeApp) { const nativeApp = this.getNativeApplication(); @@ -1394,3 +1394,29 @@ setApplicationPropertiesCallback(() => { systemAppearance: Application.systemAppearance(), }; }); + +function onLiveSync(args): void { + if (getImageFetcher()) { + getImageFetcher().clearCache(); + } +} + +global.NativeScriptGlobals.events.on('livesync', onLiveSync); + +global.NativeScriptGlobals.addEventWiring(() => { + Application.android.on('activityStarted', (args: any) => { + if (!getImageFetcher()) { + initImageCache(args.activity); + } else { + getImageFetcher().initCache(); + } + }); +}); + +global.NativeScriptGlobals.addEventWiring(() => { + Application.android.on('activityStopped', (args) => { + if (getImageFetcher()) { + getImageFetcher().closeCache(); + } + }); +}); diff --git a/packages/core/application/application.ios.ts b/packages/core/application/application.ios.ts index bab61c08a..8ae527436 100644 --- a/packages/core/application/application.ios.ts +++ b/packages/core/application/application.ios.ts @@ -2,9 +2,10 @@ import { profile } from '../profiling'; import type { View } from '../ui/core/view'; import { isEmbedded } from '../ui/embedding'; import { IOSHelper } from '../ui/core/view/view-helper'; -import { NavigationEntry } from '../ui/frame/frame-interfaces'; -import { getWindow } from '../utils/ios-helper'; -import * as Utils from '../utils'; +import type { NavigationEntry } from '../ui/frame/frame-interfaces'; +import { getWindow } from '../utils/native-helper'; +import { SDK_VERSION } from '../utils/constants'; +import { ios as iosUtils } from '../utils/native-helper'; import { ApplicationCommon } from './application-common'; import { ApplicationEventData } from './application-interfaces'; import { Observable } from '../data/observable'; @@ -46,7 +47,7 @@ import { enforceArray, } from '../accessibility/accessibility-common'; import { iosAddNotificationObserver, iosRemoveNotificationObserver } from './helpers'; -import { getiOSWindow, setA11yUpdatePropertiesCallback, setApplicationPropertiesCallback, setiOSWindow, setRootView, setToggleApplicationEventListenersCallback } from './helpers-common'; +import { getiOSWindow, setA11yUpdatePropertiesCallback, setApplicationPropertiesCallback, setAppMainEntry, setiOSWindow, setRootView, setToggleApplicationEventListenersCallback } from './helpers-common'; @NativeClass class CADisplayLinkTarget extends NSObject { @@ -126,7 +127,7 @@ export class iOSApplication extends ApplicationCommon { } run(entry?: string | NavigationEntry): void { - this.mainEntry = typeof entry === 'string' ? { moduleName: entry } : entry; + setAppMainEntry(typeof entry === 'string' ? { moduleName: entry } : entry); this.started = true; if (this.nativeApp) { @@ -174,7 +175,7 @@ export class iOSApplication extends ApplicationCommon { this.setViewControllerView(rootView); embedderDelegate.presentNativeScriptApp(controller); } else { - const visibleVC = Utils.ios.getVisibleViewController(rootController); + const visibleVC = iosUtils.getVisibleViewController(rootController); visibleVC.presentViewControllerAnimatedCompletion(controller, true, null); } @@ -217,7 +218,7 @@ export class iOSApplication extends ApplicationCommon { if (minFrameRateDisabled) { let max = 120; - const deviceMaxFrames = Utils.ios.getMainScreen().maximumFramesPerSecond; + const deviceMaxFrames = iosUtils.getMainScreen().maximumFramesPerSecond; if (options?.max) { if (deviceMaxFrames) { // iOS 10.3 @@ -228,7 +229,7 @@ export class iOSApplication extends ApplicationCommon { } } - if (Utils.SDK_VERSION >= 15 || __VISIONOS__) { + if (SDK_VERSION >= 15 || __VISIONOS__) { const min = options?.min || max / 2; const preferred = options?.preferred || max; this.displayedLink.preferredFrameRateRange = CAFrameRateRangeMake(min, max, preferred); @@ -332,7 +333,7 @@ export class iOSApplication extends ApplicationCommon { protected getSystemAppearance(): 'light' | 'dark' { // userInterfaceStyle is available on UITraitCollection since iOS 12. - if ((!__VISIONOS__ && Utils.SDK_VERSION <= 11) || !this.rootController) { + if ((!__VISIONOS__ && SDK_VERSION <= 11) || !this.rootController) { return null; } @@ -450,7 +451,7 @@ export class iOSApplication extends ApplicationCommon { } if (!__VISIONOS__) { - this.window.backgroundColor = Utils.SDK_VERSION <= 12 || !UIColor.systemBackgroundColor ? UIColor.whiteColor : UIColor.systemBackgroundColor; + this.window.backgroundColor = SDK_VERSION <= 12 || !UIColor.systemBackgroundColor ? UIColor.whiteColor : UIColor.systemBackgroundColor; } this.notifyAppStarted(notification); diff --git a/packages/core/application/helpers-common.ts b/packages/core/application/helpers-common.ts index eeb6a35f8..e1c6a7f99 100644 --- a/packages/core/application/helpers-common.ts +++ b/packages/core/application/helpers-common.ts @@ -1,8 +1,7 @@ /** - * Keep this helper file slim to avoid circular dependencies. + * Do not import other files here to avoid circular dependencies. * Used to define helper functions and variables that are shared between Android and iOS * without introducing platform-specific code directly. - * It should not import platform-specific modules directly. */ let nativeApp: UIApplication | android.app.Application; @@ -51,6 +50,15 @@ export function setiOSWindow(value: UIWindow) { _iosWindow = value; } +let _appMainEntry: any /* NavigationEntry */; + +export function getAppMainEntry(): any /* NavigationEntry */ { + return _appMainEntry; +} +export function setAppMainEntry(entry: any /* NavigationEntry */) { + _appMainEntry = entry; +} + // Aids avoiding circular dependencies by allowing the application event listeners to be toggled let _toggleApplicationEventListenersHandler: (toAdd: boolean, callback: (args: any) => void) => void; export function toggleApplicationEventListeners(toAdd: boolean, callback: (args: any) => void) { @@ -84,3 +92,44 @@ export function updateA11yPropertiesCallback(view: any /* View */) { _a11yUpdatePropertiesCallback(view); } } + +/** + * Internal Android app helpers + */ +// Circular dependency avoidance for image fetching on android +let _imageFetcher: org.nativescript.widgets.image.Fetcher; +export function getImageFetcher(): org.nativescript.widgets.image.Fetcher { + return _imageFetcher; +} +export function setImageFetcher(fetcher: org.nativescript.widgets.image.Fetcher) { + _imageFetcher = fetcher; +} +export enum CacheMode { + none, + memory, + diskAndMemory, +} + +let _currentCacheMode: CacheMode; + +export function initImageCache(context: android.content.Context, mode = CacheMode.diskAndMemory, memoryCacheSize = 0.25, diskCacheSize: number = 10 * 1024 * 1024): void { + if (_currentCacheMode === mode) { + return; + } + + _currentCacheMode = mode; + if (!getImageFetcher()) { + setImageFetcher(org.nativescript.widgets.image.Fetcher.getInstance(context)); + } else { + getImageFetcher().clearCache(); + } + + const params = new org.nativescript.widgets.image.Cache.CacheParams(); + params.memoryCacheEnabled = mode !== CacheMode.none; + params.setMemCacheSizePercent(memoryCacheSize); // Set memory cache to % of app memory + params.diskCacheEnabled = mode === CacheMode.diskAndMemory; + params.diskCacheSize = diskCacheSize; + const imageCache = org.nativescript.widgets.image.Cache.getInstance(params); + getImageFetcher().addImageCache(imageCache); + getImageFetcher().initCache(); +} diff --git a/packages/core/application/helpers.android.ts b/packages/core/application/helpers.android.ts index 9fedb8d97..6c0fddf86 100644 --- a/packages/core/application/helpers.android.ts +++ b/packages/core/application/helpers.android.ts @@ -202,3 +202,8 @@ export function applyContentDescription(view: any /* View */, forceUpdate?: bool export function setupAccessibleView(view: any /* any */): void { updateA11yPropertiesCallback(view); } + +// stubs +export const iosNotificationObservers: Array = []; +export function iosAddNotificationObserver(notificationName: string, onReceiveCallback: (notification: any) => void) {} +export function iosRemoveNotificationObserver(observer: any, notificationName: string) {} diff --git a/packages/core/css-value/reworkcss-value.d.ts b/packages/core/css-value/reworkcss-value.d.ts deleted file mode 100644 index ae0a69953..000000000 --- a/packages/core/css-value/reworkcss-value.d.ts +++ /dev/null @@ -1,8 +0,0 @@ -interface CSSValue { - type: string; - string: string; - unit?: string; - value?: number; -} - -export function parse(cssValue: string): Array; diff --git a/packages/core/css-value/reworkcss-value.js b/packages/core/css-value/reworkcss-value.js deleted file mode 100644 index ceb8f51b7..000000000 --- a/packages/core/css-value/reworkcss-value.js +++ /dev/null @@ -1,113 +0,0 @@ - -exports.parse = parse; - -function parse(str) { - return new Parser(str).parse(); -} - -function Parser(str) { - this.str = str; -} - -Parser.prototype.skip = function(m){ - this.str = this.str.slice(m[0].length); -}; - -Parser.prototype.comma = function(){ - var m = /^, */.exec(this.str); - if (!m) return; - this.skip(m); - return { type: 'comma', string: ',' }; -}; - -Parser.prototype.ident = function(){ - var m = /^([\w-]+) */.exec(this.str); - if (!m) return; - this.skip(m); - return { - type: 'ident', - string: m[1] - } -}; - -Parser.prototype.int = function(){ - var m = /^(([-\+]?\d+)(\S+)?) */.exec(this.str); - if (!m) return; - this.skip(m); - var n = ~~m[2]; - var u = m[3]; - - return { - type: 'number', - string: m[1], - unit: u || '', - value: n - } -}; - -Parser.prototype.float = function(){ - var m = /^(((?:[-\+]?\d+)?\.\d+)(\S+)?) */.exec(this.str); - if (!m) return; - this.skip(m); - var n = parseFloat(m[2]); - var u = m[3]; - - return { - type: 'number', - string: m[1], - unit: u || '', - value: n - } -}; - -Parser.prototype.number = function(){ - return this.float() || this.int(); -}; - -Parser.prototype.double = function(){ - var m = /^"([^"]*)" */.exec(this.str); - if (!m) return m; - this.skip(m); - return { - type: 'string', - quote: '"', - string: '"' + m[1] + '"', - value: m[1] - } -}; - -Parser.prototype.single = function(){ - var m = /^'([^']*)' */.exec(this.str); - if (!m) return m; - this.skip(m); - return { - type: 'string', - quote: "'", - string: "'" + m[1] + "'", - value: m[1] - } -}; - -Parser.prototype.string = function(){ - return this.single() || this.double(); -}; - - -Parser.prototype.value = function(){ - return this.number() - || this.ident() - || this.string() - || this.comma(); -}; - -Parser.prototype.parse = function(){ - var vals = []; - - while (this.str.length) { - var obj = this.value(); - if (!obj) throw new Error('failed to parse near `' + this.str.slice(0, 10) + '...`'); - vals.push(obj); - } - - return vals; -}; diff --git a/packages/core/css-value/reworkcss-value.ts b/packages/core/css-value/reworkcss-value.ts new file mode 100644 index 000000000..5e4bdcbf0 --- /dev/null +++ b/packages/core/css-value/reworkcss-value.ts @@ -0,0 +1,131 @@ +export interface CSSValue { + type: string; + string: string; + unit?: string; + value?: number; +} + +export interface CommaToken { + type: 'comma'; + string: ','; +} + +export interface IdentToken { + type: 'ident'; + string: string; +} + +export interface NumberToken { + type: 'number'; + string: string; + unit: string; + value: number; +} + +export interface StringToken { + type: 'string'; + quote: '"' | "'"; + string: string; + value: string; +} + +export type Token = CommaToken | IdentToken | NumberToken | StringToken; + +/** + * Parse a string into an array of tokens. + */ +export function parse(str: string): Token[] { + return new Parser(str).parse(); +} + +class Parser { + private str: string; + + constructor(str: string) { + this.str = str; + } + + private skip(match: RegExpExecArray): void { + this.str = this.str.slice(match[0].length); + } + + /** Comma tokens: "," */ + private comma(): CommaToken | undefined { + const m = /^, */.exec(this.str); + if (!m) return; + this.skip(m); + return { type: 'comma', string: ',' }; + } + + /** Identifier tokens: word or dash sequences */ + private ident(): IdentToken | undefined { + const m = /^([\w-]+) */.exec(this.str); + if (!m) return; + this.skip(m); + return { type: 'ident', string: m[1] }; + } + + /** Integer tokens, possibly with unit */ + private int(): NumberToken | undefined { + const m = /^(([-+]?\d+)(\S+)?) */.exec(this.str); + if (!m) return; + this.skip(m); + const n = parseInt(m[2], 10); + const u = m[3] || ''; + return { type: 'number', string: m[1], unit: u, value: n }; + } + + /** Float tokens, possibly with unit */ + private float(): NumberToken | undefined { + const m = /^(((?:[-+]?\d+)?\.\d+)(\S+)?) */.exec(this.str); + if (!m) return; + this.skip(m); + const n = parseFloat(m[2]); + const u = m[3] || ''; + return { type: 'number', string: m[1], unit: u, value: n }; + } + + /** Number tokens, either float or int */ + private number(): NumberToken | undefined { + return this.float() || this.int(); + } + + /** Double-quoted strings */ + private double(): StringToken | undefined { + const m = /^"([^"]*)" */.exec(this.str); + if (!m) return; + this.skip(m); + return { type: 'string', quote: '"', string: `"${m[1]}"`, value: m[1] }; + } + + /** Single-quoted strings */ + private single(): StringToken | undefined { + const m = /^'([^']*)' */.exec(this.str); + if (!m) return; + this.skip(m); + return { type: 'string', quote: "'", string: `'${m[1]}'`, value: m[1] }; + } + + /** String tokens: single or double quoted */ + private string(): StringToken | undefined { + return this.single() || this.double(); + } + + /** Attempt to parse any token */ + private value(): Token | undefined { + return this.number() || this.ident() || this.string() || this.comma(); + } + + /** Run the full parse loop */ + public parse(): Token[] { + const vals: Token[] = []; + while (this.str.length > 0) { + const obj = this.value(); + if (!obj) { + throw new Error(`failed to parse near \`${this.str.slice(0, 10)}...\``); + } + vals.push(obj); + } + return vals; + } +} diff --git a/packages/core/css/css-tree-parser.js b/packages/core/css/css-tree-parser.js deleted file mode 100644 index 1a62c1fef..000000000 --- a/packages/core/css/css-tree-parser.js +++ /dev/null @@ -1,137 +0,0 @@ -Object.defineProperty(exports, "__esModule", { value: true }); -exports.cssTreeParse = void 0; -var css_tree_1 = require("css-tree"); -function mapSelectors(selector) { - if (!selector) { - return []; - } - return selector.split(/\s*(?![^(]*\)),\s*/).map(function (s) { return s.replace(/\u200C/g, ','); }); -} -function mapPosition(node, css) { - var res = { - start: { - line: node.loc.start.line, - column: node.loc.start.column, - }, - end: { - line: node.loc.end.line, - column: node.loc.end.column, - }, - content: css, - }; - if (node.loc.source && node.loc.source !== '') { - res.source = node.loc.source; - } - return res; -} -function transformAst(node, css, type) { - if (type === void 0) { type = null; } - if (!node) { - return; - } - if (node.type === 'StyleSheet') { - return { - type: 'stylesheet', - stylesheet: { - rules: node.children - .map(function (child) { return transformAst(child, css); }) - .filter(function (child) { return child !== null; }) - .toArray(), - parsingErrors: [], - }, - }; - } - if (node.type === 'Atrule') { - var atrule = { - type: node.name, - }; - if (node.name === 'supports' || node.name === 'media') { - atrule[node.name] = node.prelude.value; - atrule.rules = transformAst(node.block, css); - } - else if (node.name === 'page') { - atrule.selectors = node.prelude ? mapSelectors(node.prelude.value) : []; - atrule.declarations = transformAst(node.block, css); - } - else if (node.name === 'document') { - atrule.document = node.prelude ? node.prelude.value : ''; - atrule.vendor = ''; - atrule.rules = transformAst(node.block, css); - } - else if (node.name === 'font-face') { - atrule.declarations = transformAst(node.block, css); - } - else if (node.name === 'import' || node.name === 'charset' || node.name === 'namespace') { - atrule[node.name] = node.prelude ? node.prelude.value : ''; - } - else if (node.name === 'keyframes') { - atrule.name = node.prelude ? node.prelude.value : ''; - atrule.keyframes = transformAst(node.block, css, 'keyframe'); - atrule.vendor = undefined; - } - else { - atrule.rules = transformAst(node.block, css); - } - atrule.position = mapPosition(node, css); - return atrule; - } - if (node.type === 'Block') { - return node.children - .map(function (child) { return transformAst(child, css, type); }) - .filter(function (child) { return child !== null; }) - .toArray(); - } - if (node.type === 'Rule') { - var value = node.prelude.value; - var res = { - type: type != null ? type : 'rule', - declarations: transformAst(node.block, css), - position: mapPosition(node, css), - }; - if (type === 'keyframe') { - res.values = mapSelectors(value); - } - else { - res.selectors = mapSelectors(value); - } - return res; - } - if (node.type === 'Comment') { - return { - type: 'comment', - comment: node.value, - position: mapPosition(node, css), - }; - } - if (node.type === 'Declaration') { - return { - type: 'declaration', - property: node.property, - value: node.value.value ? node.value.value.trim() : '', - position: mapPosition(node, css), - }; - } - if (node.type === 'Raw') { - return null; - } - throw Error("Unknown node type ".concat(node.type)); -} -function cssTreeParse(css, source) { - var errors = []; - var ast = (0, css_tree_1.parse)(css, { - parseValue: false, - parseAtrulePrelude: false, - parseRulePrelude: false, - positions: true, - filename: source, - onParseError: function (error) { - errors.push("".concat(source, ":").concat(error.line, ":").concat(error.column, ": ").concat(error.formattedMessage)); - }, - }); - if (errors.length > 0) { - throw new Error(errors[0]); - } - return transformAst(ast, css); -} -exports.cssTreeParse = cssTreeParse; -//# sourceMappingURL=css-tree-parser.js.map \ No newline at end of file diff --git a/packages/core/css/lib/parse/index.js b/packages/core/css/lib/parse/index.js deleted file mode 100644 index 31e0ad639..000000000 --- a/packages/core/css/lib/parse/index.js +++ /dev/null @@ -1,602 +0,0 @@ -// http://www.w3.org/TR/CSS21/grammar.html -// https://github.com/visionmedia/css-parse/pull/49#issuecomment-30088027 -var commentre = /\/\*[^*]*\*+([^/*][^*]*\*+)*\//g - -module.exports = function(css, options){ - options = options || {}; - - /** - * Positional. - */ - - var lineno = 1; - var column = 1; - - /** - * Update lineno and column based on `str`. - */ - - function updatePosition(str) { - var lines = str.match(/\n/g); - if (lines) lineno += lines.length; - var i = str.lastIndexOf('\n'); - column = ~i ? str.length - i : column + str.length; - } - - /** - * Mark position and patch `node.position`. - */ - - function position() { - var start = { line: lineno, column: column }; - return function(node){ - node.position = new Position(start); - whitespace(); - return node; - }; - } - - /** - * Store position information for a node - */ - - function Position(start) { - this.start = start; - this.end = { line: lineno, column: column }; - this.source = options.source; - } - - /** - * Non-enumerable source string - */ - - Position.prototype.content = css; - - /** - * Error `msg`. - */ - - var errorsList = []; - - function error(msg) { - var err = new Error(options.source + ':' + lineno + ':' + column + ': ' + msg); - err.reason = msg; - err.filename = options.source; - err.line = lineno; - err.column = column; - err.source = css; - - if (options.silent) { - errorsList.push(err); - } else { - throw err; - } - } - - /** - * Parse stylesheet. - */ - - function stylesheet() { - var rulesList = rules(); - - return { - type: 'stylesheet', - stylesheet: { - rules: rulesList, - parsingErrors: errorsList - } - }; - } - - /** - * Opening brace. - */ - - function open() { - return match(/^{\s*/); - } - - /** - * Closing brace. - */ - - function close() { - return match(/^}/); - } - - /** - * Parse ruleset. - */ - - function rules() { - var node; - var rules = []; - whitespace(); - comments(rules); - while (css.length && css.charAt(0) != '}' && (node = atrule() || rule())) { - if (node !== false) { - rules.push(node); - comments(rules); - } - } - return rules; - } - - /** - * Match `re` and return captures. - */ - - function match(re) { - var m = re.exec(css); - if (!m) return; - var str = m[0]; - updatePosition(str); - css = css.slice(str.length); - return m; - } - - /** - * Parse whitespace. - */ - - function whitespace() { - match(/^\s*/); - } - - /** - * Parse comments; - */ - - function comments(rules) { - var c; - rules = rules || []; - while (c = comment()) { - if (c !== false) { - rules.push(c); - } - } - return rules; - } - - /** - * Parse comment. - */ - - function comment() { - var pos = position(); - if ('/' != css.charAt(0) || '*' != css.charAt(1)) return; - - var i = 2; - while ("" != css.charAt(i) && ('*' != css.charAt(i) || '/' != css.charAt(i + 1))) ++i; - i += 2; - - if ("" === css.charAt(i-1)) { - return error('End of comment missing'); - } - - var str = css.slice(2, i - 2); - column += 2; - updatePosition(str); - css = css.slice(i); - column += 2; - - return pos({ - type: 'comment', - comment: str - }); - } - - /** - * Parse selector. - */ - - function selector() { - var m = match(/^([^{]+)/); - if (!m) return; - /* @fix Remove all comments from selectors - * http://ostermiller.org/findcomment.html */ - return trim(m[0]) - .replace(/\/\*([^*]|[\r\n]|(\*+([^*/]|[\r\n])))*\*\/+/g, '') - .replace(/"(?:\\"|[^"])*"|'(?:\\'|[^'])*'/g, function(m) { - return m.replace(/,/g, '\u200C'); - }) - .split(/\s*(?![^(]*\)),\s*/) - .map(function(s) { - return s.replace(/\u200C/g, ','); - }); - } - - /** - * Parse declaration. - */ - - function declaration() { - var pos = position(); - - // prop - var prop = match(/^(\*?[-#\/\*\\\w]+(\[[0-9a-z_-]+\])?)\s*/); - if (!prop) return; - prop = trim(prop[0]); - - // : - if (!match(/^:\s*/)) return error("property missing ':'"); - - // val - var val = match(/^((?:'(?:\\'|.)*?'|"(?:\\"|.)*?"|\([^\)]*?\)|[^};])+)/); - - var ret = pos({ - type: 'declaration', - property: prop.replace(commentre, ''), - value: val ? trim(val[0]).replace(commentre, '') : '' - }); - - // ; - match(/^[;\s]*/); - - return ret; - } - - /** - * Parse declarations. - */ - - function declarations() { - var decls = []; - - if (!open()) return error("missing '{'"); - comments(decls); - - // declarations - var decl; - while (decl = declaration()) { - if (decl !== false) { - decls.push(decl); - comments(decls); - } - } - - if (!close()) return error("missing '}'"); - return decls; - } - - /** - * Parse keyframe. - */ - - function keyframe() { - var m; - var vals = []; - var pos = position(); - - while (m = match(/^((\d+\.\d+|\.\d+|\d+)%?|[a-z]+)\s*/)) { - vals.push(m[1]); - match(/^,\s*/); - } - - if (!vals.length) return; - - return pos({ - type: 'keyframe', - values: vals, - declarations: declarations() - }); - } - - /** - * Parse keyframes. - */ - - function atkeyframes() { - var pos = position(); - var m = match(/^@([-\w]+)?keyframes\s*/); - - if (!m) return; - var vendor = m[1]; - - // identifier - var m = match(/^([-\w]+)\s*/); - if (!m) return error("@keyframes missing name"); - var name = m[1]; - - if (!open()) return error("@keyframes missing '{'"); - - var frame; - var frames = comments(); - while (frame = keyframe()) { - frames.push(frame); - frames = frames.concat(comments()); - } - - if (!close()) return error("@keyframes missing '}'"); - - return pos({ - type: 'keyframes', - name: name, - vendor: vendor, - keyframes: frames - }); - } - - /** - * Parse supports. - */ - - function atsupports() { - var pos = position(); - var m = match(/^@supports *([^{]+)/); - - if (!m) return; - var supports = trim(m[1]); - - if (!open()) return error("@supports missing '{'"); - - var style = comments().concat(rules()); - - if (!close()) return error("@supports missing '}'"); - - return pos({ - type: 'supports', - supports: supports, - rules: style - }); - } - - /** - * Parse host. - */ - - function athost() { - var pos = position(); - var m = match(/^@host\s*/); - - if (!m) return; - - if (!open()) return error("@host missing '{'"); - - var style = comments().concat(rules()); - - if (!close()) return error("@host missing '}'"); - - return pos({ - type: 'host', - rules: style - }); - } - - /** - * Parse media. - */ - - function atmedia() { - var pos = position(); - var m = match(/^@media *([^{]+)/); - - if (!m) return; - var media = trim(m[1]); - - if (!open()) return error("@media missing '{'"); - - var style = comments().concat(rules()); - - if (!close()) return error("@media missing '}'"); - - return pos({ - type: 'media', - media: media, - rules: style - }); - } - - - /** - * Parse custom-media. - */ - - function atcustommedia() { - var pos = position(); - var m = match(/^@custom-media\s+(--[^\s]+)\s*([^{;]+);/); - if (!m) return; - - return pos({ - type: 'custom-media', - name: trim(m[1]), - media: trim(m[2]) - }); - } - - /** - * Parse paged media. - */ - - function atpage() { - var pos = position(); - var m = match(/^@page */); - if (!m) return; - - var sel = selector() || []; - - if (!open()) return error("@page missing '{'"); - var decls = comments(); - - // declarations - var decl; - while (decl = declaration()) { - decls.push(decl); - decls = decls.concat(comments()); - } - - if (!close()) return error("@page missing '}'"); - - return pos({ - type: 'page', - selectors: sel, - declarations: decls - }); - } - - /** - * Parse document. - */ - - function atdocument() { - var pos = position(); - var m = match(/^@([-\w]+)?document *([^{]+)/); - if (!m) return; - - var vendor = trim(m[1]); - var doc = trim(m[2]); - - if (!open()) return error("@document missing '{'"); - - var style = comments().concat(rules()); - - if (!close()) return error("@document missing '}'"); - - return pos({ - type: 'document', - document: doc, - vendor: vendor, - rules: style - }); - } - - /** - * Parse font-face. - */ - - function atfontface() { - var pos = position(); - var m = match(/^@font-face\s*/); - if (!m) return; - - if (!open()) return error("@font-face missing '{'"); - var decls = comments(); - - // declarations - var decl; - while (decl = declaration()) { - decls.push(decl); - decls = decls.concat(comments()); - } - - if (!close()) return error("@font-face missing '}'"); - - return pos({ - type: 'font-face', - declarations: decls - }); - } - - /** - * Parse import - */ - - var atimport = _compileAtrule('import'); - - /** - * Parse charset - */ - - var atcharset = _compileAtrule('charset'); - - /** - * Parse namespace - */ - - var atnamespace = _compileAtrule('namespace'); - - /** - * Parse non-block at-rules - */ - - - function _compileAtrule(name) { - var re = new RegExp('^@' + name + '\\s*([^;]+);'); - return function() { - var pos = position(); - var m = match(re); - if (!m) return; - var ret = { type: name }; - ret[name] = m[1].trim(); - return pos(ret); - } - } - - /** - * Parse at rule. - */ - - function atrule() { - if (css[0] != '@') return; - - return atkeyframes() - || atmedia() - || atcustommedia() - || atsupports() - || atimport() - || atcharset() - || atnamespace() - || atdocument() - || atpage() - || athost() - || atfontface(); - } - - /** - * Parse rule. - */ - - function rule() { - var pos = position(); - var sel = selector(); - - if (!sel) return error('selector missing'); - comments(); - - return pos({ - type: 'rule', - selectors: sel, - declarations: declarations() - }); - } - - return addParent(stylesheet()); -}; - -/** - * Trim `str`. - */ - -function trim(str) { - return str ? str.replace(/^\s+|\s+$/g, '') : ''; -} - -/** - * Adds non-enumerable parent node reference to each node. - */ - -function addParent(obj, parent) { - var isNode = obj && typeof obj.type === 'string'; - var childParent = isNode ? obj : parent; - - for (var k in obj) { - var value = obj[k]; - if (Array.isArray(value)) { - value.forEach(function(v) { addParent(v, childParent); }); - } else if (value && typeof value === 'object') { - addParent(value, childParent); - } - } - - if (isNode) { - Object.defineProperty(obj, 'parent', { - configurable: true, - writable: true, - enumerable: false, - value: parent || null - }); - } - - return obj; -} diff --git a/packages/core/css/lib/parse/index.ts b/packages/core/css/lib/parse/index.ts new file mode 100644 index 000000000..5552a4570 --- /dev/null +++ b/packages/core/css/lib/parse/index.ts @@ -0,0 +1,592 @@ +// http://www.w3.org/TR/CSS21/grammar.html +// https://github.com/visionmedia/css-parse/pull/49#issuecomment-30088027 +var commentre = /\/\*[^*]*\*+([^/*][^*]*\*+)*\//g; + +export function parse(css, options) { + options = options || {}; + + /** + * Positional. + */ + + var lineno = 1; + var column = 1; + + /** + * Update lineno and column based on `str`. + */ + + function updatePosition(str) { + var lines = str.match(/\n/g); + if (lines) lineno += lines.length; + var i = str.lastIndexOf('\n'); + column = ~i ? str.length - i : column + str.length; + } + + /** + * Mark position and patch `node.position`. + */ + + function position() { + var start = { line: lineno, column: column }; + return function (node) { + node.position = new Position(start); + whitespace(); + return node; + }; + } + + /** + * Store position information for a node + */ + + function Position(start) { + this.start = start; + this.end = { line: lineno, column: column }; + this.source = options.source; + } + + /** + * Non-enumerable source string + */ + + Position.prototype.content = css; + + /** + * Error `msg`. + */ + + var errorsList = []; + + function error(msg) { + var err: any = new Error(options.source + ':' + lineno + ':' + column + ': ' + msg); + err.reason = msg; + err.filename = options.source; + err.line = lineno; + err.column = column; + err.source = css; + + if (options.silent) { + errorsList.push(err); + } else { + throw err; + } + } + + /** + * Parse stylesheet. + */ + + function stylesheet() { + var rulesList = rules(); + + return { + type: 'stylesheet', + stylesheet: { + rules: rulesList, + parsingErrors: errorsList, + }, + }; + } + + /** + * Opening brace. + */ + + function open() { + return match(/^{\s*/); + } + + /** + * Closing brace. + */ + + function close() { + return match(/^}/); + } + + /** + * Parse ruleset. + */ + + function rules() { + var node; + var rules = []; + whitespace(); + comments(rules); + while (css.length && css.charAt(0) != '}' && (node = atrule() || rule())) { + if (node !== false) { + rules.push(node); + comments(rules); + } + } + return rules; + } + + /** + * Match `re` and return captures. + */ + + function match(re) { + var m = re.exec(css); + if (!m) return; + var str = m[0]; + updatePosition(str); + css = css.slice(str.length); + return m; + } + + /** + * Parse whitespace. + */ + + function whitespace() { + match(/^\s*/); + } + + /** + * Parse comments; + */ + + function comments(rules?) { + var c; + rules = rules || []; + while ((c = comment())) { + if (c !== false) { + rules.push(c); + } + } + return rules; + } + + /** + * Parse comment. + */ + + function comment() { + var pos = position(); + if ('/' != css.charAt(0) || '*' != css.charAt(1)) return; + + var i = 2; + while ('' != css.charAt(i) && ('*' != css.charAt(i) || '/' != css.charAt(i + 1))) ++i; + i += 2; + + if ('' === css.charAt(i - 1)) { + return error('End of comment missing'); + } + + var str = css.slice(2, i - 2); + column += 2; + updatePosition(str); + css = css.slice(i); + column += 2; + + return pos({ + type: 'comment', + comment: str, + }); + } + + /** + * Parse selector. + */ + + function selector() { + var m = match(/^([^{]+)/); + if (!m) return; + /* @fix Remove all comments from selectors + * http://ostermiller.org/findcomment.html */ + return trim(m[0]) + .replace(/\/\*([^*]|[\r\n]|(\*+([^*/]|[\r\n])))*\*\/+/g, '') + .replace(/"(?:\\"|[^"])*"|'(?:\\'|[^'])*'/g, function (m) { + return m.replace(/,/g, '\u200C'); + }) + .split(/\s*(?![^(]*\)),\s*/) + .map(function (s) { + return s.replace(/\u200C/g, ','); + }); + } + + /** + * Parse declaration. + */ + + function declaration() { + var pos = position(); + + // prop + var prop = match(/^(\*?[-#\/\*\\\w]+(\[[0-9a-z_-]+\])?)\s*/); + if (!prop) return; + prop = trim(prop[0]); + + // : + if (!match(/^:\s*/)) return error("property missing ':'"); + + // val + var val = match(/^((?:'(?:\\'|.)*?'|"(?:\\"|.)*?"|\([^\)]*?\)|[^};])+)/); + + var ret = pos({ + type: 'declaration', + property: prop.replace(commentre, ''), + value: val ? trim(val[0]).replace(commentre, '') : '', + }); + + // ; + match(/^[;\s]*/); + + return ret; + } + + /** + * Parse declarations. + */ + + function declarations() { + var decls = []; + + if (!open()) return error("missing '{'"); + comments(decls); + + // declarations + var decl; + while ((decl = declaration())) { + if (decl !== false) { + decls.push(decl); + comments(decls); + } + } + + if (!close()) return error("missing '}'"); + return decls; + } + + /** + * Parse keyframe. + */ + + function keyframe() { + var m; + var vals = []; + var pos = position(); + + while ((m = match(/^((\d+\.\d+|\.\d+|\d+)%?|[a-z]+)\s*/))) { + vals.push(m[1]); + match(/^,\s*/); + } + + if (!vals.length) return; + + return pos({ + type: 'keyframe', + values: vals, + declarations: declarations(), + }); + } + + /** + * Parse keyframes. + */ + + function atkeyframes() { + var pos = position(); + var m = match(/^@([-\w]+)?keyframes\s*/); + + if (!m) return; + var vendor = m[1]; + + // identifier + var m = match(/^([-\w]+)\s*/); + if (!m) return error('@keyframes missing name'); + var name = m[1]; + + if (!open()) return error("@keyframes missing '{'"); + + var frame; + var frames = comments(); + while ((frame = keyframe())) { + frames.push(frame); + frames = frames.concat(comments()); + } + + if (!close()) return error("@keyframes missing '}'"); + + return pos({ + type: 'keyframes', + name: name, + vendor: vendor, + keyframes: frames, + }); + } + + /** + * Parse supports. + */ + + function atsupports() { + var pos = position(); + var m = match(/^@supports *([^{]+)/); + + if (!m) return; + var supports = trim(m[1]); + + if (!open()) return error("@supports missing '{'"); + + var style = comments().concat(rules()); + + if (!close()) return error("@supports missing '}'"); + + return pos({ + type: 'supports', + supports: supports, + rules: style, + }); + } + + /** + * Parse host. + */ + + function athost() { + var pos = position(); + var m = match(/^@host\s*/); + + if (!m) return; + + if (!open()) return error("@host missing '{'"); + + var style = comments().concat(rules()); + + if (!close()) return error("@host missing '}'"); + + return pos({ + type: 'host', + rules: style, + }); + } + + /** + * Parse media. + */ + + function atmedia() { + var pos = position(); + var m = match(/^@media *([^{]+)/); + + if (!m) return; + var media = trim(m[1]); + + if (!open()) return error("@media missing '{'"); + + var style = comments().concat(rules()); + + if (!close()) return error("@media missing '}'"); + + return pos({ + type: 'media', + media: media, + rules: style, + }); + } + + /** + * Parse custom-media. + */ + + function atcustommedia() { + var pos = position(); + var m = match(/^@custom-media\s+(--[^\s]+)\s*([^{;]+);/); + if (!m) return; + + return pos({ + type: 'custom-media', + name: trim(m[1]), + media: trim(m[2]), + }); + } + + /** + * Parse paged media. + */ + + function atpage() { + var pos = position(); + var m = match(/^@page */); + if (!m) return; + + var sel = selector() || []; + + if (!open()) return error("@page missing '{'"); + var decls = comments(); + + // declarations + var decl; + while ((decl = declaration())) { + decls.push(decl); + decls = decls.concat(comments()); + } + + if (!close()) return error("@page missing '}'"); + + return pos({ + type: 'page', + selectors: sel, + declarations: decls, + }); + } + + /** + * Parse document. + */ + + function atdocument() { + var pos = position(); + var m = match(/^@([-\w]+)?document *([^{]+)/); + if (!m) return; + + var vendor = trim(m[1]); + var doc = trim(m[2]); + + if (!open()) return error("@document missing '{'"); + + var style = comments().concat(rules()); + + if (!close()) return error("@document missing '}'"); + + return pos({ + type: 'document', + document: doc, + vendor: vendor, + rules: style, + }); + } + + /** + * Parse font-face. + */ + + function atfontface() { + var pos = position(); + var m = match(/^@font-face\s*/); + if (!m) return; + + if (!open()) return error("@font-face missing '{'"); + var decls = comments(); + + // declarations + var decl; + while ((decl = declaration())) { + decls.push(decl); + decls = decls.concat(comments()); + } + + if (!close()) return error("@font-face missing '}'"); + + return pos({ + type: 'font-face', + declarations: decls, + }); + } + + /** + * Parse import + */ + + var atimport = _compileAtrule('import'); + + /** + * Parse charset + */ + + var atcharset = _compileAtrule('charset'); + + /** + * Parse namespace + */ + + var atnamespace = _compileAtrule('namespace'); + + /** + * Parse non-block at-rules + */ + + function _compileAtrule(name) { + var re = new RegExp('^@' + name + '\\s*([^;]+);'); + return function () { + var pos = position(); + var m = match(re); + if (!m) return; + var ret = { type: name }; + ret[name] = m[1].trim(); + return pos(ret); + }; + } + + /** + * Parse at rule. + */ + + function atrule() { + if (css[0] != '@') return; + + return atkeyframes() || atmedia() || atcustommedia() || atsupports() || atimport() || atcharset() || atnamespace() || atdocument() || atpage() || athost() || atfontface(); + } + + /** + * Parse rule. + */ + + function rule() { + var pos = position(); + var sel = selector(); + + if (!sel) return error('selector missing'); + comments(); + + return pos({ + type: 'rule', + selectors: sel, + declarations: declarations(), + }); + } + + return addParent(stylesheet()); +} + +/** + * Trim `str`. + */ + +function trim(str) { + return str ? str.replace(/^\s+|\s+$/g, '') : ''; +} + +/** + * Adds non-enumerable parent node reference to each node. + */ + +function addParent(obj, parent?) { + var isNode = obj && typeof obj.type === 'string'; + var childParent = isNode ? obj : parent; + + for (var k in obj) { + var value = obj[k]; + if (Array.isArray(value)) { + value.forEach(function (v) { + addParent(v, childParent); + }); + } else if (value && typeof value === 'object') { + addParent(value, childParent); + } + } + + if (isNode) { + Object.defineProperty(obj, 'parent', { + configurable: true, + writable: true, + enumerable: false, + value: parent || null, + }); + } + + return obj; +} diff --git a/packages/core/css/parser.spec.ts b/packages/core/css/parser.spec.ts index 27fb7a094..c5023bf8b 100644 --- a/packages/core/css/parser.spec.ts +++ b/packages/core/css/parser.spec.ts @@ -6,7 +6,7 @@ import { CSSNativeScript } from './CSSNativeScript'; import * as fs from 'fs'; import * as path from 'path'; import * as shadyCss from 'shady-css-parser'; -const reworkCss = await import('./reworkcss.js'); +const reworkCss = await import('./reworkcss'); const parseCss: any = require('parse-css'); const gonzales: any = require('gonzales'); diff --git a/packages/core/css/reworkcss.js b/packages/core/css/reworkcss.js deleted file mode 100644 index e0b20ac33..000000000 --- a/packages/core/css/reworkcss.js +++ /dev/null @@ -1 +0,0 @@ -exports.parse = require('./lib/parse'); diff --git a/packages/core/css/reworkcss.d.ts b/packages/core/css/reworkcss.ts similarity index 82% rename from packages/core/css/reworkcss.d.ts rename to packages/core/css/reworkcss.ts index a20ecc113..fbdf846aa 100644 --- a/packages/core/css/reworkcss.d.ts +++ b/packages/core/css/reworkcss.ts @@ -1,3 +1,7 @@ +// exports.parse = require('./lib/parse'); + +import { parse } from './lib/parse'; + export interface Position { start: { line: number; column: number }; end: { line: number; column: number }; @@ -18,6 +22,7 @@ export interface Rule extends Node { declarations: Declaration[]; } +// @ts-ignore export type AtRule = KeyFrames | Media; export interface Keyframes extends Rule { @@ -43,5 +48,6 @@ export interface StyleSheet { export interface SyntaxTree { stylesheet: StyleSheet; } +// export function parse(css: string, options: any): SyntaxTree; -export function parse(css: string, options: any): SyntaxTree; +export { parse }; diff --git a/packages/core/debugger/dom-types.ts b/packages/core/debugger/dom-types.ts index fd01aa4a3..6face9719 100644 --- a/packages/core/debugger/dom-types.ts +++ b/packages/core/debugger/dom-types.ts @@ -1,7 +1,8 @@ import { InspectorEvents } from './devtools-elements-interfaces'; import { CSSComputedStyleProperty } from './css-agent'; -import { ViewBase } from '../ui/core/view-base'; - +import type { ViewBase } from '../ui/core/view-base'; +import { PercentLength } from '../ui/styling/length-shared'; +import { getSetProperties, getComputedCssValues } from '../ui/core/properties'; const ELEMENT_NODE_TYPE = 1; const ROOT_NODE_TYPE = 9; const propertyBlacklist = ['effectivePaddingLeft', 'effectivePaddingBottom', 'effectivePaddingRight', 'effectivePaddingTop', 'effectiveBorderTopWidth', 'effectiveBorderRightWidth', 'effectiveBorderBottomWidth', 'effectiveBorderLeftWidth', 'effectiveMinWidth', 'effectiveMinHeight', 'effectiveWidth', 'effectiveHeight', 'effectiveMarginLeft', 'effectiveMarginTop', 'effectiveMarginRight', 'effectiveMarginBottom', 'nodeName', 'nodeType', 'decodeWidth', 'decodeHeight', 'ng-reflect-items', 'domNode', 'touchListenerIsSet', 'bindingContext', 'nativeView']; @@ -10,9 +11,9 @@ function lazy(action: () => T): () => T { let _value: T; return () => _value || (_value = action()); } -const percentLengthToStringLazy = lazy<(length) => string>(() => require('../ui/styling/style-properties').PercentLength.convertToString); -const getSetPropertiesLazy = lazy<(view: ViewBase) => [string, any][]>(() => require('../ui/core/properties').getSetProperties); -const getComputedCssValuesLazy = lazy<(view: ViewBase) => [string, any][]>(() => require('../ui/core/properties').getComputedCssValues); +const percentLengthToStringLazy = lazy<(length) => string>(() => PercentLength.convertToString); +const getSetPropertiesLazy = lazy<(view: ViewBase) => [string, any][]>(() => getSetProperties); +const getComputedCssValuesLazy = lazy<(view: ViewBase) => [string, any][]>(() => getComputedCssValues); const registeredDomNodes = {}; export function getNodeById(id: number): DOMNode { diff --git a/packages/core/debugger/webinspector-css.ts b/packages/core/debugger/webinspector-css.ts index 20407dc8a..4c9c66f2a 100644 --- a/packages/core/debugger/webinspector-css.ts +++ b/packages/core/debugger/webinspector-css.ts @@ -1,14 +1,13 @@ -import * as inspectorCommandTypes from './InspectorBackendCommands'; -const inspectorCommands: typeof inspectorCommandTypes = require('./InspectorBackendCommands'); +import * as inspectorCommands from './InspectorBackendCommands'; import * as debuggerDomains from '.'; import { attachCSSInspectorCommandCallbacks } from './devtools-elements'; @inspectorCommands.DomainDispatcher('CSS') -export class CSSDomainDebugger implements inspectorCommandTypes.CSSDomain.CSSDomainDispatcher { +export class CSSDomainDebugger implements inspectorCommands.CSSDomain.CSSDomainDispatcher { private _enabled: boolean; - public events: inspectorCommandTypes.CSSDomain.CSSFrontend; + public events: inspectorCommands.CSSDomain.CSSFrontend; public commands: any; constructor() { @@ -46,33 +45,33 @@ export class CSSDomainDebugger implements inspectorCommandTypes.CSSDomain.CSSDom this._enabled = false; } - getMatchedStylesForNode(params: inspectorCommandTypes.CSSDomain.GetMatchedStylesForNodeMethodArguments): { - inlineStyle?: inspectorCommandTypes.CSSDomain.CSSStyle; - attributesStyle?: inspectorCommandTypes.CSSDomain.CSSStyle; - matchedCSSRules?: inspectorCommandTypes.CSSDomain.RuleMatch[]; - pseudoElements?: inspectorCommandTypes.CSSDomain.PseudoElementMatches[]; - inherited?: inspectorCommandTypes.CSSDomain.InheritedStyleEntry[]; - cssKeyframesRules?: inspectorCommandTypes.CSSDomain.CSSKeyframesRule[]; + getMatchedStylesForNode(params: inspectorCommands.CSSDomain.GetMatchedStylesForNodeMethodArguments): { + inlineStyle?: inspectorCommands.CSSDomain.CSSStyle; + attributesStyle?: inspectorCommands.CSSDomain.CSSStyle; + matchedCSSRules?: inspectorCommands.CSSDomain.RuleMatch[]; + pseudoElements?: inspectorCommands.CSSDomain.PseudoElementMatches[]; + inherited?: inspectorCommands.CSSDomain.InheritedStyleEntry[]; + cssKeyframesRules?: inspectorCommands.CSSDomain.CSSKeyframesRule[]; } { return {}; } // Returns the styles defined inline (explicitly in the "style" attribute and implicitly, using DOM attributes) for a DOM node identified by nodeId. - getInlineStylesForNode(params: inspectorCommandTypes.CSSDomain.GetInlineStylesForNodeMethodArguments): { - inlineStyle?: inspectorCommandTypes.CSSDomain.CSSStyle; - attributesStyle?: inspectorCommandTypes.CSSDomain.CSSStyle; + getInlineStylesForNode(params: inspectorCommands.CSSDomain.GetInlineStylesForNodeMethodArguments): { + inlineStyle?: inspectorCommands.CSSDomain.CSSStyle; + attributesStyle?: inspectorCommands.CSSDomain.CSSStyle; } { return {}; } // Returns the computed style for a DOM node identified by nodeId. - getComputedStyleForNode(params: inspectorCommandTypes.CSSDomain.GetComputedStyleForNodeMethodArguments): { - computedStyle: inspectorCommandTypes.CSSDomain.CSSComputedStyleProperty[]; + getComputedStyleForNode(params: inspectorCommands.CSSDomain.GetComputedStyleForNodeMethodArguments): { + computedStyle: inspectorCommands.CSSDomain.CSSComputedStyleProperty[]; } { return { computedStyle: this.commands.getComputedStylesForNode(params.nodeId), }; } // Requests information about platform fonts which we used to render child TextNodes in the given node. - getPlatformFontsForNode(params: inspectorCommandTypes.CSSDomain.GetPlatformFontsForNodeMethodArguments): { fonts: inspectorCommandTypes.CSSDomain.PlatformFontUsage[] } { + getPlatformFontsForNode(params: inspectorCommands.CSSDomain.GetPlatformFontsForNodeMethodArguments): { fonts: inspectorCommands.CSSDomain.PlatformFontUsage[] } { return { fonts: [ { @@ -87,7 +86,7 @@ export class CSSDomainDebugger implements inspectorCommandTypes.CSSDomain.CSSDom }; } // Returns the current textual content and the URL for a stylesheet. - getStyleSheetText(params: inspectorCommandTypes.CSSDomain.GetStyleSheetTextMethodArguments): { text: string } { + getStyleSheetText(params: inspectorCommands.CSSDomain.GetStyleSheetTextMethodArguments): { text: string } { return null; } } diff --git a/packages/core/debugger/webinspector-dom.ts b/packages/core/debugger/webinspector-dom.ts index c7cd2c4c1..f29824fb1 100644 --- a/packages/core/debugger/webinspector-dom.ts +++ b/packages/core/debugger/webinspector-dom.ts @@ -1,14 +1,13 @@ -import * as inspectorCommandTypes from './InspectorBackendCommands'; -const inspectorCommands: typeof inspectorCommandTypes = require('./InspectorBackendCommands'); +import * as inspectorCommands from './InspectorBackendCommands'; import * as debuggerDomains from '.'; import { attachDOMInspectorEventCallbacks, attachDOMInspectorCommandCallbacks } from './devtools-elements'; @inspectorCommands.DomainDispatcher('DOM') -export class DOMDomainDebugger implements inspectorCommandTypes.DOMDomain.DOMDomainDispatcher { +export class DOMDomainDebugger implements inspectorCommands.DOMDomain.DOMDomainDispatcher { private _enabled: boolean; - public events: inspectorCommandTypes.DOMDomain.DOMFrontend; + public events: inspectorCommands.DOMDomain.DOMFrontend; public commands: any; constructor() { @@ -47,41 +46,41 @@ export class DOMDomainDebugger implements inspectorCommandTypes.DOMDomain.DOMDom this._enabled = false; } - getDocument(): { root: inspectorCommandTypes.DOMDomain.Node } { + getDocument(): { root: inspectorCommands.DOMDomain.Node } { const domNode = this.commands.getDocument(); return { root: domNode }; } - removeNode(params: inspectorCommandTypes.DOMDomain.RemoveNodeMethodArguments): void { + removeNode(params: inspectorCommands.DOMDomain.RemoveNodeMethodArguments): void { this.commands.removeNode(params.nodeId); } - setAttributeValue(params: inspectorCommandTypes.DOMDomain.SetAttributeValueMethodArguments): void { + setAttributeValue(params: inspectorCommands.DOMDomain.SetAttributeValueMethodArguments): void { throw new Error('Method not implemented.'); } - setAttributesAsText(params: inspectorCommandTypes.DOMDomain.SetAttributesAsTextMethodArguments): void { + setAttributesAsText(params: inspectorCommands.DOMDomain.SetAttributesAsTextMethodArguments): void { this.commands.setAttributeAsText(params.nodeId, params.text, params.name); } - removeAttribute(params: inspectorCommandTypes.DOMDomain.RemoveAttributeMethodArguments): void { + removeAttribute(params: inspectorCommands.DOMDomain.RemoveAttributeMethodArguments): void { throw new Error('Method not implemented.'); } - performSearch(params: inspectorCommandTypes.DOMDomain.PerformSearchMethodArguments): { searchId: string; resultCount: number } { + performSearch(params: inspectorCommands.DOMDomain.PerformSearchMethodArguments): { searchId: string; resultCount: number } { return null; } - getSearchResults(params: inspectorCommandTypes.DOMDomain.GetSearchResultsMethodArguments): { nodeIds: inspectorCommandTypes.DOMDomain.NodeId[] } { + getSearchResults(params: inspectorCommands.DOMDomain.GetSearchResultsMethodArguments): { nodeIds: inspectorCommands.DOMDomain.NodeId[] } { return null; } - discardSearchResults(params: inspectorCommandTypes.DOMDomain.DiscardSearchResultsMethodArguments): void { + discardSearchResults(params: inspectorCommands.DOMDomain.DiscardSearchResultsMethodArguments): void { return; } - highlightNode(params: inspectorCommandTypes.DOMDomain.HighlightNodeMethodArguments): void { + highlightNode(params: inspectorCommands.DOMDomain.HighlightNodeMethodArguments): void { return; } @@ -89,7 +88,7 @@ export class DOMDomainDebugger implements inspectorCommandTypes.DOMDomain.DOMDom return; } - resolveNode(params: inspectorCommandTypes.DOMDomain.ResolveNodeMethodArguments): { object: inspectorCommandTypes.RuntimeDomain.RemoteObject } { + resolveNode(params: inspectorCommands.DOMDomain.ResolveNodeMethodArguments): { object: inspectorCommands.RuntimeDomain.RemoteObject } { return null; } } diff --git a/packages/core/debugger/webinspector-network.android.ts b/packages/core/debugger/webinspector-network.android.ts index 2e1536c39..1325f4174 100644 --- a/packages/core/debugger/webinspector-network.android.ts +++ b/packages/core/debugger/webinspector-network.android.ts @@ -1,8 +1,7 @@ -import * as inspectorCommandTypes from './InspectorBackendCommands'; -const inspectorCommands: typeof inspectorCommandTypes = require('./InspectorBackendCommands'); +import * as inspectorCommands from './InspectorBackendCommands'; +import { getNativeApp } from '../application/helpers-common'; import * as debuggerDomains from '.'; -const getApplicationContext = () => require('../utils/android').getApplicationContext(); declare let __inspectorSendEvent; @@ -103,7 +102,7 @@ export class Request { } } - public responseReceived(response: inspectorCommandTypes.NetworkDomain.Response): void { + public responseReceived(response: inspectorCommands.NetworkDomain.Response): void { if (this._networkDomainDebugger.enabled) { this._networkDomainDebugger.events.responseReceived(this.requestID, frameId, loaderId, __inspectorTimestamp(), this.resourceType, response); } @@ -115,7 +114,7 @@ export class Request { } } - public requestWillBeSent(request: inspectorCommandTypes.NetworkDomain.Request): void { + public requestWillBeSent(request: inspectorCommands.NetworkDomain.Request): void { if (this._networkDomainDebugger.enabled) { this._networkDomainDebugger.events.requestWillBeSent(this.requestID, frameId, loaderId, request.url, request, __inspectorTimestamp(), { type: 'Script' }); } @@ -123,9 +122,9 @@ export class Request { } @inspectorCommands.DomainDispatcher('Network') -export class NetworkDomainDebugger implements inspectorCommandTypes.NetworkDomain.NetworkDomainDispatcher { +export class NetworkDomainDebugger implements inspectorCommands.NetworkDomain.NetworkDomainDispatcher { private _enabled: boolean; - public events: inspectorCommandTypes.NetworkDomain.NetworkFrontend; + public events: inspectorCommands.NetworkDomain.NetworkFrontend; constructor() { this.events = new inspectorCommands.NetworkDomain.NetworkFrontend(); @@ -164,14 +163,14 @@ export class NetworkDomainDebugger implements inspectorCommandTypes.NetworkDomai /** * Specifies whether to always send extra HTTP headers with the requests from this page. */ - setExtraHTTPHeaders(params: inspectorCommandTypes.NetworkDomain.SetExtraHTTPHeadersMethodArguments): void { + setExtraHTTPHeaders(params: inspectorCommands.NetworkDomain.SetExtraHTTPHeadersMethodArguments): void { // } /** * Returns content served for the given request. */ - getResponseBody(params: inspectorCommandTypes.NetworkDomain.GetResponseBodyMethodArguments): { body: string; base64Encoded: boolean } { + getResponseBody(params: inspectorCommands.NetworkDomain.GetResponseBodyMethodArguments): { body: string; base64Encoded: boolean } { const resource_data = resources_datas[params.requestId]; // java.io.ByteArrayOutputStream const body = resource_data.hasTextContent ? resource_data.data.toString('UTF-8') : android.util.Base64.encodeToString(resource_data.data?.buf?.(), android.util.Base64.NO_WRAP); @@ -218,15 +217,15 @@ export class NetworkDomainDebugger implements inspectorCommandTypes.NetworkDomai /** * Toggles ignoring cache for each request. If true, cache will not be used. */ - setCacheDisabled(params: inspectorCommandTypes.NetworkDomain.SetCacheDisabledMethodArguments): void { + setCacheDisabled(params: inspectorCommands.NetworkDomain.SetCacheDisabledMethodArguments): void { // } /** * Loads a resource in the context of a frame on the inspected page without cross origin checks. */ - loadResource(params: inspectorCommandTypes.NetworkDomain.LoadResourceMethodArguments): { content: string; mimeType: string; status: number } { - const appPath = getApplicationContext().getFilesDir().getCanonicalPath() + '/app'; + loadResource(params: inspectorCommands.NetworkDomain.LoadResourceMethodArguments): { content: string; mimeType: string; status: number } { + const appPath = (getNativeApp() as android.app.Application).getApplicationContext().getFilesDir().getCanonicalPath() + '/app'; const pathUrl = params.url.replace('file://', appPath); const file = new java.io.File(pathUrl); const is = file.exists() ? new java.io.FileInputStream(file) : undefined; diff --git a/packages/core/debugger/webinspector-network.ios.ts b/packages/core/debugger/webinspector-network.ios.ts index b8ddf0771..5af7487bc 100644 --- a/packages/core/debugger/webinspector-network.ios.ts +++ b/packages/core/debugger/webinspector-network.ios.ts @@ -1,5 +1,4 @@ -import * as inspectorCommandTypes from './InspectorBackendCommands'; -const inspectorCommands: typeof inspectorCommandTypes = require('./InspectorBackendCommands'); +import * as inspectorCommands from './InspectorBackendCommands'; import * as debuggerDomains from '.'; @@ -102,7 +101,7 @@ export class Request { } } - public responseReceived(response: inspectorCommandTypes.NetworkDomain.Response): void { + public responseReceived(response: inspectorCommands.NetworkDomain.Response): void { if (this._networkDomainDebugger.enabled) { this._networkDomainDebugger.events.responseReceived(this.requestID, frameId, loaderId, __inspectorTimestamp(), this.resourceType, response); } @@ -114,7 +113,7 @@ export class Request { } } - public requestWillBeSent(request: inspectorCommandTypes.NetworkDomain.Request): void { + public requestWillBeSent(request: inspectorCommands.NetworkDomain.Request): void { if (this._networkDomainDebugger.enabled) { this._networkDomainDebugger.events.requestWillBeSent(this.requestID, frameId, loaderId, request.url, request, __inspectorTimestamp(), { type: 'Script' }); } @@ -122,9 +121,9 @@ export class Request { } @inspectorCommands.DomainDispatcher('Network') -export class NetworkDomainDebugger implements inspectorCommandTypes.NetworkDomain.NetworkDomainDispatcher { +export class NetworkDomainDebugger implements inspectorCommands.NetworkDomain.NetworkDomainDispatcher { private _enabled: boolean; - public events: inspectorCommandTypes.NetworkDomain.NetworkFrontend; + public events: inspectorCommands.NetworkDomain.NetworkFrontend; constructor() { this.events = new inspectorCommands.NetworkDomain.NetworkFrontend(); @@ -163,14 +162,14 @@ export class NetworkDomainDebugger implements inspectorCommandTypes.NetworkDomai /** * Specifies whether to always send extra HTTP headers with the requests from this page. */ - setExtraHTTPHeaders(params: inspectorCommandTypes.NetworkDomain.SetExtraHTTPHeadersMethodArguments): void { + setExtraHTTPHeaders(params: inspectorCommands.NetworkDomain.SetExtraHTTPHeadersMethodArguments): void { // } /** * Returns content served for the given request. */ - getResponseBody(params: inspectorCommandTypes.NetworkDomain.GetResponseBodyMethodArguments): { body: string; base64Encoded: boolean } { + getResponseBody(params: inspectorCommands.NetworkDomain.GetResponseBodyMethodArguments): { body: string; base64Encoded: boolean } { const resource_data = resources_datas[params.requestId]; const body = resource_data.hasTextContent ? NSString.alloc().initWithDataEncoding(resource_data.data, 4).toString() : resource_data.data.base64EncodedStringWithOptions(0); @@ -217,14 +216,14 @@ export class NetworkDomainDebugger implements inspectorCommandTypes.NetworkDomai /** * Toggles ignoring cache for each request. If true, cache will not be used. */ - setCacheDisabled(params: inspectorCommandTypes.NetworkDomain.SetCacheDisabledMethodArguments): void { + setCacheDisabled(params: inspectorCommands.NetworkDomain.SetCacheDisabledMethodArguments): void { // } /** * Loads a resource in the context of a frame on the inspected page without cross origin checks. */ - loadResource(params: inspectorCommandTypes.NetworkDomain.LoadResourceMethodArguments): { content: string; mimeType: string; status: number } { + loadResource(params: inspectorCommands.NetworkDomain.LoadResourceMethodArguments): { content: string; mimeType: string; status: number } { const appPath = NSBundle.mainBundle.bundlePath; const pathUrl = params.url.replace('file://', appPath); const fileManager = NSFileManager.defaultManager; diff --git a/packages/core/file-system/index.ts b/packages/core/file-system/index.ts index 42055416c..7e8b0a90f 100644 --- a/packages/core/file-system/index.ts +++ b/packages/core/file-system/index.ts @@ -1,6 +1,6 @@ import { IFileSystemAccess, FileSystemAccess, FileSystemAccess29 } from './file-system-access'; import { SDK_VERSION } from '../utils/constants'; -import { android as androidUtils } from '../utils'; +import { getNativeApp } from '../application/helpers-common'; // The FileSystemAccess implementation, used through all the APIs. let fileAccess: IFileSystemAccess; @@ -270,7 +270,7 @@ class Android { throw new Error(`createFile is available on Android only!`); } - const context = androidUtils.getApplicationContext() as android.content.Context; + const context = (getNativeApp() as android.app.Application).getApplicationContext() as android.content.Context; const meta = new android.content.ContentValues(); meta.put(android.provider.MediaStore.MediaColumns.DISPLAY_NAME, options.name); @@ -323,7 +323,7 @@ export class File extends FileSystemEntity { // falls back to creating a temp file without a known extension. if (!fileInfo) { const tempFile = `${knownFolders.temp().path}/${java.util.UUID.randomUUID().toString()}`; - org.nativescript.widgets.Async.File.copySync(path, tempFile, androidUtils.getApplicationContext()); + org.nativescript.widgets.Async.File.copySync(path, tempFile, (getNativeApp() as android.app.Application).getApplicationContext()); path = tempFile; } else { const ext = fileInfo.extension; diff --git a/packages/core/http/http-interfaces.ts b/packages/core/http/http-interfaces.ts new file mode 100644 index 000000000..1790113e6 --- /dev/null +++ b/packages/core/http/http-interfaces.ts @@ -0,0 +1,98 @@ +import type { ImageSource } from '../image-source'; +import type { File } from '../file-system'; + +/** + * Provides options for the http requests. + */ +export interface HttpRequestOptions { + /** + * Gets or sets the request url. + */ + url: string; + + /** + * Gets or sets the request method. + */ + method: string; + + /** + * Gets or sets the request headers in JSON format. + */ + headers?: any; + + /** + * Gets or sets the request body. + */ + content?: string | FormData | ArrayBuffer | Uint8Array; + + /** + * Gets or sets the request timeout in milliseconds. + */ + timeout?: number; + + /** + * Gets or sets whether to *not* follow server's redirection responses. + */ + dontFollowRedirects?: boolean; +} + +/** + * Encapsulates HTTP-response information from an HTTP-request. + */ +export interface HttpResponse { + /** + * Gets the response status code. + */ + statusCode: number; + + /** + * Gets the response headers. + */ + headers: Headers; + + /** + * Gets the response content. + */ + content?: HttpContent; +} + +export type Headers = { [key: string]: string | string[] }; + +export enum HttpResponseEncoding { + UTF8, + GBK, +} +/** + * Encapsulates the content of an HttpResponse. + */ +export interface HttpContent { + /** + * Gets the response body as raw data. + */ + raw: any; + + /** + * Gets the response body as ArrayBuffer + */ + toArrayBuffer: () => ArrayBuffer; + + /** + * Gets the response body as string. + */ + toString: (encoding?: HttpResponseEncoding) => string; + + /** + * Gets the response body as JSON object. + */ + toJSON: (encoding?: HttpResponseEncoding) => any; + + /** + * Gets the response body as ImageSource. + */ + toImage: () => Promise; + + /** + * Gets the response body as file. + */ + toFile: (destinationFilePath?: string) => File; +} diff --git a/packages/core/http/http-request/http-request-common.ts b/packages/core/http/http-request/http-request-common.ts index 12bb5e5f5..d2088d481 100644 --- a/packages/core/http/http-request/http-request-common.ts +++ b/packages/core/http/http-request/http-request-common.ts @@ -1,7 +1,6 @@ -import * as fsModule from '../../file-system'; +import { knownFolders, path } from '../../file-system'; export function getFilenameFromUrl(url: string) { - const fs: typeof fsModule = require('../../file-system'); const slashPos = url.lastIndexOf('/') + 1; const questionMarkPos = url.lastIndexOf('?'); @@ -12,7 +11,7 @@ export function getFilenameFromUrl(url: string) { actualFileName = url.substring(slashPos); } - const result = fs.path.join(fs.knownFolders.documents().path, actualFileName); + const result = path.join(knownFolders.documents().path, actualFileName); return result; } diff --git a/packages/core/http/http-request/index.android.ts b/packages/core/http/http-request/index.android.ts index 470a97aa8..ec96a8da9 100644 --- a/packages/core/http/http-request/index.android.ts +++ b/packages/core/http/http-request/index.android.ts @@ -1,17 +1,12 @@ // imported for definition purposes only import * as httpModule from '../../http'; -import * as imageSourceModule from '../../image-source'; +import { ImageSource } from '../../image-source'; import { Screen } from '../../platform'; -import * as fsModule from '../../file-system'; - +import { File } from '../../file-system'; +import { HttpResponseEncoding } from '../http-interfaces'; import { getFilenameFromUrl } from './http-request-common'; import * as domainDebugger from '../../debugger'; -export enum HttpResponseEncoding { - UTF8, - GBK, -} - function parseJSON(source: string): any { const src = source.trim(); if (src.lastIndexOf(')') === src.length - 1) { @@ -24,19 +19,6 @@ function parseJSON(source: string): any { let requestIdCounter = 0; const pendingRequests = {}; -let imageSource: typeof imageSourceModule; -function ensureImageSource() { - if (!imageSource) { - imageSource = require('../../image-source'); - } -} - -let fs: typeof fsModule; -function ensureFileSystem() { - if (!fs) { - fs = require('../../file-system'); - } -} let debugRequests: Map; if (__DEV__) { debugRequests = new Map(); @@ -151,26 +133,22 @@ function onRequestComplete(requestId: number, result: org.nativescript.widgets.A return parseJSON(str); }, toImage: () => { - ensureImageSource(); - return new Promise((resolveImage, rejectImage) => { if (result.responseAsImage != null) { - resolveImage(new imageSource.ImageSource(result.responseAsImage)); + resolveImage(new ImageSource(result.responseAsImage)); } else { rejectImage(new Error('Response content may not be converted to an Image')); } }); }, toFile: (destinationFilePath: string) => { - ensureFileSystem(); - if (!destinationFilePath) { destinationFilePath = getFilenameFromUrl(callbacks.url); } let stream: java.io.FileOutputStream; try { // ensure destination path exists by creating any missing parent directories - const file = fs.File.fromPath(destinationFilePath); + const file = File.fromPath(destinationFilePath); const javaFile = new java.io.File(destinationFilePath); stream = new java.io.FileOutputStream(javaFile); diff --git a/packages/core/http/http-request/index.d.ts b/packages/core/http/http-request/index.d.ts index 5acc3ce14..277e298a6 100644 --- a/packages/core/http/http-request/index.d.ts +++ b/packages/core/http/http-request/index.d.ts @@ -1,3 +1,3 @@ -import { HttpRequestOptions, HttpResponse, Headers } from '..'; +import { HttpRequestOptions, HttpResponse, Headers } from '../http-interfaces'; export const request: (options: HttpRequestOptions) => Promise; export function addHeader(headers: Headers, key: string, value: string): void; diff --git a/packages/core/http/http-request/index.ios.ts b/packages/core/http/http-request/index.ios.ts index 4e004d64c..c7448bd68 100644 --- a/packages/core/http/http-request/index.ios.ts +++ b/packages/core/http/http-request/index.ios.ts @@ -1,18 +1,12 @@ -// imported for definition purposes only -import * as httpModule from '../../http'; -import * as imageSourceModule from '../../image-source'; -import * as fsModule from '../../file-system'; - import { SDK_VERSION } from '../../utils/constants'; import { isRealDevice } from '../../utils'; import * as types from '../../utils/types'; import * as domainDebugger from '../../debugger'; import { getFilenameFromUrl } from './http-request-common'; - -export enum HttpResponseEncoding { - UTF8, - GBK, -} +import { ImageSource } from '../../image-source'; +import { File } from '../../file-system'; +import type { HttpRequestOptions, HttpResponse, Headers } from '../http-interfaces'; +import { HttpResponseEncoding } from '../http-interfaces'; const currentDevice = UIDevice.currentDevice; const device = currentDevice.userInterfaceIdiom === UIUserInterfaceIdiom.Phone ? 'Phone' : 'Pad'; @@ -58,22 +52,8 @@ function ensureSessionNotFollowingRedirects() { } } -let imageSource: typeof imageSourceModule; -function ensureImageSource() { - if (!imageSource) { - imageSource = require('../../image-source'); - } -} - -let fs: typeof fsModule; -function ensureFileSystem() { - if (!fs) { - fs = require('../../file-system'); - } -} - -export function request(options: httpModule.HttpRequestOptions): Promise { - return new Promise((resolve, reject) => { +export function request(options: HttpRequestOptions): Promise { + return new Promise((resolve, reject) => { if (!options.url) { reject(new Error('Request url was empty.')); @@ -121,7 +101,7 @@ export function request(options: httpModule.HttpRequestOptions): Promise parseJSON(NSDataToString(data, encoding)), toImage: () => { - ensureImageSource(); - return new Promise((resolve, reject) => { (UIImage).tns_decodeImageWithDataCompletion(data, (image) => { if (image) { - resolve(new imageSource.ImageSource(image)); + resolve(new ImageSource(image)); } else { reject(new Error('Response content may not be converted to an Image')); } @@ -190,14 +168,12 @@ export function request(options: httpModule.HttpRequestOptions): Promise { - ensureFileSystem(); - if (!destinationFilePath) { destinationFilePath = getFilenameFromUrl(options.url); } if (data instanceof NSData) { // ensure destination path exists by creating any missing parent directories - const file = fs.File.fromPath(destinationFilePath); + const file = File.fromPath(destinationFilePath); data.writeToFileAtomically(destinationFilePath, true); @@ -250,7 +226,7 @@ function NSDataToString(data: any, encoding?: HttpResponseEncoding): string { return encodedString.toString(); } -export function addHeader(headers: httpModule.Headers, key: string, value: string): void { +export function addHeader(headers: Headers, key: string, value: string): void { if (!headers[key]) { headers[key] = value; } else if (Array.isArray(headers[key])) { diff --git a/packages/core/http/http-shared.ts b/packages/core/http/http-shared.ts index 827ea86ea..d2e21b3ca 100644 --- a/packages/core/http/http-shared.ts +++ b/packages/core/http/http-shared.ts @@ -7,3 +7,15 @@ export interface ImageSourceLike { toBase64String(format: string, quality?: number): string; // ... add other shared methods/properties as needed } + +// Circular dependency resolution handling (http <--> image-source) +let _getImage: (arg: any) => Promise; +export function getImageRequest(arg: any): Promise { + if (_getImage) { + return _getImage(arg); + } + return Promise.reject(new Error('No getImage request handler set.')); +} +export function setGetImageRequest(fn: (arg: any) => Promise) { + _getImage = fn; +} diff --git a/packages/core/http/index.d.ts b/packages/core/http/index.d.ts index b039c73c1..147849780 100644 --- a/packages/core/http/index.d.ts +++ b/packages/core/http/index.d.ts @@ -1,6 +1,5 @@ -// import { ImageSource } from '../image-source'; -import type { ImageSourceLike } from './http-shared'; -import { File } from '../file-system'; +import type { ImageSource } from '../image-source'; +export * from './http-interfaces'; /** * Downloads the content from the specified URL as a string. @@ -30,13 +29,13 @@ export function getJSON(options: HttpRequestOptions): Promise; * Downloads the content from the specified URL and attempts to decode it as an image. * @param url The URL to request from. */ -export function getImage(url: string): Promise; +export function getImage(url: string): Promise; /** * Downloads the content from the specified URL and attempts to decode it as an image. * @param options An object that specifies various request options. */ -export function getImage(options: HttpRequestOptions): Promise; +export function getImage(options: HttpRequestOptions): Promise; /** * Downloads the content from the specified URL and attempts to save it as file. @@ -158,10 +157,10 @@ export interface HttpContent { /** * Gets the response body as ImageSource. */ - toImage: () => Promise; + toImage: () => Promise; /** * Gets the response body as file. */ - toFile: (destinationFilePath?: string) => File; + toFile: (destinationFilePath?: string) => any /* File */; } diff --git a/packages/core/http/index.ts b/packages/core/http/index.ts index 6f8517513..d625b89a6 100644 --- a/packages/core/http/index.ts +++ b/packages/core/http/index.ts @@ -1,107 +1,11 @@ -import type { ImageSourceLike } from './http-shared'; -import { File } from '../file-system'; -import * as httpRequest from './http-request'; +import { setGetImageRequest, type ImageSourceLike } from './http-shared'; +import { request } from './http-request'; export * from './http-request'; - -/** - * Provides options for the http requests. - */ -export interface HttpRequestOptions { - /** - * Gets or sets the request url. - */ - url: string; - - /** - * Gets or sets the request method. - */ - method: string; - - /** - * Gets or sets the request headers in JSON format. - */ - headers?: any; - - /** - * Gets or sets the request body. - */ - content?: string | FormData | ArrayBuffer | Uint8Array; - - /** - * Gets or sets the request timeout in milliseconds. - */ - timeout?: number; - - /** - * Gets or sets whether to *not* follow server's redirection responses. - */ - dontFollowRedirects?: boolean; -} - -/** - * Encapsulates HTTP-response information from an HTTP-request. - */ -export interface HttpResponse { - /** - * Gets the response status code. - */ - statusCode: number; - - /** - * Gets the response headers. - */ - headers: Headers; - - /** - * Gets the response content. - */ - content?: HttpContent; -} - -export type Headers = { [key: string]: string | string[] }; - -export enum HttpResponseEncoding { - UTF8, - GBK, -} -/** - * Encapsulates the content of an HttpResponse. - */ -export interface HttpContent { - /** - * Gets the response body as raw data. - */ - raw: any; - - /** - * Gets the response body as ArrayBuffer - */ - toArrayBuffer: () => ArrayBuffer; - - /** - * Gets the response body as string. - */ - toString: (encoding?: HttpResponseEncoding) => string; - - /** - * Gets the response body as JSON object. - */ - toJSON: (encoding?: HttpResponseEncoding) => any; - - /** - * Gets the response body as ImageSource. - */ - toImage: () => Promise; - - /** - * Gets the response body as file. - */ - toFile: (destinationFilePath?: string) => File; -} +export * from './http-interfaces'; export function getString(arg: any): Promise { return new Promise((resolve, reject) => { - httpRequest.request(typeof arg === 'string' ? { url: arg, method: 'GET' } : arg).then( + request(typeof arg === 'string' ? { url: arg, method: 'GET' } : arg).then( (r) => { try { const str = r.content.toString(); @@ -117,7 +21,7 @@ export function getString(arg: any): Promise { export function getJSON(arg: any): Promise { return new Promise((resolve, reject) => { - httpRequest.request(typeof arg === 'string' ? { url: arg, method: 'GET' } : arg).then( + request(typeof arg === 'string' ? { url: arg, method: 'GET' } : arg).then( (r) => { try { const json = r.content.toJSON(); @@ -133,7 +37,7 @@ export function getJSON(arg: any): Promise { export function getImage(arg: any): Promise { return new Promise((resolve, reject) => { - httpRequest.request(typeof arg === 'string' ? { url: arg, method: 'GET' } : arg).then( + request(typeof arg === 'string' ? { url: arg, method: 'GET' } : arg).then( (r) => { try { resolve(r.content.toImage()); @@ -147,10 +51,11 @@ export function getImage(arg: any): Promise { ); }); } +setGetImageRequest(getImage); export function getFile(arg: any, destinationFilePath?: string): Promise { return new Promise((resolve, reject) => { - httpRequest.request(typeof arg === 'string' ? { url: arg, method: 'GET' } : arg).then( + request(typeof arg === 'string' ? { url: arg, method: 'GET' } : arg).then( (r) => { try { const file = r.content.toFile(destinationFilePath); @@ -166,7 +71,7 @@ export function getFile(arg: any, destinationFilePath?: string): Promise { export function getBinary(arg: any): Promise { return new Promise((resolve, reject) => { - httpRequest.request(typeof arg === 'string' ? { url: arg, method: 'GET' } : arg).then( + request(typeof arg === 'string' ? { url: arg, method: 'GET' } : arg).then( (r) => { try { const arrayBuffer = r.content.toArrayBuffer(); diff --git a/packages/core/image-source/index.android.ts b/packages/core/image-source/index.android.ts index e51847f2b..224802877 100644 --- a/packages/core/image-source/index.android.ts +++ b/packages/core/image-source/index.android.ts @@ -1,6 +1,6 @@ import { ImageSource as ImageSourceDefinition, iosSymbolScaleType } from '.'; import { ImageAsset } from '../image-asset'; -import * as http from '../http'; +import { getImageRequest } from '../http/http-shared'; import { path as fsPath, knownFolders } from '../file-system'; import { isFileOrResourcePath, RESOURCE_PREFIX, layout } from '../utils'; import { getNativeApp } from '../application/helpers-common'; @@ -63,7 +63,7 @@ export class ImageSource implements ImageSourceDefinition { } static fromUrl(url: string): Promise { - return http.getImage(url) as Promise; + return getImageRequest(url) as Promise; } static fromResourceSync(name: string): ImageSource { diff --git a/packages/core/image-source/index.ios.ts b/packages/core/image-source/index.ios.ts index aa555d427..684cb3eb6 100644 --- a/packages/core/image-source/index.ios.ts +++ b/packages/core/image-source/index.ios.ts @@ -1,17 +1,13 @@ -// Definitions. import { ImageSource as ImageSourceDefinition, iosSymbolScaleType } from '.'; import { ImageAsset } from '../image-asset'; import type { ImageBase } from '../ui/image/image-common'; import { Font } from '../ui/styling/font'; import { Color } from '../color'; import { Trace } from '../trace'; - -// Types. import { path as fsPath, knownFolders } from '../file-system'; import { isFileOrResourcePath, RESOURCE_PREFIX, layout, releaseNativeObject, SYSTEM_PREFIX } from '../utils'; - import { getScaledDimensions } from './image-source-common'; -import * as http from '../http'; +import { getImageRequest } from '../http/http-shared'; export { isFileOrResourcePath }; @@ -62,7 +58,7 @@ export class ImageSource implements ImageSourceDefinition { } static fromUrl(url: string): Promise { - return http.getImage(url) as Promise; + return getImageRequest(url) as Promise; } static iosSystemScaleFor(scale: iosSymbolScaleType) { diff --git a/packages/core/inspector_modules.ts b/packages/core/inspector_modules.ts index 79c21d582..ca9c177a1 100644 --- a/packages/core/inspector_modules.ts +++ b/packages/core/inspector_modules.ts @@ -1,5 +1,8 @@ import './globals'; -require('./debugger/webinspector-network'); -require('./debugger/webinspector-dom'); -require('./debugger/webinspector-css'); +import './debugger/webinspector-network'; +import './debugger/webinspector-dom'; +import './debugger/webinspector-css'; +// require('./debugger/webinspector-network'); +// require('./debugger/webinspector-dom'); +// require('./debugger/webinspector-css'); diff --git a/packages/core/js-libs/easysax/easysax.js b/packages/core/js-libs/easysax/easysax.js index f625d6449..3d11cf2a7 100644 --- a/packages/core/js-libs/easysax/easysax.js +++ b/packages/core/js-libs/easysax/easysax.js @@ -59,11 +59,11 @@ // << ------------------------------------------------------------------------ >> // -if (typeof exports === 'object' /*&& this == exports*/) { - module.exports.EasySAXParser = EasySAXParser; -}; +// if (typeof exports === 'object' /*&& this == exports*/) { +// module.exports.EasySAXParser = EasySAXParser; +// }; -function EasySAXParser() { +export function EasySAXParser() { 'use strict'; if (!this) return null; diff --git a/packages/core/platform/screen/index.ios.ts b/packages/core/platform/screen/index.ios.ts index acfed170d..f2bcc52d1 100644 --- a/packages/core/platform/screen/index.ios.ts +++ b/packages/core/platform/screen/index.ios.ts @@ -1,4 +1,4 @@ -import { getWindow } from '../../utils/ios-helper'; +import { getWindow } from '../../utils/native-helper'; class MainScreen { private _screen: UIScreen; diff --git a/packages/core/profiling/index.ts b/packages/core/profiling/index.ts index e7affab0b..d549bd878 100644 --- a/packages/core/profiling/index.ts +++ b/packages/core/profiling/index.ts @@ -158,7 +158,9 @@ export function enable(mode: InstrumentationMode = 'counters') { } try { - const appConfig = require('~/package.json'); + // @ts-ignore + const appConfig = await import('~/package.json'); + console.log('profiling appConfig:', appConfig); if (appConfig && appConfig.profiling) { enable(appConfig.profiling); } diff --git a/packages/core/project.json b/packages/core/project.json index 7fb25d8bb..35ad0ceed 100644 --- a/packages/core/project.json +++ b/packages/core/project.json @@ -16,18 +16,7 @@ "outputPath": "{workspaceRoot}/dist/packages/core", "rootDir": "{projectRoot}", "main": "{projectRoot}/index.ts", - "assets": [ - "{workspaceRoot}/LICENSE", - "{projectRoot}/README.md", - "{projectRoot}/global-types.d.ts", - "{projectRoot}/fetch/LICENSE", - { "glob": "**/*", "input": "{projectRoot}/js-libs/", "output": "./js-libs/" }, - { "glob": "**/*", "input": "{projectRoot}/cli-hooks/", "output": "./cli-hooks/" }, - { "glob": "**/*", "input": "{projectRoot}/css/", "output": "./css/" }, - { "glob": "**/*", "input": "{projectRoot}/css-value/", "output": "./css-value/" }, - { "glob": "**/*", "input": "{projectRoot}/platforms/", "output": "./platforms/" }, - { "glob": "**/*.d.ts", "input": "{projectRoot}/", "output": "./" } - ] + "assets": ["{workspaceRoot}/LICENSE", "{projectRoot}/README.md", "{projectRoot}/global-types.d.ts", "{projectRoot}/fetch/LICENSE", { "glob": "**/*", "input": "{projectRoot}/js-libs/", "output": "./js-libs/" }, { "glob": "**/*", "input": "{projectRoot}/cli-hooks/", "output": "./cli-hooks/" }, { "glob": "**/*", "input": "{projectRoot}/platforms/", "output": "./platforms/" }, { "glob": "**/*.d.ts", "input": "{projectRoot}/", "output": "./" }] }, "dependsOn": ["^build"] }, diff --git a/packages/core/ui/animation/index.android.ts b/packages/core/ui/animation/index.android.ts index f6294898a..14ef2ce9c 100644 --- a/packages/core/ui/animation/index.android.ts +++ b/packages/core/ui/animation/index.android.ts @@ -5,9 +5,9 @@ import { Color } from '../../color'; import { Trace } from '../../trace'; import { opacityProperty, backgroundColorProperty, rotateProperty, rotateXProperty, rotateYProperty, translateXProperty, translateYProperty, scaleXProperty, scaleYProperty, heightProperty, widthProperty } from '../styling/style-properties'; import { PercentLength } from '../styling/length-shared'; -import { layout } from '../../utils'; +import { layout } from '../../utils/layout-helper'; import { SDK_VERSION } from '../../utils/constants'; -import { Device, Screen } from '../../platform'; +import { Screen } from '../../platform/screen'; import lazy from '../../utils/lazy'; export * from './animation-common'; diff --git a/packages/core/ui/builder/component-builder/index.ts b/packages/core/ui/builder/component-builder/index.ts index 45ee4b88a..2c3353185 100644 --- a/packages/core/ui/builder/component-builder/index.ts +++ b/packages/core/ui/builder/component-builder/index.ts @@ -95,8 +95,7 @@ const createComponentInstance = profile('createComponentInstance', (elementName: // Create instance of the component. instance = new instanceType(); } catch (ex) { - const debug: typeof debugModule = require('../../../utils/debug'); - throw new debug.ScopeError(ex, "Module '" + (resolvedModuleName || elementName) + "' not found for element '" + (namespace ? namespace + ':' : '') + elementName + "'."); + throw new debugModule.ScopeError(ex, "Module '" + (resolvedModuleName || elementName) + "' not found for element '" + (namespace ? namespace + ':' : '') + elementName + "'."); } return { instance, instanceModule }; @@ -211,7 +210,7 @@ export function setPropertyValue(instance: View, instanceModule: Object, exports expression: bindOptions[bindingConstants.expression], twoWay: bindOptions[bindingConstants.twoWay], }, - bindOptions[bindingConstants.source] + bindOptions[bindingConstants.source], ); } else if (isEventOrGesture(propertyName, instance)) { // Get the event handler from page module exports. diff --git a/packages/core/ui/builder/index.ts b/packages/core/ui/builder/index.ts index 0750c254b..9b62c0721 100644 --- a/packages/core/ui/builder/index.ts +++ b/packages/core/ui/builder/index.ts @@ -1,18 +1,41 @@ -// Definitions. -import { View, Template, KeyedTemplate } from '../core/view'; -import { ViewBase } from '../core/view-base'; -import { ViewEntry } from '../frame'; -import { Page } from '../page'; - -// Types. +import type { View, Template, KeyedTemplate } from '../core/view'; +import type { ViewBase } from '../core/view-base'; +import type { ViewEntry } from '../frame/frame-interfaces'; +import type { Page } from '../page'; import { debug } from '../../utils/debug'; import { isDefined } from '../../utils/types'; import { sanitizeModuleName } from '../../utils/common'; import { setPropertyValue, getComponentModule } from './component-builder'; import type { ComponentModule } from './component-builder'; -import { platformNames } from '../../platform'; +import { platformNames } from '../../platform/common'; import { resolveModuleName } from '../../module-name-resolver'; -import { xml2ui } from './xml2ui'; +import { ScopeError, SourceError, Source } from '../../utils/debug-source'; +import * as xml from '../../xml'; +import { isString, isObject } from '../../utils/types'; +import { Device } from '../../platform/device'; +import { profile } from '../../profiling'; + +// Note: after all circulars are resolve, try importing this from single place or see if globals/index.ts properly handles it +if (typeof global.__metadata === 'undefined') { + /** + * TS decorator metadata helper. + * @param metadataKey the metadata key (e.g. "design:type") + * @param metadataValue the metadata value (e.g. the constructor function) + * @returns a decorator function, or undefined if Reflect.metadata isn’t available + */ + global.__metadata = (metadataKey, metadataValue) => { + if ( + typeof Reflect === 'object' && + // @ts-expect-error + typeof Reflect.metadata === 'function' + ) { + // Delegate to the reflect-metadata shim + // @ts-expect-error + return Reflect.metadata(metadataKey, metadataValue); + } + // no-op if no Reflect.metadata + }; +} export const ios = platformNames.ios.toLowerCase(); export const android = platformNames.android.toLowerCase(); @@ -256,3 +279,524 @@ function parseInternal(value: string, context: any, xmlModule?: string, moduleNa return null; } } + +export namespace xml2ui { + /** + * Pipes and filters: + * https://en.wikipedia.org/wiki/Pipeline_(software) + */ + interface XmlProducer { + pipe(next: Next): Next; + } + + interface XmlConsumer { + parse(args: xml.ParserEvent); + } + + interface ParseInputData extends String { + default?: string; + } + + export class XmlProducerBase implements XmlProducer { + private _next: XmlConsumer; + public pipe(next: Next) { + this._next = next; + + return next; + } + protected next(args: xml.ParserEvent) { + this._next.parse(args); + } + } + + export class XmlStringParser extends XmlProducerBase implements XmlProducer { + private error: ErrorFormatter; + + constructor(error?: ErrorFormatter) { + super(); + this.error = error || PositionErrorFormat; + } + + public parse(value: ParseInputData) { + if (__UI_USE_XML_PARSER__) { + const xmlParser = new xml.XmlParser( + (args: xml.ParserEvent) => { + try { + this.next(args); + } catch (e) { + throw this.error(e, args.position); + } + }, + (e, p) => { + throw this.error(e, p); + }, + true, + ); + + if (isString(value)) { + xmlParser.parse(value); + } else if (isObject(value) && isString(value.default)) { + xmlParser.parse(value.default); + } + } + } + } + + interface ErrorFormatter { + (e: Error, p: xml.Position): Error; + } + + export function PositionErrorFormat(e: Error, p: xml.Position): Error { + return new ScopeError(e, 'Parsing XML at ' + p.line + ':' + p.column); + } + + export function SourceErrorFormat(uri): ErrorFormatter { + return (e: Error, p: xml.Position) => { + console.error(uri); + const source = p ? new Source(uri, p.line, p.column) : new Source(uri, -1, -1); + e = new SourceError(e, source, 'Building UI from XML.'); + + return e; + }; + } + + interface SourceTracker { + (component: any, p: xml.Position): void; + } + + export function ComponentSourceTracker(uri): SourceTracker { + return (component: any, p: xml.Position) => { + if (!Source.get(component)) { + const source = p ? new Source(uri, p.line, p.column) : new Source(uri, -1, -1); + Source.set(component, source); + } + }; + } + + export class PlatformFilter extends XmlProducerBase implements XmlProducer, XmlConsumer { + private currentPlatformContext: string; + + public parse(args: xml.ParserEvent) { + if (args.eventType === xml.ParserEventType.StartElement) { + if (PlatformFilter.isPlatform(args.elementName)) { + if (this.currentPlatformContext) { + throw new Error("Already in '" + this.currentPlatformContext + "' platform context and cannot switch to '" + args.elementName + "' platform! Platform tags cannot be nested."); + } + + this.currentPlatformContext = args.elementName; + + return; + } + } + + if (args.eventType === xml.ParserEventType.EndElement) { + if (PlatformFilter.isPlatform(args.elementName)) { + this.currentPlatformContext = undefined; + + return; + } + } + + if (this.currentPlatformContext && !PlatformFilter.isCurentPlatform(this.currentPlatformContext)) { + return; + } + + this.next(args); + } + + private static isPlatform(value: string): boolean { + if (value) { + const toLower = value.toLowerCase(); + + return toLower === android || toLower === ios || toLower === visionos || toLower === apple; + } + + return false; + } + + private static isCurentPlatform(value: string): boolean { + value = value && value.toLowerCase(); + return value === apple ? __APPLE__ : value === Device.os.toLowerCase(); + } + } + + export class XmlArgsReplay extends XmlProducerBase implements XmlProducer { + private error: ErrorFormatter; + private args: xml.ParserEvent[]; + + constructor(args: xml.ParserEvent[], errorFormat: ErrorFormatter) { + super(); + this.args = args; + this.error = errorFormat; + } + + public replay() { + this.args.forEach((args: xml.ParserEvent) => { + try { + this.next(args); + } catch (e) { + throw this.error(e, args.position); + } + }); + } + } + + interface TemplateProperty { + context?: any; + parent: ComponentModule; + name: string; + elementName: string; + templateItems: Array; + errorFormat: ErrorFormatter; + sourceTracker: SourceTracker; + } + + /** + * It is a state pattern + * https://en.wikipedia.org/wiki/State_pattern + */ + export class XmlStateParser implements XmlConsumer { + private state: XmlStateConsumer; + + constructor(state: XmlStateConsumer) { + this.state = state; + } + + parse(args: xml.ParserEvent) { + this.state = this.state.parse(args); + } + } + + interface XmlStateConsumer extends XmlConsumer { + parse(args: xml.ParserEvent): XmlStateConsumer; + } + + export class TemplateParser implements XmlStateConsumer { + private _context: any; + private _recordedXmlStream: Array; + private _templateProperty: TemplateProperty; + private _nestingLevel: number; + private _state: TemplateParser.State; + + private parent: XmlStateConsumer; + private _setTemplateProperty: boolean; + + constructor(parent: XmlStateConsumer, templateProperty: TemplateProperty, setTemplateProperty = true) { + this.parent = parent; + + this._context = templateProperty.context; + this._recordedXmlStream = new Array(); + this._templateProperty = templateProperty; + this._nestingLevel = 0; + this._state = TemplateParser.State.EXPECTING_START; + this._setTemplateProperty = setTemplateProperty; + } + + public parse(args: xml.ParserEvent): XmlStateConsumer { + if (args.eventType === xml.ParserEventType.StartElement) { + this.parseStartElement(args.prefix, args.namespace, args.elementName, args.attributes); + } else if (args.eventType === xml.ParserEventType.EndElement) { + this.parseEndElement(args.prefix, args.elementName); + } + + this._recordedXmlStream.push(args); + + return this._state === TemplateParser.State.FINISHED ? this.parent : this; + } + + public get elementName(): string { + return this._templateProperty.elementName; + } + + private parseStartElement(prefix: string, namespace: string, elementName: string, attributes: Object) { + if (this._state === TemplateParser.State.EXPECTING_START) { + this._state = TemplateParser.State.PARSING; + } else if (this._state === TemplateParser.State.FINISHED) { + throw new Error('Template must have exactly one root element but multiple elements were found.'); + } + + this._nestingLevel++; + } + + private parseEndElement(prefix: string, elementName: string) { + if (this._state === TemplateParser.State.EXPECTING_START) { + throw new Error('Template must have exactly one root element but none was found.'); + } else if (this._state === TemplateParser.State.FINISHED) { + throw new Error('No more closing elements expected for this template.'); + } + + this._nestingLevel--; + + if (this._nestingLevel === 0) { + this._state = TemplateParser.State.FINISHED; + + if (this._setTemplateProperty && this._templateProperty.name in this._templateProperty.parent.component) { + const template = this.buildTemplate(); + this._templateProperty.parent.component[this._templateProperty.name] = template; + } + } + } + + public buildTemplate(): Template { + if (__UI_USE_XML_PARSER__) { + const context = this._context; + const errorFormat = this._templateProperty.errorFormat; + const sourceTracker = this._templateProperty.sourceTracker; + const template: Template =