Bug 816730 - [Activities] Filters do not work anymore. r=mounir, sr=sicking
authorAndrea Marchesini <amarchesini@mozilla.com>
Thu, 27 Dec 2012 11:12:06 -0500
changeset 126212 f9c5b23292cadd1b2c2c02c7e3c4a4a6ce566741
parent 126211 6874adaa8fb037581549ab36083adc591d5b7771
child 126213 23549b4dffb1791a5509ce0b1d927720f08ebb46
child 126222 58edc998ee4f9de4bae86eeccec77ea5ef3ef95a
push id2151
push userlsblakk@mozilla.com
push dateTue, 19 Feb 2013 18:06:57 +0000
treeherdermozilla-beta@4952e88741ec [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmounir, sicking
bugs816730
milestone20.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 816730 - [Activities] Filters do not work anymore. r=mounir, sr=sicking
dom/activities/Makefile.in
dom/activities/src/ActivitiesService.jsm
dom/activities/src/ActivitiesServiceFilter.jsm
dom/activities/src/Makefile.in
dom/activities/tests/Makefile.in
dom/activities/tests/unit/test_activityFilters.js
dom/activities/tests/unit/xpcshell.ini
testing/xpcshell/xpcshell.ini
--- a/dom/activities/Makefile.in
+++ b/dom/activities/Makefile.in
@@ -1,14 +1,17 @@
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this file,
 # You can obtain one at http://mozilla.org/MPL/2.0/.
 
 DEPTH     = @DEPTH@
 topsrcdir = @top_srcdir@
 srcdir    = @srcdir@
 VPATH     = @srcdir@
+relativesrcdir = @relativesrcdir@
 
 include $(DEPTH)/config/autoconf.mk
 
 PARALLEL_DIRS = interfaces src
 
+TEST_DIRS += tests
+
 include $(topsrcdir)/config/rules.mk
--- a/dom/activities/src/ActivitiesService.jsm
+++ b/dom/activities/src/ActivitiesService.jsm
@@ -6,16 +6,17 @@
 
 const Cu = Components.utils;
 const Cc = Components.classes;
 const Ci = Components.interfaces;
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/IndexedDBHelper.jsm");
+Cu.import("resource://gre/modules/ActivitiesServiceFilter.jsm");
 
 XPCOMUtils.defineLazyServiceGetter(this, "ppmm",
                                    "@mozilla.org/parentprocessmessagemanager;1",
                                    "nsIMessageBroadcaster");
 
 this.EXPORTED_SYMBOLS = [];
 
 let idbGlobal = this;
@@ -253,49 +254,18 @@ let Activities = {
     };
 
     let errorCb = function errorCb(aError) {
       // Something unexpected happened. Should we send an error back?
       debug("Error in startActivity: " + aError + "\n");
     };
 
     let matchFunc = function matchFunc(aResult) {
-
-      function matchFuncValue(aValue, aFilter) {
-        // Bug 805822 - Regexp support for MozActivity
-
-        let values = Array.isArray(aValue) ? aValue : [aValue];
-        let filters = Array.isArray(aFilter) ? aFilter : [aFilter];
-
-        // At least 1 value must match.
-        let ret = false;
-        values.forEach(function(value) {
-          if (filters.indexOf(value) != -1) {
-            ret = true;
-          }
-        });
-
-        return ret;
-      }
-
-      // For any incoming property.
-      for (let prop in aMsg.options.data) {
-
-        // If this is unknown for the app, let's continue.
-        if (!(prop in aResult.description.filters)) {
-          continue;
-        }
-
-        // Otherwise, let's check the value against the filter.
-        if (!matchFuncValue(aMsg.options.data[prop], aResult.description.filters[prop])) {
-          return false;
-        }
-      }
-
-      return true;
+      return ActivitiesServiceFilter.match(aMsg.options.data,
+                                           aResult.description.filters);
     };
 
     this.db.find(aMsg, successCb, errorCb, matchFunc);
   },
 
   receiveMessage: function activities_receiveMessage(aMessage) {
     let mm = aMessage.target;
     let msg = aMessage.json;
new file mode 100644
--- /dev/null
+++ b/dom/activities/src/ActivitiesServiceFilter.jsm
@@ -0,0 +1,127 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict"
+
+this.EXPORTED_SYMBOLS = ['ActivitiesServiceFilter'];
+
+this.ActivitiesServiceFilter = {
+  match: function(aValues, aFilters) {
+
+    function matchValue(aValue, aFilter, aFilterObj) {
+      if (aFilter !== null) {
+        // Custom functions for the different types.
+        switch (typeof(aFilter)) {
+        case 'boolean':
+          return aValue === aFilter;
+
+        case 'number':
+          return Number(aValue) === aFilter;
+
+        case 'string':
+          return String(aValue) === aFilter;
+
+        default: // not supported
+          return false;
+        }
+      }
+
+      // Regexp.
+      if (('regexp' in aFilterObj)) {
+        var regexp = String(aFilterObj.regexp);
+
+        if (regexp[0] != "/")
+          return false;
+
+        var pos = regexp.lastIndexOf("/");
+        if (pos == 0)
+          return false;
+
+        var re = new RegExp(regexp.substring(1, pos), regexp.substr(pos + 1));
+        return re.test(aValue);
+      }
+
+      // Validation of the min/Max.
+      if (('min' in aFilterObj) || ('max' in aFilterObj)) {
+        // Min value.
+        if (('min' in aFilterObj) &&
+            aFilterObj.min > aValue) {
+          return false;
+        }
+
+        // Max value.
+        if (('max' in aFilterObj) &&
+            aFilterObj.max < aValue) {
+          return false;
+        }
+      }
+
+      return true;
+    }
+
+    // this function returns true if the value matches with the filter object
+    function matchObject(aValue, aFilterObj) {
+
+      // Let's consider anything an array.
+      let filters = ('value' in aFilterObj)
+                      ? (Array.isArray(aFilterObj.value)
+                          ? aFilterObj.value
+                          : [aFilterObj.value])
+                      : [ null ];
+      let values  = Array.isArray(aValue) ? aValue : [aValue];
+
+      for (var filterId = 0; filterId < filters.length; ++filterId) {
+        for (var valueId = 0; valueId < values.length; ++valueId) {
+          if (matchValue(values[valueId], filters[filterId], aFilterObj)) {
+            return true;
+          }
+        }
+      }
+
+      return false;
+    }
+
+    // Creation of a filter map useful to know what has been
+    // matched and what is not.
+    let filtersMap = {}
+    for (let filter in aFilters) {
+      // Convert this filter in an object if needed
+      let filterObj = aFilters[filter];
+
+      if (Array.isArray(filterObj) || typeof(filterObj) !== 'object') {
+        filterObj = {
+          required: false,
+          value: filterObj
+        }
+      }
+
+      filtersMap[filter] = { filter: filterObj,
+                             found:  false };
+    }
+
+    // For any incoming property.
+    for (let prop in aValues) {
+      // If this is unknown for the app, let's continue.
+      if (!(prop in filtersMap)) {
+        continue;
+      }
+
+      // Otherwise, let's check the value against the filter.
+      if (!matchObject(aValues[prop], filtersMap[prop].filter)) {
+        return false;
+      }
+
+      filtersMap[prop].found = true;
+    }
+
+    // Required filters:
+    for (let filter in filtersMap) {
+      if (filtersMap[filter].filter.required && !filtersMap[filter].found) {
+        return false;
+      }
+    }
+
+    return true;
+  }
+}
--- a/dom/activities/src/Makefile.in
+++ b/dom/activities/src/Makefile.in
@@ -31,12 +31,13 @@ EXTRA_COMPONENTS = \
   ActivityProxy.js \
   ActivityRequestHandler.js \
   ActivityWrapper.js \
   Activities.manifest \
   $(NULL)
 
 EXTRA_JS_MODULES =   \
   ActivitiesService.jsm \
+  ActivitiesServiceFilter.jsm \
   $(NULL)
 
 include $(topsrcdir)/config/config.mk
 include $(topsrcdir)/config/rules.mk
new file mode 100644
--- /dev/null
+++ b/dom/activities/tests/Makefile.in
@@ -0,0 +1,17 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+DEPTH           = @DEPTH@
+topsrcdir       = @top_srcdir@
+srcdir          = @srcdir@
+VPATH           = @srcdir@
+relativesrcdir  = @relativesrcdir@
+
+FAIL_ON_WARNINGS := 1
+
+include $(DEPTH)/config/autoconf.mk
+
+XPCSHELL_TESTS = unit
+
+include $(topsrcdir)/config/rules.mk
new file mode 100644
--- /dev/null
+++ b/dom/activities/tests/unit/test_activityFilters.js
@@ -0,0 +1,151 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+function run_test() {
+  Components.utils.import("resource:///modules/ActivitiesServiceFilter.jsm")
+
+  do_check_true(!!ActivitiesServiceFilter);
+
+  // No requests, no filters:
+  do_check_true(ActivitiesServiceFilter.match(null, null));
+  do_check_true(ActivitiesServiceFilter.match({}, {}));
+
+  // No filters:
+  do_check_true(ActivitiesServiceFilter.match({foobar: 42}, null));
+
+  // Empty request:
+  do_check_true(ActivitiesServiceFilter.match({}, {a: 'foobar', b: [1, 2, 3], c: 42}));
+
+
+  // Simple match:
+  do_check_true(ActivitiesServiceFilter.match({a: 'foobar'},
+                                              {a: 'foobar', b: [1, 2, 3], c: 42}));
+  do_check_false(ActivitiesServiceFilter.match({a: 'foobar', b: 2, c: true},
+                                               {a: 'foobar', b: [1, 2, 3], c: 42}));
+  do_check_true(ActivitiesServiceFilter.match({a: 'foobar', b: 2, c: 42},
+                                              {a: 'foobar', b: [1, 2, 3], c: 42}));
+  do_check_false(ActivitiesServiceFilter.match({a: 'foobar2'},
+                                               {a: 'foobar', b: [1, 2, 3], c: 42}));
+
+  // Simple match in array:
+  do_check_true(ActivitiesServiceFilter.match({b: 2},
+                                              {a: 'foobar', b: [1, 2, 3], c: 42}));
+  do_check_false(ActivitiesServiceFilter.match({b: 4},
+                                               {a: 'foobar', b: [1, 2, 3], c: 42}));
+  do_check_true(ActivitiesServiceFilter.match({b: [2, 4]},
+                                              {a: 'foobar', b: [1, 2, 3], c: 42}));
+  do_check_false(ActivitiesServiceFilter.match({b: [4, 5]},
+                                               {a: 'foobar', b: [1, 2, 3], c: 42}));
+  do_check_false(ActivitiesServiceFilter.match({a: [4, 'foobar2']},
+                                               {a: 'foobar', b: [1, 2, 3], c: 42}));
+  do_check_true(ActivitiesServiceFilter.match({a: [4, 'foobar']},
+                                              {a: 'foobar', b: [1, 2, 3], c: 42}));
+  do_check_true(ActivitiesServiceFilter.match({a: ['foo', 'bar']},
+                                              {a: 'foo'}));
+
+  // Unknown property
+  do_check_true(ActivitiesServiceFilter.match({k: 4},
+                                              {a: 'foobar', b: [1, 2, 3], c: 42}));
+  do_check_true(ActivitiesServiceFilter.match({k: [1,2,3,4]},
+                                              {a: 'foobar', b: [1, 2, 3], c: 42}));
+
+  // Required/non required
+  do_check_false(ActivitiesServiceFilter.match({},
+                                               {a: { required: true, value: 'foobar'}}));
+  do_check_true(ActivitiesServiceFilter.match({a: 'foobar'},
+                                              {a: { required: true, value: 'foobar'}}));
+  do_check_false(ActivitiesServiceFilter.match({a: 'foobar2'},
+                                               {a: { required: true, value: 'foobar'}}));
+  do_check_false(ActivitiesServiceFilter.match({a: 'foobar2'},
+                                               {a: { required: true, value: ['a', 'b', 'foobar']}}));
+  do_check_true(ActivitiesServiceFilter.match({a: 'foobar'},
+                                              {a: { required: true, value: ['a', 'b', 'foobar']}}));
+  do_check_true(ActivitiesServiceFilter.match({a: ['k', 'z', 'foobar']},
+                                              {a: { required: true, value: ['a', 'b', 'foobar']}}));
+  do_check_false(ActivitiesServiceFilter.match({a: ['k', 'z', 'foobar2']},
+                                               {a: { required: true, value: ['a', 'b', 'foobar']}}));
+
+  // Empty values
+  do_check_true(ActivitiesServiceFilter.match({a: 42},
+                                              {a: { required: true}}));
+  do_check_false(ActivitiesServiceFilter.match({},
+                                               {a: { required: true}}));
+
+  // Boolean
+  do_check_true(ActivitiesServiceFilter.match({a: false},
+                                              {a: { required: true, value: false}}));
+  do_check_false(ActivitiesServiceFilter.match({a: true},
+                                               {a: { required: true, value: false}}));
+  do_check_true(ActivitiesServiceFilter.match({a: [false, true]},
+                                              {a: { required: true, value: false}}));
+  do_check_true(ActivitiesServiceFilter.match({a: [false, true]},
+                                              {a: { required: true, value: [false,true]}}));
+
+  // Number
+  do_check_true(ActivitiesServiceFilter.match({a: 42},
+                                              {a: { required: true, value: 42}}));
+  do_check_false(ActivitiesServiceFilter.match({a: 2},
+                                               {a: { required: true, value: 42}}));
+  do_check_true(ActivitiesServiceFilter.match({a: 2},
+                                              {a: { required: true, min: 1}}));
+  do_check_true(ActivitiesServiceFilter.match({a: 2},
+                                              {a: { required: true, min: 2}}));
+  do_check_false(ActivitiesServiceFilter.match({a: 2},
+                                               {a: { required: true, min: 3}}));
+  do_check_false(ActivitiesServiceFilter.match({a: 2},
+                                               {a: { required: true, max: 1}}));
+  do_check_true(ActivitiesServiceFilter.match({a: 2},
+                                              {a: { required: true, max: 2}}));
+  do_check_true(ActivitiesServiceFilter.match({a: 2},
+                                              {a: { required: true, max: 3}}));
+  do_check_false(ActivitiesServiceFilter.match({a: 2},
+                                               {a: { required: true, min: 1, max: 1}}));
+  do_check_true(ActivitiesServiceFilter.match({a: 2},
+                                              {a: { required: true, min: 1, max: 2}}));
+  do_check_true(ActivitiesServiceFilter.match({a: 2},
+                                              {a: { required: true, min: 2, max: 2}}));
+  do_check_false(ActivitiesServiceFilter.match({a: 2},
+                                               {a: { required: true, value: 'foo'}}));
+  do_check_false(ActivitiesServiceFilter.match({a: 2},
+                                               {a: { required: true, min: 100, max: 0}}));
+  do_check_true(ActivitiesServiceFilter.match({a: 2},
+                                              {a: { required: true, min: 'a', max: 'b'}}));
+  do_check_false(ActivitiesServiceFilter.match({a: 2},
+                                               {a: { required: true, min: 10, max: 1}}));
+
+  // String
+  do_check_true(ActivitiesServiceFilter.match({a: 'foo'},
+                                              {a: { required: true, value: 'foo'}}));
+  do_check_false(ActivitiesServiceFilter.match({a: 'foo2'},
+                                               {a: { required: true, value: 'foo'}}));
+
+  // Number VS string
+  do_check_true(ActivitiesServiceFilter.match({a: '42'},
+                                              {a: { required: true, value: 42}}));
+  do_check_true(ActivitiesServiceFilter.match({a: 42},
+                                              {a: { required: true, value: '42'}}));
+  do_check_true(ActivitiesServiceFilter.match({a: '-42e+12'},
+                                              {a: { required: true, value: -42e+12}}));
+  do_check_true(ActivitiesServiceFilter.match({a: 42},
+                                              {a: { required: true, min: '1', max: '50'}}));
+  do_check_true(ActivitiesServiceFilter.match({a: '42'},
+                                              {a: 42 }));
+  do_check_true(ActivitiesServiceFilter.match({a: 42},
+                                              {a: '42' }));
+  do_check_false(ActivitiesServiceFilter.match({a: 42},
+                                               {a: { min: '44' }}));
+  do_check_false(ActivitiesServiceFilter.match({a: 42},
+                                               {a: { max: '0' }}));
+
+  // String + RegExp
+  do_check_true(ActivitiesServiceFilter.match({a: 'foobar'},
+                                              {a: { required: true, regexp: '/^foobar/'}}));
+  do_check_false(ActivitiesServiceFilter.match({a: 'aafoobar'},
+                                               {a: { required: true, regexp: '/^foobar/'}}));
+  do_check_true(ActivitiesServiceFilter.match({a: 'aaFOOsdsad'},
+                                              {a: { required: true, regexp: '/foo/i'}}));
+  do_check_true(ActivitiesServiceFilter.match({a: 'aafoobarasdsad'},
+                                              {a: { required: true, regexp: '/foo/'}}));
+  do_check_false(ActivitiesServiceFilter.match({a: 'aaFOOsdsad'},
+                                               {a: { required: true, regexp: '/foo/'}}));
+}
new file mode 100644
--- /dev/null
+++ b/dom/activities/tests/unit/xpcshell.ini
@@ -0,0 +1,5 @@
+[DEFAULT]
+head =
+tail =
+
+[test_activityFilters.js]
--- a/testing/xpcshell/xpcshell.ini
+++ b/testing/xpcshell/xpcshell.ini
@@ -4,16 +4,17 @@
 
 [include:chrome/test/unit/xpcshell.ini]
 [include:intl/locale/tests/unit/xpcshell.ini]
 [include:netwerk/cookie/test/unit/xpcshell.ini]
 [include:modules/libjar/zipwriter/test/unit/xpcshell.ini]
 [include:uriloader/exthandler/tests/unit/xpcshell.ini]
 [include:parser/xml/test/unit/xpcshell.ini]
 [include:image/test/unit/xpcshell.ini]
+[include:dom/activities/tests/unit/xpcshell.ini]
 [include:dom/encoding/test/unit/xpcshell.ini]
 [include:dom/plugins/test/unit/xpcshell.ini]
 [include:dom/sms/tests/xpcshell.ini]
 [include:dom/mms/tests/xpcshell.ini]
 [include:dom/network/tests/unit/xpcshell.ini]
 [include:dom/network/tests/unit_ipc/xpcshell.ini]
 [include:dom/network/tests/unit_stats/xpcshell.ini]
 [include:dom/permission/tests/unit/xpcshell.ini]