[widgetservice][utils] Common implementation for ListenerManager. 12/196112/6
authorMichal Michalski <m.michalski2@partner.samsung.com>
Fri, 21 Dec 2018 19:35:17 +0000 (20:35 +0100)
committerMichal Michalski <m.michalski2@partner.samsung.com>
Thu, 3 Jan 2019 12:38:40 +0000 (13:38 +0100)
I extracted broken ListenerManager implementation from widgetservice module
fixed it, and placed it inside utils_api.js module as CommonListenerManager.
This implementation has customizable behavior if removeListener cannot find
listener for given id, so you can throw exception, do nothing, or return
error object.

[Verification]

Test usage of this CommonListenerManager implementation is provided in this
commit using widgetservice widgetStateChangeListener which I replaced.
Manual tests were performed to verify that the implementation behaves as
expected.

Change-Id: I0e18e091b9ad8cacc685663c65f3358cc022188d

src/utils/utils_api.js
src/widgetservice/widgetservice_api.js

index 83f18c9..d642eb8 100644 (file)
@@ -29,6 +29,77 @@ var signature_to_type = {
   'o': 'object'
 };
 
+
+/**
+ * @brief CommonListenerManager constructor function.
+ * @param nativeMgr NativeManager handle.
+ * @param managerName Name which will be used as native listener name.
+ * @param onIdNotFound Callback without arguments invoked when trying to remove not existing listener. If unspecified it is replaced by empty function.
+ */
+var CommonListenerManager = function(nativeMgr, managerName, onIdNotFound) {
+  this.listeners = {}
+  this.nextId = 1;
+  this.numberOfListeners = 0;
+  this.name = managerName;
+  this.native = nativeMgr;
+  this.hasNativeListener = false;
+  if (onIdNotFound === undefined) {
+    this.idNotFoundBehavior = function() {};
+  } else {
+    this.idNotFoundBehavior = onIdNotFound;
+  }
+}
+
+/**
+ * @brief Callback for native listener, which invokes all existing listeners.
+ * @param msg native Arguments object for native listener callback.
+ */
+CommonListenerManager.prototype.onListenerCalled = function(msg) {
+  for (var watchId in this.listeners) {
+    if (this.listeners.hasOwnProperty(watchId)) {
+      this.listeners[watchId](this.native.getResultObject(msg));
+    }
+  }
+};
+
+/**
+ * @brief Registers new listener.
+ * @param callback Callback function taking single argument.
+ * @retval integer listener id. Use it later to remove listener.
+ */
+CommonListenerManager.prototype.addListener = function(callback) {
+  if (!this.hasNativeListener) {
+    this.native.addListener(this.name, this.onListenerCalled.bind(this));
+    this.hasNativeListener = true;
+  }
+  var id = this.nextId++;
+  this.listeners[id] = callback;
+  this.numberOfListeners++;
+  return id;
+}
+
+/**
+ * @brief Remove previously registered listener.
+ *
+ * If listener for given id is not found, onIdNotFound callback is invoked.
+ *
+ * @param watchId Listener id returned by addListener.
+ * @retval onIdNotFoundCallback return value.
+ */
+CommonListenerManager.prototype.removeListener = function(watchId) {
+  if (this.listeners.hasOwnProperty(watchId)) {
+    delete this.listeners[watchId];
+    this.numberOfListeners--;
+    if (this.numberOfListeners == 0) {
+      this.native.removeListener(this.name);
+      this.hasNativeListener = false;
+      this.nextId = 1;
+    }
+  } else {
+    return this.idNotFoundBehavior();
+  }
+}
+
 var DateConverter = function() {};
 
 DateConverter.prototype.toTZDate = function(v, isAllDay) {
@@ -70,10 +141,10 @@ var _dateConverter = new DateConverter();
 
 /** @constructor */
 function Utils() {
-   
+
   /**
-   * Cynara(since tizen 3.0) only support native privilege. 
-   * simply web privilege convert native privilege for checking access. 
+   * Cynara(since tizen 3.0) only support native privilege.
+   * simply web privilege convert native privilege for checking access.
    */
   var privilege = {
     ACCOUNT_READ: 'http://tizen.org/privilege/account.read',
@@ -1385,6 +1456,7 @@ Utils.prototype.type = _type;
 Utils.prototype.converter = _converter;
 Utils.prototype.validator = _validator;
 Utils.prototype.NativeManager = NativeManager;
+Utils.prototype.CommonListenerManager = CommonListenerManager;
 
 var native_ = new NativeManager(extension);
 
@@ -1394,4 +1466,5 @@ Object.freeze(exports);
 Object.freeze(exports.utils);
 Object.freeze(Utils.prototype);
 Object.freeze(NativeManager.prototype);
+Object.freeze(CommonListenerManager.prototype);
 
index e09d3fb..0126865 100644 (file)
@@ -184,7 +184,14 @@ WidgetInstance.prototype.getContent = function() {
   }
 };
 
+
+var widgetToListenerManagerMapping = {};
+
 function Widget(data) {
+  if (!widgetToListenerManagerMapping.hasOwnProperty(data.id)) {
+    widgetToListenerManagerMapping[data.id] = new xwalk.utils.CommonListenerManager(
+      native, 'WidgetStateChangeCallback_' + data.id);
+  }
   Object.defineProperties(this, {
     id: {
       value: data.id,
@@ -210,7 +217,7 @@ function Widget(data) {
       value: data.noDisplay,
       writable: false,
       enumerable: true
-    },
+    }
   });
 };
 
@@ -316,51 +323,17 @@ Widget.prototype.getVariants = function() {
   }
 };
 
-function ListenerManager(native, listenerName) {
-  this.listeners = {};
-  this.nextId = 1;
-  this.nativeSet = false;
-  this.native = native;
-  this.listenerName = listenerName;
-};
-
-ListenerManager.prototype.onListenerCalled = function(msg) {
-  for (var watchId in this.listeners) {
-    if (this.listeners.hasOwnProperty(watchId) ) {
-      this.listeners[watchId](this.native.getResultObject(msg));
-    }
-  }
-};
-
-ListenerManager.prototype.addListener = function(callback) {
-  var id = this.nextId;
-  if (!this.nativeSet) {
-    this.native.addListener(this.listenerName, this.onListenerCalled.bind(this));
-    this.nativeSet = true;
-  }
-  this.listeners[id] = callback;
-  ++this.nextId;
-  return id;
-};
-
-ListenerManager.prototype.removeListener = function(watchId) {
-  if (this.listeners.hasOwnProperty(watchId)) {
-    delete this.listeners[watchId];
-  }
-};
-
-var WIDGET_STATE_CHANGE_LISTENER = 'WidgetStateChangeCallback';
-var widgetStateChangeListener = new ListenerManager(native, WIDGET_STATE_CHANGE_LISTENER);
-
 Widget.prototype.addStateChangeListener = function() {
   var args = validator.validateMethod(arguments, [{
     name : 'eventCallback',
     type : types.FUNCTION,
   }]);
 
-  var result = native.callSync('Widget_addStateChangeListener', {widgetId : this.id});
-  if (native.isFailure(result)) {
-    throw native.getErrorObject(result);
+  if (widgetToListenerManagerMapping[this.id].numberOfListeners == 0) {
+      var result = native.callSync('Widget_addStateChangeListener', {widgetId : this.id});
+      if (native.isFailure(result)) {
+        throw native.getErrorObject(result);
+      }
   }
 
   var func = function(msg) {
@@ -369,7 +342,7 @@ Widget.prototype.addStateChangeListener = function() {
     }
   }.bind(this);
 
-  return widgetStateChangeListener.addListener(func);
+  return widgetToListenerManagerMapping[this.id].addListener(func);
 };
 
 Widget.prototype.removeStateChangeListener = function() {
@@ -378,11 +351,12 @@ Widget.prototype.removeStateChangeListener = function() {
     type : types.LONG,
   }]);
 
-  widgetStateChangeListener.removeListener(args.watchId);
-
-  var result = native.callSync('Widget_removeStateChangeListener', {widgetId : this.id});
-  if (native.isFailure(result)) {
-    throw native.getErrorObject(result);
+  widgetToListenerManagerMapping[this.id].removeListener(args.watchId);
+  if (widgetToListenerManagerMapping[this.id].numberOfListeners == 0) {
+      var result = native.callSync('Widget_removeStateChangeListener', {widgetId : this.id});
+      if (native.isFailure(result)) {
+        throw native.getErrorObject(result);
+      }
   }
 };