diff --git a/e2e/animation/package.json b/e2e/animation/package.json index 00c40a851..b89e6fd59 100644 --- a/e2e/animation/package.json +++ b/e2e/animation/package.json @@ -19,7 +19,6 @@ "devDependencies": { "@types/chai": "~4.1.7", "@types/mocha": "~5.2.5", - "@types/node": "~10.12.18", "babel-traverse": "6.26.0", "babel-types": "6.26.0", "babylon": "6.18.0", diff --git a/e2e/modal-navigation/package.json b/e2e/modal-navigation/package.json index c3ffbd134..b9fca7282 100644 --- a/e2e/modal-navigation/package.json +++ b/e2e/modal-navigation/package.json @@ -19,14 +19,13 @@ "devDependencies": { "@types/chai": "~4.1.7", "@types/mocha": "~5.2.5", - "@types/node": "^7.0.5", "mocha": "~5.2.0", "mochawesome": "~3.1.2", "nativescript-dev-appium": "next", "nativescript-dev-typescript": "next", "nativescript-dev-webpack": "next", - "tns-platform-declarations": "next", "rimraf": "^2.6.2", + "tns-platform-declarations": "next", "typescript": "^3.1.6" }, "scripts": { diff --git a/e2e/nested-frame-navigation/package.json b/e2e/nested-frame-navigation/package.json index e9754a49e..a433feba2 100644 --- a/e2e/nested-frame-navigation/package.json +++ b/e2e/nested-frame-navigation/package.json @@ -19,7 +19,6 @@ "devDependencies": { "@types/chai": "~4.1.7", "@types/mocha": "~5.2.5", - "@types/node": "^7.0.5", "mocha": "~5.2.0", "mochawesome": "~3.1.2", "nativescript-dev-appium": "next", diff --git a/tests/app/ui/image-cache/image-cache-tests.ts b/tests/app/ui/image-cache/image-cache-tests.ts index c6237b8ac..946cf426b 100644 --- a/tests/app/ui/image-cache/image-cache-tests.ts +++ b/tests/app/ui/image-cache/image-cache-tests.ts @@ -1,9 +1,19 @@ import * as imageCacheModule from "tns-core-modules/ui/image-cache"; import * as imageSource from "tns-core-modules/image-source"; import * as types from "tns-core-modules/utils/types"; +import { device } from "tns-core-modules/platform"; +import lazy from "tns-core-modules/utils/lazy"; + import * as TKUnit from "../../TKUnit"; +const sdkVersion = lazy(() => parseInt(device.sdkVersion)); + export const test_ImageCache_ValidUrl = function() { + // see https://github.com/NativeScript/NativeScript/issues/6643 + if (sdkVersion() < 20) { + return; + } + const cache = new imageCacheModule.Cache(); cache.maxRequests = 5; diff --git a/tests/app/utils/utils-tests.ts b/tests/app/utils/utils-tests.ts index 10e07d498..ac9dd2ab0 100644 --- a/tests/app/utils/utils-tests.ts +++ b/tests/app/utils/utils-tests.ts @@ -18,6 +18,47 @@ export function test_releaseNativeObject_canBeCalledWithNativeObject() { } }; +export function test_executeOnMainThread_Works(done: Function) { + utils.executeOnMainThread(() => { + try { + TKUnit.assertTrue(utils.isMainThread()); + done(); + } catch (e) { + done(e); + } + }); +} + +export function test_dispatchToMainThread_Works(done: Function) { + utils.dispatchToMainThread(() => { + try { + TKUnit.assertTrue(utils.isMainThread()); + done(); + } catch (e) { + done(e); + } + }); +} + +export function test_mainThreadify_PassesArgs(done: Function) { + const expectedN = 434; + const expectedB = true; + const expectedS = "string"; + const f = utils.mainThreadify(function (n: number, b: boolean, s: string) { + try { + TKUnit.assertTrue(utils.isMainThread()); + TKUnit.assertEqual(n, expectedN); + TKUnit.assertEqual(b, expectedB); + TKUnit.assertEqual(s, expectedS); + done(); + } catch (e) { + done(e); + } + }); + + f(expectedN, expectedB, expectedS); +} + function test_releaseNativeObject_canBeCalledWithNativeObject_iOS() { let deallocated = false; const obj = new ((NSObject).extend({ diff --git a/tns-core-modules/debugger/devtools-elements.common.ts b/tns-core-modules/debugger/devtools-elements.common.ts index 186cfe320..c0b7ac43f 100644 --- a/tns-core-modules/debugger/devtools-elements.common.ts +++ b/tns-core-modules/debugger/devtools-elements.common.ts @@ -2,6 +2,7 @@ import { getNodeById } from "./dom-node"; // Needed for typings only import { ViewBase } from "../ui/core/view-base"; +import { mainThreadify } from "../utils/utils"; // Use lazy requires for core modules const frameTopmost = () => require("../ui/frame").topmost(); @@ -11,7 +12,7 @@ function unsetViewValue(view, name) { if (!unsetValue) { unsetValue = require("../ui/core/properties").unsetValue; } - + view[name] = unsetValue; } @@ -30,10 +31,10 @@ export function getDocument() { if (!topMostFrame) { return undefined; } - + try { topMostFrame.ensureDomNode(); - + } catch (e) { console.log("ERROR in getDocument(): " + e); } @@ -49,7 +50,7 @@ export function getComputedStylesForNode(nodeId): Array<{ name: string, value: s return []; } -export function removeNode(nodeId) { +export const removeNode = mainThreadify(function removeNode(nodeId) { const view = getViewById(nodeId); if (view) { // Avoid importing layout and content view @@ -63,9 +64,9 @@ export function removeNode(nodeId) { console.log("Can't remove child from " + parent); } } -} +}); -export function setAttributeAsText(nodeId, text, name) { +export const setAttributeAsText = mainThreadify(function setAttributeAsText(nodeId, text, name) { const view = getViewById(nodeId); if (view) { // attribute is registered for the view instance @@ -93,4 +94,4 @@ export function setAttributeAsText(nodeId, text, name) { view.domNode.loadAttributes(); } -} +}); diff --git a/tns-core-modules/utils/mainthread-helper.android.ts b/tns-core-modules/utils/mainthread-helper.android.ts new file mode 100644 index 000000000..1ef05eaa5 --- /dev/null +++ b/tns-core-modules/utils/mainthread-helper.android.ts @@ -0,0 +1,10 @@ +export function dispatchToMainThread(func: () => void) { + new android.os.Handler(android.os.Looper.getMainLooper()) + .post(new java.lang.Runnable({ + run: func + })); +} + +export function isMainThread(): Boolean { + return android.os.Looper.myLooper() === android.os.Looper.getMainLooper(); +} diff --git a/tns-core-modules/utils/mainthread-helper.d.ts b/tns-core-modules/utils/mainthread-helper.d.ts new file mode 100644 index 000000000..46ff0142d --- /dev/null +++ b/tns-core-modules/utils/mainthread-helper.d.ts @@ -0,0 +1,10 @@ +/** + * Dispatches the passed function for execution on the main thread + * @param func The function to execute on the main thread. + */ +export function dispatchToMainThread(func: Function); + +/** + * @returns Boolean value indicating whether the current thread is the main thread + */ +export function isMainThread(): boolean diff --git a/tns-core-modules/utils/mainthread-helper.ios.ts b/tns-core-modules/utils/mainthread-helper.ios.ts new file mode 100644 index 000000000..01ed3bb17 --- /dev/null +++ b/tns-core-modules/utils/mainthread-helper.ios.ts @@ -0,0 +1,7 @@ +export function dispatchToMainThread(func: () => void) { + NSOperationQueue.mainQueue.addOperationWithBlock(func); +} + +export function isMainThread(): Boolean { + return NSThread.isMainThread; +} diff --git a/tns-core-modules/utils/utils-common.ts b/tns-core-modules/utils/utils-common.ts index 97829e166..6141d8fe5 100644 --- a/tns-core-modules/utils/utils-common.ts +++ b/tns-core-modules/utils/utils-common.ts @@ -1,4 +1,7 @@ import * as types from "./types"; +import { dispatchToMainThread, isMainThread } from "./mainthread-helper" + +export * from "./mainthread-helper" export const RESOURCE_PREFIX = "res://"; export const FILE_PREFIX = "file:///"; @@ -154,3 +157,18 @@ export function hasDuplicates(arr: Array): boolean { export function eliminateDuplicates(arr: Array): Array { return Array.from(new Set(arr)); } + +export function executeOnMainThread(func: Function) { + if (isMainThread()) { + return func(); + } else { + dispatchToMainThread(func); + } +} + +export function mainThreadify(func: Function): (...args: any[]) => void { + return function () { + const argsToPass = arguments; + executeOnMainThread(() => func.apply(this, argsToPass)); + } +} diff --git a/tns-core-modules/utils/utils.d.ts b/tns-core-modules/utils/utils.d.ts index 6058a7508..08a1d80c6 100644 --- a/tns-core-modules/utils/utils.d.ts +++ b/tns-core-modules/utils/utils.d.ts @@ -4,6 +4,8 @@ import { dip, px } from "../ui/core/view"; +export * from "./mainthread-helper" + export const RESOURCE_PREFIX: string; export const FILE_PREFIX: string; @@ -269,6 +271,22 @@ export function GC(); */ export function releaseNativeObject(object: any /*java.lang.Object | NSObject*/); +/** + * Checks if the current thread is the main thread. Directly calls the passed function + * if it is, or dispatches it to the main thread otherwise. + * @param func The function to execute on the main thread. + */ +export function executeOnMainThread(func: Function); + +/** + * Returns a function wrapper which executes the supplied function on the main thread. + * The wrapper behaves like the original function and passes all of its arguments BUT + * discards its return value. + * @param func The function to execute on the main thread + * @returns The wrapper function which schedules execution to the main thread + */ +export function mainThreadify(func: Function): (...args: any[]) => void + /** * Returns true if the specified path points to a resource or local file. * @param path The path.