mirror of
https://github.com/espruino/Espruino.git
synced 2025-12-08 19:06:15 +00:00
Added E.stopEventPropagation() to allow event propagation to be stopped with multiple X.on('...', ...)
This commit is contained in:
parent
325c7e6c1d
commit
35c8cb9be1
@ -35,6 +35,7 @@
|
||||
Fix issue with Graphics.createImage if more than the first line was empty (fix #2296)
|
||||
Bangle.js: Now auto-reset compass if it was on while the watch was charging (fix https://github.com/espruino/BangleApps/issues/2648)
|
||||
Graphics: Added .floodFill method to allow flood fill
|
||||
Added E.stopEventPropagation() to allow event propagation to be stopped with multiple X.on('...', ...)
|
||||
|
||||
2v17 : Bangle.js: When reading file info from a filename table, do it in blocks of 8 (20% faster file search)
|
||||
Bangle.js2: Increase flash buffer size from 16->32 bytes (5% performance increase)
|
||||
|
||||
@ -81,7 +81,6 @@ uint16_t inputStateNumber; ///< Number from when `Esc [ 1234` is sent - for stor
|
||||
uint16_t jsiLineNumberOffset; ///< When we execute code, this is the 'offset' we apply to line numbers in error/debug
|
||||
bool hasUsedHistory = false; ///< Used to speed up - if we were cycling through history and then edit, we need to copy the string
|
||||
unsigned char loopsIdling = 0; ///< How many times around the loop have we been entirely idle?
|
||||
bool interruptedDuringEvent; ///< Were we interrupted while executing an event? If so may want to clear timers
|
||||
JsErrorFlags lastJsErrorFlags = 0; ///< Compare with jsErrorFlags in order to report errors
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
@ -784,8 +783,6 @@ void jsiSoftKill() {
|
||||
void jsiSemiInit(bool autoLoad, JsfFileName *loadedFilename) {
|
||||
// Set up execInfo.root/etc
|
||||
jspInit();
|
||||
// Set state
|
||||
interruptedDuringEvent = false;
|
||||
// Set defaults
|
||||
jsiStatus &= JSIS_SOFTINIT_MASK;
|
||||
#ifndef SAVE_ON_FLASH
|
||||
@ -1179,9 +1176,9 @@ bool jsiAtEndOfInputLine() {
|
||||
}
|
||||
|
||||
void jsiCheckErrors() {
|
||||
if (interruptedDuringEvent) {
|
||||
if (jsiStatus & JSIS_EVENTEMITTER_INTERRUPTED) {
|
||||
jspSetInterrupted(false);
|
||||
interruptedDuringEvent = false;
|
||||
jsiStatus &= ~JSIS_EVENTEMITTER_INTERRUPTED;
|
||||
jsiConsoleRemoveInputLine();
|
||||
jsiConsolePrint("Execution Interrupted during event processing.\n");
|
||||
}
|
||||
@ -1683,13 +1680,6 @@ void jsiQueueObjectCallbacks(JsVar *object, const char *callbackName, JsVar **ar
|
||||
jsvUnLock(callback);
|
||||
}
|
||||
|
||||
void jsiExecuteObjectCallbacks(JsVar *object, const char *callbackName, JsVar **args, int argCount) {
|
||||
JsVar *callback = jsvObjectGetChildIfExists(object, callbackName);
|
||||
if (!callback) return;
|
||||
jsiExecuteEventCallback(object, callback, (unsigned int)argCount, args);
|
||||
jsvUnLock(callback);
|
||||
}
|
||||
|
||||
void jsiExecuteEvents() {
|
||||
bool hasEvents = !jsvArrayIsEmpty(events);
|
||||
if (hasEvents) jsiSetBusy(BUSY_INTERACTIVE, true);
|
||||
@ -1710,7 +1700,7 @@ void jsiExecuteEvents() {
|
||||
if (hasEvents) {
|
||||
jsiSetBusy(BUSY_INTERACTIVE, false);
|
||||
if (jspIsInterrupted())
|
||||
interruptedDuringEvent = true;
|
||||
jsiStatus |= JSIS_EVENTEMITTER_INTERRUPTED;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1733,35 +1723,35 @@ NO_INLINE bool jsiExecuteEventCallbackArgsArray(JsVar *thisVar, JsVar *callbackV
|
||||
|
||||
NO_INLINE bool jsiExecuteEventCallback(JsVar *thisVar, JsVar *callbackVar, unsigned int argCount, JsVar **argPtr) { // array of functions or single function
|
||||
JsVar *callbackNoNames = jsvSkipName(callbackVar);
|
||||
if (!callbackNoNames) return false;
|
||||
|
||||
bool ok = true;
|
||||
if (callbackNoNames) {
|
||||
if (jsvIsArray(callbackNoNames)) {
|
||||
JsvObjectIterator it;
|
||||
jsvObjectIteratorNew(&it, callbackNoNames);
|
||||
while (ok && jsvObjectIteratorHasValue(&it)) {
|
||||
JsVar *child = jsvObjectIteratorGetValue(&it);
|
||||
ok &= jsiExecuteEventCallback(thisVar, child, argCount, argPtr);
|
||||
jsvUnLock(child);
|
||||
jsvObjectIteratorNext(&it);
|
||||
}
|
||||
jsvObjectIteratorFree(&it);
|
||||
} else if (jsvIsFunction(callbackNoNames)) {
|
||||
jsvUnLock(jspExecuteFunction(callbackNoNames, thisVar, (int)argCount, argPtr));
|
||||
} else if (jsvIsString(callbackNoNames)) {
|
||||
jsvUnLock(jspEvaluateVar(callbackNoNames, 0, 0));
|
||||
} else
|
||||
jsError("Unknown type of callback in Event Queue");
|
||||
jsvUnLock(callbackNoNames);
|
||||
}
|
||||
jsiStatus |= JSIS_EVENTEMITTER_PROCESSING;
|
||||
if (jsvIsArray(callbackNoNames)) {
|
||||
JsvObjectIterator it;
|
||||
jsvObjectIteratorNew(&it, callbackNoNames);
|
||||
while (ok && jsvObjectIteratorHasValue(&it) && !(jsiStatus & JSIS_EVENTEMITTER_STOP)) {
|
||||
JsVar *child = jsvObjectIteratorGetValue(&it);
|
||||
ok &= jsiExecuteEventCallback(thisVar, child, argCount, argPtr);
|
||||
jsvUnLock(child);
|
||||
jsvObjectIteratorNext(&it);
|
||||
}
|
||||
jsvObjectIteratorFree(&it);
|
||||
} else if (jsvIsFunction(callbackNoNames)) {
|
||||
jsvUnLock(jspExecuteFunction(callbackNoNames, thisVar, (int)argCount, argPtr));
|
||||
} else if (jsvIsString(callbackNoNames)) {
|
||||
jsvUnLock(jspEvaluateVar(callbackNoNames, 0, 0));
|
||||
} else
|
||||
jsError("Unknown type of callback in Event Queue");
|
||||
jsvUnLock(callbackNoNames);
|
||||
jsiStatus &= ~JSIS_EVENTEMITTER_PROCESSING;
|
||||
if (!ok || jspIsInterrupted()) {
|
||||
interruptedDuringEvent = true;
|
||||
jsiStatus |= JSIS_EVENTEMITTER_INTERRUPTED;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
// Execute the named Event callback on object, and return true if it exists
|
||||
bool jsiExecuteEventCallbackName(JsVar *obj, const char *cbName, unsigned int argCount, JsVar **argPtr) {
|
||||
bool executed = false;
|
||||
@ -1784,7 +1774,6 @@ bool jsiExecuteEventCallbackOn(const char *objectName, const char *cbName, unsig
|
||||
return executed;
|
||||
}
|
||||
|
||||
|
||||
/// Create a timeout in JS to execute the given native function (outside of an IRQ). Returns the index
|
||||
JsVar *jsiSetTimeout(void (*functionPtr)(void), JsVarFloat milliseconds) {
|
||||
JsVar *fn = jsvNewNativeFunction((void (*)(void))functionPtr, JSWAT_VOID);
|
||||
@ -1925,9 +1914,9 @@ void jsiIdle() {
|
||||
JsVar *usartClass = jsvSkipNameAndUnLock(jsiGetClassNameFromDevice(IOEVENTFLAGS_GETTYPE(IOEVENTFLAGS_SERIAL_STATUS_TO_SERIAL(event.flags))));
|
||||
if (jsvIsObject(usartClass)) {
|
||||
if (event.flags & EV_SERIAL_STATUS_FRAMING_ERR)
|
||||
jsiExecuteObjectCallbacks(usartClass, JS_EVENT_PREFIX"framing", 0, 0);
|
||||
jsiExecuteEventCallbackName(usartClass, JS_EVENT_PREFIX"framing", 0, 0);
|
||||
if (event.flags & EV_SERIAL_STATUS_PARITY_ERR)
|
||||
jsiExecuteObjectCallbacks(usartClass, JS_EVENT_PREFIX"parity", 0, 0);
|
||||
jsiExecuteEventCallbackName(usartClass, JS_EVENT_PREFIX"parity", 0, 0);
|
||||
}
|
||||
jsvUnLock(usartClass);
|
||||
#endif
|
||||
@ -1946,7 +1935,7 @@ void jsiIdle() {
|
||||
if (obj) {
|
||||
jsvObjectSetChildAndUnLock(obj, "addr", jsvNewFromInteger(addr&0x7F));
|
||||
jsvObjectSetChildAndUnLock(obj, "length", jsvNewFromInteger(len));
|
||||
jsiExecuteObjectCallbacks(i2cClass, (addr&0x80) ? JS_EVENT_PREFIX"read" : JS_EVENT_PREFIX"write", &obj, 1);
|
||||
jsiExecuteEventCallbackName(i2cClass, (addr&0x80) ? JS_EVENT_PREFIX"read" : JS_EVENT_PREFIX"write", 1, &obj);
|
||||
jsvUnLock(obj);
|
||||
}
|
||||
}
|
||||
@ -2236,7 +2225,7 @@ void jsiIdle() {
|
||||
unsigned int oldJsVarsSize = jsvGetMemoryTotal(); // we must remember the old memory size - mainly for ESP32 where it can change at boot time
|
||||
JsiStatus s = jsiStatus;
|
||||
if ((s&JSIS_TODO_RESET) == JSIS_TODO_RESET) {
|
||||
// shut down everything and start up again
|
||||
// shut down everything and start up again
|
||||
jsiKill();
|
||||
jsvKill();
|
||||
jshReset();
|
||||
@ -2493,7 +2482,7 @@ void jsiDebuggerLoop() {
|
||||
// in debugger already
|
||||
// echo is off for line (probably uploading)
|
||||
if (jsiStatus & (JSIS_IN_DEBUGGER|JSIS_ECHO_OFF_FOR_LINE)) return;
|
||||
|
||||
|
||||
execInfo.execute &= (JsExecFlags)~(
|
||||
EXEC_CTRL_C_MASK |
|
||||
EXEC_DEBUGGER_NEXT_LINE |
|
||||
|
||||
@ -53,8 +53,6 @@ void jsiQueueEvents(JsVar *object, JsVar *callback, JsVar **args, int argCount);
|
||||
bool jsiObjectHasCallbacks(JsVar *object, const char *callbackName);
|
||||
/// Queue up callbacks for other things (touchscreen? network?)
|
||||
void jsiQueueObjectCallbacks(JsVar *object, const char *callbackName, JsVar **args, int argCount);
|
||||
/// Execute callbacks straight away (like jsiQueueObjectCallbacks, but without queueing)
|
||||
void jsiExecuteObjectCallbacks(JsVar *object, const char *callbackName, JsVar **args, int argCount);
|
||||
/// Execute the given function/string/array of functions and return true on success, false on failure (break during execution)
|
||||
bool jsiExecuteEventCallback(JsVar *thisVar, JsVar *callbackVar, unsigned int argCount, JsVar **argPtr);
|
||||
/// Same as above, but with a JsVarArray (this calls jsiExecuteEventCallback, so use jsiExecuteEventCallback where possible)
|
||||
@ -162,6 +160,10 @@ typedef enum {
|
||||
JSIS_COMPLETELY_RESET = 1<<11, ///< Has the board powered on *having not loaded anything from flash*
|
||||
JSIS_FIRST_BOOT = 1<<12, ///< Is this the first time we started, or has load/reset/etc been called?
|
||||
|
||||
JSIS_EVENTEMITTER_PROCESSING = 1<<13, ///< Are we currently executing events with jsiExecuteEvent*
|
||||
JSIS_EVENTEMITTER_STOP = 1<<14, ///< Has E.stopEventPropagation() been called during event processing?
|
||||
JSIS_EVENTEMITTER_INTERRUPTED = 1<<15,///< Has there been an error during jsiExecuteEvent* execution?
|
||||
|
||||
JSIS_ECHO_OFF_MASK = JSIS_ECHO_OFF|JSIS_ECHO_OFF_FOR_LINE,
|
||||
JSIS_SOFTINIT_MASK = JSIS_PASSWORD_PROTECTED|JSIS_WATCHDOG_AUTO|JSIS_TODO_MASK|JSIS_FIRST_BOOT|JSIS_COMPLETELY_RESET // stuff that DOESN'T get reset on softinit
|
||||
// watchdog can't be reset without a reboot so if it's set to auto we must keep it as auto
|
||||
|
||||
@ -615,18 +615,18 @@ If `isAuto` is false, you must call `E.kickWatchdog()` yourself every so often
|
||||
or the chip will reset.
|
||||
|
||||
```
|
||||
E.enableWatchdog(0.5); // automatic mode
|
||||
E.enableWatchdog(0.5); // automatic mode
|
||||
while(1); // Espruino will reboot because it has not been idle for 0.5 sec
|
||||
```
|
||||
|
||||
```
|
||||
E.enableWatchdog(1, false);
|
||||
E.enableWatchdog(1, false);
|
||||
setInterval(function() {
|
||||
if (everything_ok)
|
||||
E.kickWatchdog();
|
||||
}, 500);
|
||||
// Espruino will now reset if everything_ok is false,
|
||||
// or if the interval fails to be called
|
||||
// or if the interval fails to be called
|
||||
```
|
||||
|
||||
**NOTE:** This is only implemented on STM32, nRF5x and ESP32 devices (all official
|
||||
@ -2298,3 +2298,32 @@ JsVar *jswrap_espruino_decodeUTF8(JsVar *str, JsVar *lookup, JsVar *replaceFn) {
|
||||
return dst;
|
||||
}
|
||||
|
||||
/*JSON{
|
||||
"type" : "staticmethod",
|
||||
"class" : "E",
|
||||
"name" : "stopEventPropagation",
|
||||
"generate" : "jswrap_espruino_stopEventPropagation"
|
||||
}
|
||||
When using events with `X.on('foo', function() { ... })`
|
||||
and then `X.emit('foo')` you might want to stop subsequent
|
||||
event handlers from being executed.
|
||||
|
||||
Calling this function doing the execution of events will
|
||||
ensure that no subsequent event handlers are executed.
|
||||
|
||||
```
|
||||
var X = {}; // in Espruino all objects are EventEmitters
|
||||
X.on('foo', function() { print("A"); })
|
||||
X.on('foo', function() { print("B"); E.stopEventPropagation(); })
|
||||
X.on('foo', function() { print("C"); })
|
||||
X.emit('foo');
|
||||
// prints A,B but not C
|
||||
```
|
||||
*/
|
||||
void jswrap_espruino_stopEventPropagation() {
|
||||
if (jsiStatus & JSIS_EVENTEMITTER_PROCESSING) {
|
||||
jsiStatus |= JSIS_EVENTEMITTER_STOP;
|
||||
} else {
|
||||
jsExceptionHere(JSET_ERROR, "E.stopEventPropagation() called when not handling events");
|
||||
}
|
||||
}
|
||||
@ -74,5 +74,6 @@ JsVarInt jswrap_espruino_getBattery();
|
||||
void jswrap_espruino_setRTCPrescaler(int prescale);
|
||||
int jswrap_espruino_getRTCPrescaler(bool calibrate);
|
||||
JsVar *jswrap_espruino_decodeUTF8(JsVar *str, JsVar *lookup, JsVar *replaceFn);
|
||||
void jswrap_espruino_stopEventPropagation();
|
||||
|
||||
#endif // JSWRAP_ESPRUINO_H_
|
||||
|
||||
@ -830,6 +830,10 @@ o.removeAllListeners('answer')
|
||||
o.emit('answer', 44);
|
||||
// nothing printed
|
||||
```
|
||||
|
||||
If you have more than one handler for an event, and you'd
|
||||
like that handler to stop the event being passed to other handlers
|
||||
then you can call `E.stopEventPropagation()` in that handler.
|
||||
*/
|
||||
#ifndef ESPR_EMBED
|
||||
void jswrap_object_on(JsVar *parent, JsVar *event, JsVar *listener) {
|
||||
@ -1085,7 +1089,7 @@ void jswrap_function_replaceWith(JsVar *oldFunc, JsVar *newFunc) {
|
||||
oldFunc->varData.native = newFunc->varData.native; // copy fn pointer
|
||||
} else {
|
||||
memset(&oldFunc->varData.native, 0, sizeof(oldFunc->varData.native)); // remove pointer info, zero it all out
|
||||
oldFunc->flags = (oldFunc->flags&~JSV_VARTYPEMASK) | JSV_FUNCTION;
|
||||
oldFunc->flags = (oldFunc->flags&~JSV_VARTYPEMASK) | JSV_FUNCTION;
|
||||
}
|
||||
}
|
||||
// If old fn started with 'return' or vice versa...
|
||||
|
||||
18
tests/test_eventemitter_stoppropagation.js
Normal file
18
tests/test_eventemitter_stoppropagation.js
Normal file
@ -0,0 +1,18 @@
|
||||
var X = {}; // in Espruino all objects are EventEmitters
|
||||
var v = "";
|
||||
X.on('foo', function() { print("A"); v+="A"; })
|
||||
X.on('foo', function() { print("B"); v+="B"; E.stopEventPropagation(); })
|
||||
X.on('foo', function() { print("C"); v+="C"; })
|
||||
X.emit('foo');
|
||||
|
||||
try {
|
||||
E.stopEventPropagation();
|
||||
} catch (e) {
|
||||
// expected!
|
||||
v += "x";
|
||||
}
|
||||
|
||||
// prints A,B but not C
|
||||
setTimeout(function() {
|
||||
result = v=="xAB";
|
||||
}, 1);
|
||||
Loading…
x
Reference in New Issue
Block a user