refactor: circular deps part 15

This commit is contained in:
Nathan Walker
2025-07-11 16:18:14 -07:00
parent e7ab426ee2
commit 35fef40e5b
18 changed files with 697 additions and 690 deletions

View File

@ -425,11 +425,11 @@ export var testFileNameExtension = function () {
var file = documents.getFile('Test.txt');
// Getting the file name "Test.txt".
var fileName = file.name;
// Getting the file extension ".txt".
// Getting the file extension "txt".
var fileExtension = file.extension;
// >> (hide)
TKUnit.assert(fileName === 'Test.txt', 'Wrong file name.');
TKUnit.assert(fileExtension === '.txt', 'Wrong extension.');
TKUnit.assert(fileExtension === 'txt', 'Wrong extension.');
file.remove();
// << (hide)
// << file-system-extension
@ -633,7 +633,7 @@ export function test_FSEntity_Properties() {
var documents = fs.knownFolders.documents();
var file = documents.getFile('Test_File.txt');
TKUnit.assert(file.extension === '.txt', 'FileEntity.extension not working.');
TKUnit.assert(file.extension === 'txt', 'FileEntity.extension not working.');
TKUnit.assert(file.isLocked === false, 'FileEntity.isLocked not working.');
TKUnit.assert(file.lastModified instanceof Date, 'FileEntity.lastModified not working.');
TKUnit.assert(file.size === 0, 'FileEntity.size not working.');

View File

@ -1,5 +1,6 @@
import * as textModule from '../text';
import { getFileExtension, android as androidUtils } from '../utils';
import { android as androidUtils } from '../utils';
import { getFileExtension } from '../utils/utils-shared';
import { SDK_VERSION } from '../utils/constants';
import type { IFileSystemAccess } from './file-system-access';

View File

@ -1,5 +1,6 @@
import { encoding as textEncoding } from '../text';
import { ios as iosUtils } from '../utils';
import { getFileExtension } from '../utils/utils-shared';
// TODO: Implement all the APIs receiving callback using async blocks
// TODO: Check whether we need try/catch blocks for the iOS implementation
@ -611,24 +612,8 @@ export class FileSystemAccess {
return url.path;
}
// TODO: This method is the same as in the iOS implementation.
// Make it in a separate file / module so it can be reused from both implementations.
public getFileExtension(path: string): string {
// TODO [For Panata]: The definitions currently specify "any" as a return value of this method
//const nsString = Foundation.NSString.stringWithString(path);
//const extension = nsString.pathExtension();
//if (extension && extension.length > 0) {
// extension = extension.concat(".", extension);
//}
//return extension;
const dotIndex = path.lastIndexOf('.');
if (dotIndex && dotIndex >= 0 && dotIndex < path.length) {
return path.substring(dotIndex);
}
return '';
return getFileExtension(path);
}
private deleteEntity(path: string, onError?: (error: any) => any) {

View File

@ -1,7 +1,7 @@
// imported for definition purposes only
import * as httpModule from '../../http';
import type { Headers, HttpResponse, HttpRequestOptions } from '../../http';
import { ImageSource } from '../../image-source';
import { Screen } from '../../platform';
import { Screen } from '../../platform/screen';
import { File } from '../../file-system';
import { HttpResponseEncoding } from '../http-interfaces';
import { getFilenameFromUrl } from './http-request-common';
@ -51,7 +51,7 @@ function onRequestComplete(requestId: number, result: org.nativescript.widgets.A
}
// read the headers
const headers: httpModule.Headers = {};
const headers: Headers = {};
if (result.headers) {
const jHeaders = result.headers;
const length = jHeaders.size();
@ -177,7 +177,7 @@ function onRequestError(error: string, requestId: number) {
}
}
function buildJavaOptions(options: httpModule.HttpRequestOptions) {
function buildJavaOptions(options: HttpRequestOptions) {
if (typeof options.url !== 'string') {
throw new Error('Http request must provide a valid url.');
}
@ -224,13 +224,13 @@ function buildJavaOptions(options: httpModule.HttpRequestOptions) {
return javaOptions;
}
export function request(options: httpModule.HttpRequestOptions): Promise<httpModule.HttpResponse> {
export function request(options: HttpRequestOptions): Promise<HttpResponse> {
if (options === undefined || options === null) {
// TODO: Shouldn't we throw an error here - defensive programming
return;
}
return new Promise<httpModule.HttpResponse>((resolve, reject) => {
return new Promise<HttpResponse>((resolve, reject) => {
try {
// initialize the options
const javaOptions = buildJavaOptions(options);
@ -289,7 +289,7 @@ function decodeResponse(raw: any, encoding?: HttpResponseEncoding) {
return raw.toString(charsetName);
}
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])) {

View File

@ -1,5 +1,5 @@
import { SDK_VERSION } from '../../utils/constants';
import { isRealDevice } from '../../utils';
import { isRealDevice } from '../../utils/native-helper';
import * as types from '../../utils/types';
import * as domainDebugger from '../../debugger';
import { getFilenameFromUrl } from './http-request-common';

View File

@ -63,7 +63,7 @@
// module.exports.EasySAXParser = EasySAXParser;
// };
export function EasySAXParser() {
function EasySAXParser() {
'use strict';
if (!this) return null;
@ -780,3 +780,5 @@ EasySAXParser.prototype.parse = function(xml) {
j += 1;
};
};
export { EasySAXParser };

View File

@ -1,5 +1,5 @@
import { EventData, Observable } from '../data/observable';
import { Screen } from '../platform';
import { Screen } from '../platform/screen';
import { getApplicationProperties, toggleApplicationEventListeners } from '../application/helpers-common';
import type { ApplicationEventData } from '../application/application-interfaces';
import { matchQuery, MediaQueryType } from '../css-mediaquery';

View File

@ -1,5 +1,5 @@
import { isEmbedded, getEmbeddedView } from '../embedding';
import { setFragmentCallbacks } from '.';
import { setFragmentCallbacks } from './frame-helper-for-android';
declare const com: any;

View File

@ -0,0 +1,342 @@
import { Trace } from '../../trace';
import { _clearEntry, _clearFragment, _getAnimatedEntries, _reverseTransitions, _setAndroidFragmentTransitions, _updateTransitions } from './fragment.transitions';
import type { BackstackEntry } from '.';
import { profile } from '../../profiling';
import { getNativeApp } from '../../application/helpers-common';
import { Color } from '../../color';
import type { Page } from '../page';
import type { AndroidFrame as Frame } from '.';
export const FRAMEID = '_frameId';
export const CALLBACKS = '_callbacks';
export const framesCache = new Array<WeakRef<any>>();
export interface AndroidFragmentCallbacks {
onHiddenChanged(fragment: any, hidden: boolean, superFunc: Function): void;
onCreateAnimator(fragment: any, transit: number, enter: boolean, nextAnim: number, superFunc: Function): any;
onCreate(fragment: any, savedInstanceState: any, superFunc: Function): void;
onCreateView(fragment: any, inflater: any, container: any, savedInstanceState: any, superFunc: Function): any;
onSaveInstanceState(fragment: any, outState: any, superFunc: Function): void;
onDestroyView(fragment: any, superFunc: Function): void;
onDestroy(fragment: any, superFunc: Function): void;
onPause(fragment: any, superFunc: Function): void;
onResume(fragment: any, superFunc: Function): void;
onStop(fragment: any, superFunc: Function): void;
toStringOverride(fragment: any, superFunc: Function): string;
}
function findPageForFragment(fragment: androidx.fragment.app.Fragment, frame: Frame) {
const fragmentTag = fragment.getTag();
if (Trace.isEnabled()) {
Trace.write(`Finding page for ${fragmentTag}.`, Trace.categories.NativeLifecycle);
}
let entry: BackstackEntry;
const current = frame._currentEntry;
const executingContext = frame._executingContext;
if (current && current.fragmentTag === fragmentTag) {
entry = current;
} else if (executingContext && executingContext.entry && executingContext.entry.fragmentTag === fragmentTag) {
entry = executingContext.entry;
}
let page: Page;
if (entry) {
entry.recreated = true;
page = entry.resolvedPage;
}
if (page) {
const callbacks: FragmentCallbacksImplementation = fragment[CALLBACKS];
callbacks.frame = frame;
callbacks.entry = entry;
entry.fragment = fragment;
_updateTransitions(entry);
} else {
throw new Error(`Could not find a page for ${fragmentTag}.`);
}
}
export class FragmentCallbacksImplementation implements AndroidFragmentCallbacks {
public frame: Frame;
public entry: BackstackEntry;
private backgroundBitmap: android.graphics.Bitmap = null;
@profile
public onHiddenChanged(fragment: androidx.fragment.app.Fragment, hidden: boolean, superFunc: Function): void {
if (Trace.isEnabled()) {
Trace.write(`${fragment}.onHiddenChanged(${hidden})`, Trace.categories.NativeLifecycle);
}
superFunc.call(fragment, hidden);
}
@profile
public onCreateAnimator(fragment: androidx.fragment.app.Fragment, transit: number, enter: boolean, nextAnim: number, superFunc: Function): android.animation.Animator {
let animator = null;
const entry = <any>this.entry;
// Return enterAnimator only when new (no current entry) nested transition.
if (enter && entry.isNestedDefaultTransition) {
animator = entry.enterAnimator;
entry.isNestedDefaultTransition = false;
}
return animator || superFunc.call(fragment, transit, enter, nextAnim);
}
@profile
public onCreate(fragment: androidx.fragment.app.Fragment, savedInstanceState: android.os.Bundle, superFunc: Function): void {
if (Trace.isEnabled()) {
Trace.write(`${fragment}.onCreate(${savedInstanceState})`, Trace.categories.NativeLifecycle);
}
superFunc.call(fragment, savedInstanceState);
// There is no entry set to the fragment, so this must be destroyed fragment that was recreated by Android.
// We should find its corresponding page in our backstack and set it manually.
if (!this.entry) {
const args = fragment.getArguments();
const frameId = args.getInt(FRAMEID);
const frame = getFrameByNumberId(frameId);
if (!frame) {
throw new Error(`Cannot find Frame for ${fragment}`);
}
findPageForFragment(fragment, frame);
}
}
@profile
public onCreateView(fragment: androidx.fragment.app.Fragment, inflater: android.view.LayoutInflater, container: android.view.ViewGroup, savedInstanceState: android.os.Bundle, superFunc: Function): android.view.View {
if (Trace.isEnabled()) {
Trace.write(`${fragment}.onCreateView(inflater, container, ${savedInstanceState})`, Trace.categories.NativeLifecycle);
}
const entry = this.entry;
if (!entry) {
Trace.error(`${fragment}.onCreateView: entry is null or undefined`);
return null;
}
const page = entry.resolvedPage;
if (!page) {
Trace.error(`${fragment}.onCreateView: entry has no resolvedPage`);
return null;
}
const frame = this.frame;
if (!frame) {
Trace.error(`${fragment}.onCreateView: this.frame is null or undefined`);
return null;
}
frame._resolvedPage = page;
// @ts-ignore
if (page.parent === frame) {
(frame as Frame)._inheritStyles(page);
// If we are navigating to a page that was destroyed
// reinitialize its UI.
if (!page._context) {
const context = (container && container.getContext()) || (inflater && inflater.getContext());
page._setupUI(context);
}
if ((frame as Frame).isLoaded && !page.isLoaded) {
page.callLoaded();
}
} else {
if (!page.parent) {
if (!frame._styleScope) {
// Make sure page will have styleScope even if parents don't.
page._updateStyleScope();
}
frame._addView(page);
} else {
throw new Error('Page is already shown on another frame.');
}
}
const savedState = entry.viewSavedState;
if (savedState) {
(<android.view.View>page.nativeViewProtected).restoreHierarchyState(savedState);
entry.viewSavedState = null;
}
// fixes 'java.lang.IllegalStateException: The specified child already has a parent. You must call removeView() on the child's parent first'.
// on app resume in nested frame scenarios with support library version greater than 26.0.0
// HACK: this whole code block shouldn't be necessary as the native view is supposedly removed from its parent
// right after onDestroyView(...) is called but for some reason the fragment view (page) still thinks it has a
// parent while its supposed parent believes it properly removed its children; in order to "force" the child to
// lose its parent we temporarily add it to the parent, and then remove it (addViewInLayout doesn't trigger layout pass)
const nativeView = page.nativeViewProtected;
if (nativeView != null) {
const parentView = nativeView.getParent();
if (parentView instanceof android.view.ViewGroup) {
if (parentView.getChildCount() === 0) {
parentView.addViewInLayout(nativeView, -1, new org.nativescript.widgets.CommonLayoutParams());
}
parentView.removeAllViews();
}
}
return page.nativeViewProtected;
}
@profile
public onSaveInstanceState(fragment: androidx.fragment.app.Fragment, outState: android.os.Bundle, superFunc: Function): void {
if (Trace.isEnabled()) {
Trace.write(`${fragment}.onSaveInstanceState(${outState})`, Trace.categories.NativeLifecycle);
}
superFunc.call(fragment, outState);
}
@profile
public onDestroyView(fragment: org.nativescript.widgets.FragmentBase, superFunc: Function): void {
try {
if (Trace.isEnabled()) {
Trace.write(`${fragment}.onDestroyView()`, Trace.categories.NativeLifecycle);
}
const hasRemovingParent = fragment.getRemovingParentFragment();
if (hasRemovingParent) {
const nativeFrameView = this.frame.nativeViewProtected;
if (nativeFrameView) {
const bitmapDrawable = new android.graphics.drawable.BitmapDrawable((getNativeApp() as android.app.Application).getApplicationContext().getResources(), this.backgroundBitmap);
this.frame._originalBackground = this.frame.backgroundColor || new Color('White');
nativeFrameView.setBackgroundDrawable(bitmapDrawable);
this.backgroundBitmap = null;
}
}
} finally {
superFunc.call(fragment);
}
}
@profile
public onDestroy(fragment: androidx.fragment.app.Fragment, superFunc: Function): void {
if (Trace.isEnabled()) {
Trace.write(`${fragment}.onDestroy()`, Trace.categories.NativeLifecycle);
}
superFunc.call(fragment);
const entry = this.entry;
if (!entry) {
Trace.error(`${fragment}.onDestroy: entry is null or undefined`);
return null;
}
// [nested frames / fragments] see https://github.com/NativeScript/NativeScript/issues/6629
// retaining reference to a destroyed fragment here somehow causes a cryptic
// "IllegalStateException: Failure saving state: active fragment has cleared index: -1"
// in a specific mixed parent / nested frame navigation scenario
entry.fragment = null;
const page = entry.resolvedPage;
if (!page) {
// todo: check why this happens when using shared element transition!!!
// commented out the Trace.error to prevent a crash (the app will still work interestingly)
console.log(`${fragment}.onDestroy: entry has no resolvedPage`);
// Trace.error(`${fragment}.onDestroy: entry has no resolvedPage`);
return null;
}
}
@profile
public onPause(fragment: org.nativescript.widgets.FragmentBase, superFunc: Function): void {
try {
// Get view as bitmap and set it as background. This is workaround for the disapearing nested fragments.
// TODO: Consider removing it when update to androidx.fragment:1.2.0
const hasRemovingParent = fragment.getRemovingParentFragment();
if (hasRemovingParent) {
this.backgroundBitmap = this.loadBitmapFromView(this.frame.nativeViewProtected);
}
} finally {
superFunc.call(fragment);
}
}
@profile
public onResume(fragment: org.nativescript.widgets.FragmentBase, superFunc: Function): void {
const frame = this.entry.resolvedPage.frame;
// on some cases during the first navigation on nested frames the animation doesn't trigger
// we depend on the animation (even None animation) to set the entry as the current entry
// animation should start between start and resume, so if we have an executing navigation here it probably means the animation was skipped
// so we manually set the entry
// also, to be compatible with fragments 1.2.x we need this setTimeout as animations haven't run on onResume yet
const weakRef = new WeakRef(this);
setTimeout(() => {
const owner = weakRef.get();
if (!owner) {
return;
}
if (frame._executingContext && !(<any>owner.entry).isAnimationRunning) {
frame.setCurrent(owner.entry, frame._executingContext.navigationType);
}
}, 0);
superFunc.call(fragment);
}
@profile
public onStop(fragment: androidx.fragment.app.Fragment, superFunc: Function): void {
superFunc.call(fragment);
}
@profile
public toStringOverride(fragment: androidx.fragment.app.Fragment, superFunc: Function): string {
const entry = this.entry;
if (entry) {
return `${entry.fragmentTag}<${entry.resolvedPage}>`;
} else {
return 'NO ENTRY, ' + superFunc.call(fragment);
}
}
private loadBitmapFromView(view: android.view.View): android.graphics.Bitmap {
// Don't try to create bitmaps with no dimensions as this causes a crash
// This might happen when showing and closing dialogs fast.
if (!(view && view.getWidth() > 0 && view.getHeight() > 0)) {
return undefined;
}
// Another way to get view bitmap. Test performance vs setDrawingCacheEnabled
// const width = view.getWidth();
// const height = view.getHeight();
// const bitmap = android.graphics.Bitmap.createBitmap(width, height, android.graphics.Bitmap.Config.ARGB_8888);
// const canvas = new android.graphics.Canvas(bitmap);
// view.layout(0, 0, width, height);
// view.draw(canvas);
// view.setDrawingCacheEnabled(true);
// const drawCache = view.getDrawingCache();
// const bitmap = android.graphics.Bitmap.createBitmap(drawCache);
// view.setDrawingCacheEnabled(false);
return org.nativescript.widgets.Utils.getBitmapFromView(view);
}
}
export function getFrameByNumberId(frameId: number): Frame {
// Find the frame for this activity.
for (let i = 0; i < framesCache.length; i++) {
const aliveFrame = framesCache[i].get();
if (aliveFrame && aliveFrame.frameId === frameId) {
return aliveFrame.owner;
}
}
return null;
}
export function setFragmentCallbacks(fragment: androidx.fragment.app.Fragment): void {
fragment[CALLBACKS] = new FragmentCallbacksImplementation();
}

View File

@ -11,18 +11,16 @@ import { android as androidUtils } from '../../utils/native-helper';
import type { ExpandedEntry } from './fragment.transitions.android';
import { ensureFragmentClass, fragmentClass } from './fragment';
import { getAppMainEntry } from '../../application/helpers-common';
import { getNativeApp } from '../../application/helpers-common';
import { Color } from '../../color';
import { AndroidActivityBackPressedEventData, AndroidActivityNewIntentEventData, AndroidActivityRequestPermissionsEventData, AndroidActivityResultEventData } from '../../application/application-interfaces';
import { Application } from '../../application/application';
import { isEmbedded, setEmbeddedView } from '../embedding';
import { CALLBACKS, FRAMEID, framesCache, setFragmentCallbacks } from './frame-helper-for-android';
export * from './frame-common';
export { setFragmentClass } from './fragment';
const INTENT_EXTRA = 'com.tns.activity';
const FRAMEID = '_frameId';
const CALLBACKS = '_callbacks';
const ownerSymbol = Symbol('_owner');
@ -648,7 +646,6 @@ function restoreTransitionState(entry: BackstackEntry, snapshot: TransitionState
}
let framesCounter = 0;
const framesCache = new Array<WeakRef<AndroidFrame>>();
class AndroidFrame extends Observable implements AndroidFrameDefinition {
public rootViewGroup: android.view.ViewGroup;
@ -760,18 +757,6 @@ function startActivity(activity: androidx.appcompat.app.AppCompatActivity, frame
activity.startActivity(intent);
}
export function getFrameByNumberId(frameId: number): Frame {
// Find the frame for this activity.
for (let i = 0; i < framesCache.length; i++) {
const aliveFrame = framesCache[i].get();
if (aliveFrame && aliveFrame.frameId === frameId) {
return aliveFrame.owner;
}
}
return null;
}
const activityRootViewsMap = new Map<number, WeakRef<View>>();
const ROOT_VIEW_ID_EXTRA = 'com.tns.activity.rootViewId';
@ -1063,310 +1048,6 @@ export class ActivityCallbacksImplementation implements AndroidActivityCallbacks
}
}
export class FragmentCallbacksImplementation implements AndroidFragmentCallbacks {
public frame: Frame;
public entry: BackstackEntry;
private backgroundBitmap: android.graphics.Bitmap = null;
@profile
public onHiddenChanged(fragment: androidx.fragment.app.Fragment, hidden: boolean, superFunc: Function): void {
if (Trace.isEnabled()) {
Trace.write(`${fragment}.onHiddenChanged(${hidden})`, Trace.categories.NativeLifecycle);
}
superFunc.call(fragment, hidden);
}
@profile
public onCreateAnimator(fragment: androidx.fragment.app.Fragment, transit: number, enter: boolean, nextAnim: number, superFunc: Function): android.animation.Animator {
let animator = null;
const entry = <any>this.entry;
// Return enterAnimator only when new (no current entry) nested transition.
if (enter && entry.isNestedDefaultTransition) {
animator = entry.enterAnimator;
entry.isNestedDefaultTransition = false;
}
return animator || superFunc.call(fragment, transit, enter, nextAnim);
}
@profile
public onCreate(fragment: androidx.fragment.app.Fragment, savedInstanceState: android.os.Bundle, superFunc: Function): void {
if (Trace.isEnabled()) {
Trace.write(`${fragment}.onCreate(${savedInstanceState})`, Trace.categories.NativeLifecycle);
}
superFunc.call(fragment, savedInstanceState);
// There is no entry set to the fragment, so this must be destroyed fragment that was recreated by Android.
// We should find its corresponding page in our backstack and set it manually.
if (!this.entry) {
const args = fragment.getArguments();
const frameId = args.getInt(FRAMEID);
const frame = getFrameByNumberId(frameId);
if (!frame) {
throw new Error(`Cannot find Frame for ${fragment}`);
}
findPageForFragment(fragment, frame);
}
}
@profile
public onCreateView(fragment: androidx.fragment.app.Fragment, inflater: android.view.LayoutInflater, container: android.view.ViewGroup, savedInstanceState: android.os.Bundle, superFunc: Function): android.view.View {
if (Trace.isEnabled()) {
Trace.write(`${fragment}.onCreateView(inflater, container, ${savedInstanceState})`, Trace.categories.NativeLifecycle);
}
const entry = this.entry;
if (!entry) {
Trace.error(`${fragment}.onCreateView: entry is null or undefined`);
return null;
}
const page = entry.resolvedPage;
if (!page) {
Trace.error(`${fragment}.onCreateView: entry has no resolvedPage`);
return null;
}
const frame = this.frame;
if (!frame) {
Trace.error(`${fragment}.onCreateView: this.frame is null or undefined`);
return null;
}
frame._resolvedPage = page;
if (page.parent === frame) {
frame._inheritStyles(page);
// If we are navigating to a page that was destroyed
// reinitialize its UI.
if (!page._context) {
const context = (container && container.getContext()) || (inflater && inflater.getContext());
page._setupUI(context);
}
if (frame.isLoaded && !page.isLoaded) {
page.callLoaded();
}
} else {
if (!page.parent) {
if (!frame._styleScope) {
// Make sure page will have styleScope even if parents don't.
page._updateStyleScope();
}
frame._addView(page);
} else {
throw new Error('Page is already shown on another frame.');
}
}
const savedState = entry.viewSavedState;
if (savedState) {
(<android.view.View>page.nativeViewProtected).restoreHierarchyState(savedState);
entry.viewSavedState = null;
}
// fixes 'java.lang.IllegalStateException: The specified child already has a parent. You must call removeView() on the child's parent first'.
// on app resume in nested frame scenarios with support library version greater than 26.0.0
// HACK: this whole code block shouldn't be necessary as the native view is supposedly removed from its parent
// right after onDestroyView(...) is called but for some reason the fragment view (page) still thinks it has a
// parent while its supposed parent believes it properly removed its children; in order to "force" the child to
// lose its parent we temporarily add it to the parent, and then remove it (addViewInLayout doesn't trigger layout pass)
const nativeView = page.nativeViewProtected;
if (nativeView != null) {
const parentView = nativeView.getParent();
if (parentView instanceof android.view.ViewGroup) {
if (parentView.getChildCount() === 0) {
parentView.addViewInLayout(nativeView, -1, new org.nativescript.widgets.CommonLayoutParams());
}
parentView.removeAllViews();
}
}
return page.nativeViewProtected;
}
@profile
public onSaveInstanceState(fragment: androidx.fragment.app.Fragment, outState: android.os.Bundle, superFunc: Function): void {
if (Trace.isEnabled()) {
Trace.write(`${fragment}.onSaveInstanceState(${outState})`, Trace.categories.NativeLifecycle);
}
superFunc.call(fragment, outState);
}
@profile
public onDestroyView(fragment: org.nativescript.widgets.FragmentBase, superFunc: Function): void {
try {
if (Trace.isEnabled()) {
Trace.write(`${fragment}.onDestroyView()`, Trace.categories.NativeLifecycle);
}
const hasRemovingParent = fragment.getRemovingParentFragment();
if (hasRemovingParent) {
const nativeFrameView = this.frame.nativeViewProtected;
if (nativeFrameView) {
const bitmapDrawable = new android.graphics.drawable.BitmapDrawable((getNativeApp() as android.app.Application).getApplicationContext().getResources(), this.backgroundBitmap);
this.frame._originalBackground = this.frame.backgroundColor || new Color('White');
nativeFrameView.setBackgroundDrawable(bitmapDrawable);
this.backgroundBitmap = null;
}
}
} finally {
superFunc.call(fragment);
}
}
@profile
public onDestroy(fragment: androidx.fragment.app.Fragment, superFunc: Function): void {
if (Trace.isEnabled()) {
Trace.write(`${fragment}.onDestroy()`, Trace.categories.NativeLifecycle);
}
superFunc.call(fragment);
const entry = this.entry;
if (!entry) {
Trace.error(`${fragment}.onDestroy: entry is null or undefined`);
return null;
}
// [nested frames / fragments] see https://github.com/NativeScript/NativeScript/issues/6629
// retaining reference to a destroyed fragment here somehow causes a cryptic
// "IllegalStateException: Failure saving state: active fragment has cleared index: -1"
// in a specific mixed parent / nested frame navigation scenario
entry.fragment = null;
const page = entry.resolvedPage;
if (!page) {
// todo: check why this happens when using shared element transition!!!
// commented out the Trace.error to prevent a crash (the app will still work interestingly)
console.log(`${fragment}.onDestroy: entry has no resolvedPage`);
// Trace.error(`${fragment}.onDestroy: entry has no resolvedPage`);
return null;
}
}
@profile
public onPause(fragment: org.nativescript.widgets.FragmentBase, superFunc: Function): void {
try {
// Get view as bitmap and set it as background. This is workaround for the disapearing nested fragments.
// TODO: Consider removing it when update to androidx.fragment:1.2.0
const hasRemovingParent = fragment.getRemovingParentFragment();
if (hasRemovingParent) {
this.backgroundBitmap = this.loadBitmapFromView(this.frame.nativeViewProtected);
}
} finally {
superFunc.call(fragment);
}
}
@profile
public onResume(fragment: org.nativescript.widgets.FragmentBase, superFunc: Function): void {
const frame = this.entry.resolvedPage.frame;
// on some cases during the first navigation on nested frames the animation doesn't trigger
// we depend on the animation (even None animation) to set the entry as the current entry
// animation should start between start and resume, so if we have an executing navigation here it probably means the animation was skipped
// so we manually set the entry
// also, to be compatible with fragments 1.2.x we need this setTimeout as animations haven't run on onResume yet
const weakRef = new WeakRef(this);
setTimeout(() => {
const owner = weakRef.get();
if (!owner) {
return;
}
if (frame._executingContext && !(<any>owner.entry).isAnimationRunning) {
frame.setCurrent(owner.entry, frame._executingContext.navigationType);
}
}, 0);
superFunc.call(fragment);
}
@profile
public onStop(fragment: androidx.fragment.app.Fragment, superFunc: Function): void {
superFunc.call(fragment);
}
@profile
public toStringOverride(fragment: androidx.fragment.app.Fragment, superFunc: Function): string {
const entry = this.entry;
if (entry) {
return `${entry.fragmentTag}<${entry.resolvedPage}>`;
} else {
return 'NO ENTRY, ' + superFunc.call(fragment);
}
}
private loadBitmapFromView(view: android.view.View): android.graphics.Bitmap {
// Don't try to create bitmaps with no dimensions as this causes a crash
// This might happen when showing and closing dialogs fast.
if (!(view && view.getWidth() > 0 && view.getHeight() > 0)) {
return undefined;
}
// Another way to get view bitmap. Test performance vs setDrawingCacheEnabled
// const width = view.getWidth();
// const height = view.getHeight();
// const bitmap = android.graphics.Bitmap.createBitmap(width, height, android.graphics.Bitmap.Config.ARGB_8888);
// const canvas = new android.graphics.Canvas(bitmap);
// view.layout(0, 0, width, height);
// view.draw(canvas);
// view.setDrawingCacheEnabled(true);
// const drawCache = view.getDrawingCache();
// const bitmap = android.graphics.Bitmap.createBitmap(drawCache);
// view.setDrawingCacheEnabled(false);
return org.nativescript.widgets.Utils.getBitmapFromView(view);
}
}
function findPageForFragment(fragment: androidx.fragment.app.Fragment, frame: Frame) {
const fragmentTag = fragment.getTag();
if (Trace.isEnabled()) {
Trace.write(`Finding page for ${fragmentTag}.`, Trace.categories.NativeLifecycle);
}
let entry: BackstackEntry;
const current = frame._currentEntry;
const executingContext = frame._executingContext;
if (current && current.fragmentTag === fragmentTag) {
entry = current;
} else if (executingContext && executingContext.entry && executingContext.entry.fragmentTag === fragmentTag) {
entry = executingContext.entry;
}
let page: Page;
if (entry) {
entry.recreated = true;
page = entry.resolvedPage;
}
if (page) {
const callbacks: FragmentCallbacksImplementation = fragment[CALLBACKS];
callbacks.frame = frame;
callbacks.entry = entry;
entry.fragment = fragment;
_updateTransitions(entry);
} else {
throw new Error(`Could not find a page for ${fragmentTag}.`);
}
}
export function setActivityCallbacks(activity: androidx.appcompat.app.AppCompatActivity): void {
activity[CALLBACKS] = new ActivityCallbacksImplementation();
}
export function setFragmentCallbacks(fragment: androidx.fragment.app.Fragment): void {
fragment[CALLBACKS] = new FragmentCallbacksImplementation();
}

View File

@ -1,8 +1,8 @@
import { NavigationType, FrameBase } from './frame-common';
import type { NavigationType, FrameBase } from './frame-common';
import type { NavigatedData, Page } from '../page';
import { Observable, EventData } from '../../data/observable';
import { Property, View } from '../core/view';
import { Transition } from '../transition';
import type { Observable, EventData } from '../../data/observable';
import type { Property, View } from '../core/view';
import type { Transition } from '../transition';
export * from './frame-interfaces';
@ -521,6 +521,19 @@ export interface AndroidFrame extends Observable {
* @param page The Page instance to search for.
*/
fragmentForPage(entry: BackstackEntry): any;
// common properties
_resolvedPage?: Page;
_currentEntry?: BackstackEntry;
_executingContext?: NavigationContext;
_inheritStyles?(page: Page): void;
isLoaded?: boolean;
_styleScope?: any;
_addView?(view: View): void;
nativeViewProtected?: any /* android.view.View */;
_originalBackground?: any /* android.graphics.drawable.Drawable */;
backgroundColor?: any;
owner?: any;
}
export interface AndroidActivityCallbacks {
@ -539,20 +552,6 @@ export interface AndroidActivityCallbacks {
onNewIntent(activity: any, intent: any, superSetIntentFunc: Function, superFunc: Function): void;
}
export interface AndroidFragmentCallbacks {
onHiddenChanged(fragment: any, hidden: boolean, superFunc: Function): void;
onCreateAnimator(fragment: any, transit: number, enter: boolean, nextAnim: number, superFunc: Function): any;
onCreate(fragment: any, savedInstanceState: any, superFunc: Function): void;
onCreateView(fragment: any, inflater: any, container: any, savedInstanceState: any, superFunc: Function): any;
onSaveInstanceState(fragment: any, outState: any, superFunc: Function): void;
onDestroyView(fragment: any, superFunc: Function): void;
onDestroy(fragment: any, superFunc: Function): void;
onPause(fragment: any, superFunc: Function): void;
onResume(fragment: any, superFunc: Function): void;
onStop(fragment: any, superFunc: Function): void;
toStringOverride(fragment: any, superFunc: Function): string;
}
/* tslint:disable */
/**
* Represents the iOS-specific Frame object, aggregated within the common Frame one.

View File

@ -2,7 +2,7 @@
// Only put platform-agnostic logic here.
import { CoreTypes } from '../../core-types';
import { layout } from '../../utils';
import { layout } from '../../utils/layout-helper';
import { isCssWideKeyword } from '../core/properties/property-shared';
function equalsCommon(a: CoreTypes.LengthType, b: CoreTypes.LengthType): boolean;

View File

@ -4,6 +4,7 @@ import emojiRegex from 'emoji-regex';
export * from './mainthread-helper';
export * from './macrotask-scheduler';
export * from './utils-shared';
export const RESOURCE_PREFIX = 'res://';
export const SYSTEM_PREFIX = 'sys://';
@ -164,5 +165,3 @@ export function isEmoji(value: string): boolean {
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions/Unicode_Property_Escapes
return emojiRegex().test(value);
}
export { getFileExtension } from './utils-shared';

View File

@ -1,5 +1,5 @@
import { Trace } from '../trace';
import { getFileExtension } from './common';
import { getFileExtension } from './utils-shared';
import { SDK_VERSION } from './constants';
import { android as AndroidUtils } from './native-helper';
import { topmost } from '../ui/frame/frame-stack';

View File

@ -0,0 +1,296 @@
import { numberHasDecimals, numberIs64Bit } from './types';
import { getNativeApp } from '../application/helpers-common';
import { androidGetCurrentActivity } from '../application/helpers';
import { Trace } from '../trace';
import { topmost } from '../ui/frame/frame-stack';
export function dataDeserialize(nativeData?: any) {
if (nativeData === null || typeof nativeData !== 'object') {
return nativeData;
}
let store;
switch (nativeData.getClass().getName()) {
case 'java.lang.String': {
return String(nativeData);
}
case 'java.lang.Boolean': {
return String(nativeData) === 'true';
}
case 'java.lang.Float':
case 'java.lang.Integer':
case 'java.lang.Long':
case 'java.lang.Double':
case 'java.lang.Short': {
return Number(nativeData);
}
case 'org.json.JSONArray': {
store = [];
for (let j = 0; j < nativeData.length(); j++) {
store[j] = dataDeserialize(nativeData.get(j));
}
break;
}
case 'org.json.JSONObject': {
store = {};
const i = nativeData.keys();
let key;
while (i.hasNext()) {
key = i.next();
store[key] = dataDeserialize(nativeData.get(key));
}
break;
}
case 'androidx.collection.SimpleArrayMap': {
const count = nativeData.size();
for (let l = 0; l < count; l++) {
const key = nativeData.keyAt(l);
store[key] = dataDeserialize(nativeData.get(key));
}
break;
}
case 'androidx.collection.ArrayMap':
case 'android.os.Bundle':
case 'java.util.HashMap':
case 'java.util.Map': {
store = {};
const keys = nativeData.keySet().toArray();
for (let k = 0; k < keys.length; k++) {
const key = keys[k];
store[key] = dataDeserialize(nativeData.get(key));
}
break;
}
default:
if (typeof nativeData === 'object' && nativeData instanceof java.util.List) {
const array = [];
const size = nativeData.size();
for (let i = 0, n = size; i < n; i++) {
array[i] = dataDeserialize(nativeData.get(i));
}
store = array;
} else {
store = null;
}
break;
}
return store;
}
export function dataSerialize(data?: any, wrapPrimitives?: boolean) {
let store;
switch (typeof data) {
case 'string':
case 'boolean': {
if (wrapPrimitives) {
if (typeof data === 'string') {
return new java.lang.String(data);
}
return new java.lang.Boolean(data);
}
return data;
}
case 'number': {
const hasDecimals = numberHasDecimals(data);
if (numberIs64Bit(data)) {
if (hasDecimals) {
return java.lang.Double.valueOf(data);
} else {
return java.lang.Long.valueOf(data);
}
} else {
if (hasDecimals) {
return java.lang.Float.valueOf(data);
} else {
return java.lang.Integer.valueOf(data);
}
}
}
case 'object': {
if (!data) {
return null;
}
if (data instanceof Date) {
return new java.util.Date(data.getTime());
}
if (Array.isArray(data)) {
store = new java.util.ArrayList();
data.forEach((item) => store.add(dataSerialize(item, wrapPrimitives)));
return store;
}
if (data.native) {
return data.native;
}
store = new java.util.HashMap();
Object.keys(data).forEach((key) => store.put(key, dataSerialize(data[key], wrapPrimitives)));
return store;
}
default:
return null;
}
}
export function getApplicationContext() {
return getApplication().getApplicationContext();
}
export function getCurrentActivity() {
return androidGetCurrentActivity();
}
export function getApplication() {
return getNativeApp() as android.app.Application;
}
export function getResources() {
return getApplication().getResources();
}
let packageName: string;
export function getPackageName() {
if (!packageName) {
packageName = getApplicationContext().getPackageName();
}
return packageName;
}
let inputMethodManager: android.view.inputmethod.InputMethodManager;
export function getInputMethodManager(): android.view.inputmethod.InputMethodManager {
if (!inputMethodManager) {
inputMethodManager = <android.view.inputmethod.InputMethodManager>getApplicationContext().getSystemService(android.content.Context.INPUT_METHOD_SERVICE);
}
return inputMethodManager;
}
export function getWindow() {
return getCurrentActivity()?.getWindow();
}
export function showSoftInput(nativeView: android.view.View): void {
const inputManager = getInputMethodManager();
if (inputManager && nativeView instanceof android.view.View) {
inputManager.showSoftInput(nativeView, android.view.inputmethod.InputMethodManager.SHOW_IMPLICIT);
}
}
export function dismissSoftInput(nativeView?: android.view.View): void {
const inputManager = getInputMethodManager();
let windowToken: android.os.IBinder;
if (nativeView instanceof android.view.View) {
if (!nativeView.hasFocus()) {
return;
}
windowToken = nativeView.getWindowToken();
} else if (getCurrentActivity() instanceof androidx.appcompat.app.AppCompatActivity) {
const modalDialog = (topmost()?._modalParent ?? (topmost()?.modal as any))?._dialogFragment?.getDialog();
const window = (modalDialog ?? getCurrentActivity()).getWindow();
const decorView = window.getDecorView();
if (decorView) {
windowToken = decorView.getWindowToken();
decorView.requestFocus();
} else {
windowToken = null;
}
}
if (inputManager && windowToken) {
inputManager.hideSoftInputFromWindow(windowToken, 0);
}
}
export namespace collections {
export function stringArrayToStringSet(str: string[]): java.util.HashSet<string> {
const hashSet = new java.util.HashSet<string>();
if (str !== undefined) {
for (const element in str) {
hashSet.add('' + str[element]);
}
}
return hashSet;
}
export function stringSetToStringArray(stringSet: any): string[] {
const arr = [];
if (stringSet !== undefined) {
const it = stringSet.iterator();
while (it.hasNext()) {
const element = '' + it.next();
arr.push(element);
}
}
return arr;
}
}
export namespace resources {
let attr;
const attrCache = new Map<string, number>();
export function getDrawableId(name) {
return getId(':drawable/' + name);
}
export function getStringId(name) {
return getId(':string/' + name);
}
export function getId(name: string): number {
const resources = getResources();
const packageName = getPackageName();
const uri = packageName + name;
return resources.getIdentifier(uri, null, null);
}
export function getResource(name: string, type?: string): number {
return getResources().getIdentifier(name, type, getPackageName());
}
export function getPaletteColor(name: string, context: android.content.Context): number {
if (attrCache.has(name)) {
return attrCache.get(name);
}
let result = 0;
try {
if (!attr) {
attr = java.lang.Class.forName('androidx.appcompat.R$attr');
}
let colorID = 0;
const field = attr.getField(name);
if (field) {
colorID = field.getInt(null);
}
if (colorID) {
const typedValue = new android.util.TypedValue();
context.getTheme().resolveAttribute(colorID, typedValue, true);
result = typedValue.data;
}
} catch (ex) {
Trace.write('Cannot get pallete color: ' + name, Trace.categories.Error, Trace.messageType.error);
}
attrCache.set(name, result);
return result;
}
}
export function isRealDevice(): boolean {
const fingerprint = android.os.Build.FINGERPRINT;
return fingerprint != null && (fingerprint.indexOf('vbox') > -1 || fingerprint.indexOf('generic') > -1);
}

View File

@ -1,300 +1,7 @@
import { platformCheck } from './platform-check';
import { numberHasDecimals, numberIs64Bit } from './types';
import { getNativeApp } from '../application/helpers-common';
import { androidGetCurrentActivity } from '../application/helpers';
import { Trace } from '../trace';
import { topmost } from '../ui/frame/frame-stack';
export function dataDeserialize(nativeData?: any) {
if (nativeData === null || typeof nativeData !== 'object') {
return nativeData;
}
let store;
switch (nativeData.getClass().getName()) {
case 'java.lang.String': {
return String(nativeData);
}
case 'java.lang.Boolean': {
return String(nativeData) === 'true';
}
case 'java.lang.Float':
case 'java.lang.Integer':
case 'java.lang.Long':
case 'java.lang.Double':
case 'java.lang.Short': {
return Number(nativeData);
}
case 'org.json.JSONArray': {
store = [];
for (let j = 0; j < nativeData.length(); j++) {
store[j] = dataDeserialize(nativeData.get(j));
}
break;
}
case 'org.json.JSONObject': {
store = {};
const i = nativeData.keys();
let key;
while (i.hasNext()) {
key = i.next();
store[key] = dataDeserialize(nativeData.get(key));
}
break;
}
case 'androidx.collection.SimpleArrayMap': {
const count = nativeData.size();
for (let l = 0; l < count; l++) {
const key = nativeData.keyAt(l);
store[key] = dataDeserialize(nativeData.get(key));
}
break;
}
case 'androidx.collection.ArrayMap':
case 'android.os.Bundle':
case 'java.util.HashMap':
case 'java.util.Map': {
store = {};
const keys = nativeData.keySet().toArray();
for (let k = 0; k < keys.length; k++) {
const key = keys[k];
store[key] = dataDeserialize(nativeData.get(key));
}
break;
}
default:
if (typeof nativeData === 'object' && nativeData instanceof java.util.List) {
const array = [];
const size = nativeData.size();
for (let i = 0, n = size; i < n; i++) {
array[i] = dataDeserialize(nativeData.get(i));
}
store = array;
} else {
store = null;
}
break;
}
return store;
}
export function dataSerialize(data?: any, wrapPrimitives?: boolean) {
let store;
switch (typeof data) {
case 'string':
case 'boolean': {
if (wrapPrimitives) {
if (typeof data === 'string') {
return new java.lang.String(data);
}
return new java.lang.Boolean(data);
}
return data;
}
case 'number': {
const hasDecimals = numberHasDecimals(data);
if (numberIs64Bit(data)) {
if (hasDecimals) {
return java.lang.Double.valueOf(data);
} else {
return java.lang.Long.valueOf(data);
}
} else {
if (hasDecimals) {
return java.lang.Float.valueOf(data);
} else {
return java.lang.Integer.valueOf(data);
}
}
}
case 'object': {
if (!data) {
return null;
}
if (data instanceof Date) {
return new java.util.Date(data.getTime());
}
if (Array.isArray(data)) {
store = new java.util.ArrayList();
data.forEach((item) => store.add(dataSerialize(item, wrapPrimitives)));
return store;
}
if (data.native) {
return data.native;
}
store = new java.util.HashMap();
Object.keys(data).forEach((key) => store.put(key, dataSerialize(data[key], wrapPrimitives)));
return store;
}
default:
return null;
}
}
function getApplicationContext() {
return getApplication().getApplicationContext();
}
function getCurrentActivity() {
return androidGetCurrentActivity();
}
function getApplication() {
return getNativeApp() as globalAndroid.app.Application;
}
function getResources() {
return getApplication().getResources();
}
let packageName: string;
function getPackageName() {
if (!packageName) {
packageName = getApplicationContext().getPackageName();
}
return packageName;
}
let inputMethodManager: globalAndroid.view.inputmethod.InputMethodManager;
function getInputMethodManager(): globalAndroid.view.inputmethod.InputMethodManager {
if (!inputMethodManager) {
inputMethodManager = <globalAndroid.view.inputmethod.InputMethodManager>getApplicationContext().getSystemService(globalAndroid.content.Context.INPUT_METHOD_SERVICE);
}
return inputMethodManager;
}
export function getWindow() {
return getCurrentActivity()?.getWindow();
}
function showSoftInput(nativeView: globalAndroid.view.View): void {
const inputManager = getInputMethodManager();
if (inputManager && nativeView instanceof globalAndroid.view.View) {
inputManager.showSoftInput(nativeView, globalAndroid.view.inputmethod.InputMethodManager.SHOW_IMPLICIT);
}
}
function dismissSoftInput(nativeView?: globalAndroid.view.View): void {
const inputManager = getInputMethodManager();
let windowToken: globalAndroid.os.IBinder;
if (nativeView instanceof globalAndroid.view.View) {
if (!nativeView.hasFocus()) {
return;
}
windowToken = nativeView.getWindowToken();
} else if (getCurrentActivity() instanceof androidx.appcompat.app.AppCompatActivity) {
const modalDialog = (topmost()?._modalParent ?? (topmost()?.modal as any))?._dialogFragment?.getDialog();
const window = (modalDialog ?? getCurrentActivity()).getWindow();
const decorView = window.getDecorView();
if (decorView) {
windowToken = decorView.getWindowToken();
decorView.requestFocus();
} else {
windowToken = null;
}
}
if (inputManager && windowToken) {
inputManager.hideSoftInputFromWindow(windowToken, 0);
}
}
namespace collections {
export function stringArrayToStringSet(str: string[]): java.util.HashSet<string> {
const hashSet = new java.util.HashSet<string>();
if (str !== undefined) {
for (const element in str) {
hashSet.add('' + str[element]);
}
}
return hashSet;
}
export function stringSetToStringArray(stringSet: any): string[] {
const arr = [];
if (stringSet !== undefined) {
const it = stringSet.iterator();
while (it.hasNext()) {
const element = '' + it.next();
arr.push(element);
}
}
return arr;
}
}
namespace resources {
let attr;
const attrCache = new Map<string, number>();
export function getDrawableId(name) {
return getId(':drawable/' + name);
}
export function getStringId(name) {
return getId(':string/' + name);
}
export function getId(name: string): number {
const resources = getResources();
const packageName = getPackageName();
const uri = packageName + name;
return resources.getIdentifier(uri, null, null);
}
export function getResource(name: string, type?: string): number {
return getResources().getIdentifier(name, type, getPackageName());
}
export function getPaletteColor(name: string, context: globalAndroid.content.Context): number {
if (attrCache.has(name)) {
return attrCache.get(name);
}
let result = 0;
try {
if (!attr) {
attr = java.lang.Class.forName('androidx.appcompat.R$attr');
}
let colorID = 0;
const field = attr.getField(name);
if (field) {
colorID = field.getInt(null);
}
if (colorID) {
const typedValue = new globalAndroid.util.TypedValue();
context.getTheme().resolveAttribute(colorID, typedValue, true);
result = typedValue.data;
}
} catch (ex) {
Trace.write('Cannot get pallete color: ' + name, Trace.categories.Error, Trace.messageType.error);
}
attrCache.set(name, result);
return result;
}
}
export function isRealDevice(): boolean {
const fingerprint = globalAndroid.os.Build.FINGERPRINT;
return fingerprint != null && (fingerprint.indexOf('vbox') > -1 || fingerprint.indexOf('generic') > -1);
}
// importing this helper as a separate file avoids "android" symbol clash with the global android object
import { resources, getApplication, getCurrentActivity, getApplicationContext, getWindow, getResources, getPackageName, getInputMethodManager, showSoftInput, dismissSoftInput } from './native-helper-for-android';
export const android = {
resources,

View File

@ -1,6 +1,3 @@
// Shared helpers and types for utils, used by both native-helper and common.
// Only put platform-agnostic logic here.
export function getFileExtension(path: string): string {
if (!path) {
return '';
@ -8,5 +5,3 @@ export function getFileExtension(path: string): string {
const index = path.lastIndexOf('.');
return index !== -1 ? path.substring(index + 1) : '';
}
// Add more shared helpers/types/constants as needed.

View File

@ -1,5 +1,6 @@
import * as http from '../http';
import * as types from '../utils/types';
import type { HttpRequestOptions, HttpResponse } from '../http';
import { request } from '../http';
import { isString, isFunction } from '../utils/types';
import { Trace } from '../trace';
namespace XMLHttpRequestResponseType {
@ -24,7 +25,7 @@ export class XMLHttpRequest {
public onloadstart: (...args: any[]) => void;
public onprogress: (...args: any[]) => void;
private _options: http.HttpRequestOptions;
private _options: HttpRequestOptions;
private _readyState: number;
private _status: number;
private _response: any;
@ -64,7 +65,7 @@ export class XMLHttpRequest {
throw new Error("Failed to read the 'responseText' property from 'XMLHttpRequest': " + "The value is only accessible if the object's 'responseType' is '' or 'text' " + `(was '${this._responseType}').`);
}
if (types.isFunction(this._responseTextReader)) {
if (isFunction(this._responseTextReader)) {
return this._responseTextReader();
}
@ -103,7 +104,7 @@ export class XMLHttpRequest {
this._readyState = this.UNSENT;
}
private _loadResponse(r: http.HttpResponse) {
private _loadResponse(r: HttpResponse) {
this._status = r.statusCode;
this._headers = r.headers;
this._setReadyState(this.HEADERS_RECEIVED);
@ -135,7 +136,7 @@ export class XMLHttpRequest {
}
private emitEvent(eventName: string, ...args: Array<any>) {
if (types.isFunction(this['on' + eventName])) {
if (isFunction(this['on' + eventName])) {
this['on' + eventName](...args);
}
@ -188,15 +189,15 @@ export class XMLHttpRequest {
}
public open(method: string, url: string, async?: boolean, user?: string, password?: string) {
if (types.isString(method) && types.isString(url)) {
if (isString(method) && isString(url)) {
this._options = { url: url, method: method };
this._options.headers = {};
if (types.isString(user)) {
if (isString(user)) {
this._options.headers['user'] = user;
}
if (types.isString(password)) {
if (isString(password)) {
this._options.headers['password'] = password;
}
@ -232,7 +233,7 @@ export class XMLHttpRequest {
throw new Error("Failed to execute 'send' on 'XMLHttpRequest': " + "The object's state must be OPENED.");
}
if (types.isString(data) && this._options.method !== 'GET') {
if (isString(data) && this._options.method !== 'GET') {
//The Android Java HTTP lib throws an exception if we provide a
//a request body for GET requests, so we avoid doing that.
//Browser implementations silently ignore it as well.
@ -250,8 +251,7 @@ export class XMLHttpRequest {
this.emitEvent('loadstart');
http
.request(this._options)
request(this._options)
.then((r) => {
if (!this._errorFlag && this._sendFlag) {
this._loadResponse(r);
@ -269,7 +269,7 @@ export class XMLHttpRequest {
throw new Error("Failed to execute 'setRequestHeader' on 'XMLHttpRequest': " + "The object's state must be OPENED.");
}
if (types.isString(header) && types.isString(value)) {
if (isString(header) && isString(value)) {
this._options.headers[header] = value;
}
}
@ -289,7 +289,7 @@ export class XMLHttpRequest {
}
public getResponseHeader(header: string): string {
if (types.isString(header) && this._readyState > 1 && this._headers && !this._errorFlag) {
if (isString(header) && this._readyState > 1 && this._headers && !this._errorFlag) {
header = header.toLowerCase();
for (const i in this._headers) {
if (i.toLowerCase() === header) {
@ -559,7 +559,7 @@ export class FileReader {
}
private emitEvent(eventName: string, ...args: Array<any>) {
if (types.isFunction(this['on' + eventName])) {
if (isFunction(this['on' + eventName])) {
this['on' + eventName](...args);
}