Bug 1016785 - Add more structured filtering to SSDP r=wesj
authorMark Finkle <mfinkle@mozilla.com>
Wed, 04 Jun 2014 15:25:07 -0400
changeset 186583 1d19acb6394d08e14e714773ea469651a527fb9b
parent 186582 a9b2fcd44bec187638a881eda547731d268a0f61
child 186584 e160cbe83f39d9cd6bacadd1f0a44ce54f1de0d0
push idunknown
push userunknown
push dateunknown
reviewerswesj
bugs1016785
milestone32.0a1
Bug 1016785 - Add more structured filtering to SSDP r=wesj
mobile/android/base/tests/testSimpleDiscovery.js
mobile/android/chrome/content/CastingApps.js
mobile/android/modules/SimpleServiceDiscovery.jsm
--- a/mobile/android/base/tests/testSimpleDiscovery.js
+++ b/mobile/android/base/tests/testSimpleDiscovery.js
@@ -19,23 +19,32 @@ function discovery_observer(subject, top
   do_check_eq(service.friendlyName, "Pretend Device");
   do_check_eq(service.uuid, "uuid:5ec9ff92-e8b2-4a94-a72c-76b34e6dabb1");
   do_check_eq(service.manufacturer, "Copy Cat Inc.");
   do_check_eq(service.modelName, "Eureka Dongle");
 
   run_next_test();
 };
 
+var testTarget = {
+  target: "test:service",
+  factory: function(service) { /* dummy */  }
+};
+
 add_test(function test_default() {
   do_register_cleanup(function cleanup() {
+    SimpleServiceDiscovery.unregisterTarget(testTarget);
     Services.obs.removeObserver(discovery_observer, "ssdp-service-found");
   });
 
   Services.obs.addObserver(discovery_observer, "ssdp-service-found", false);
 
+  // We need to register a target or processService will ignore us
+  SimpleServiceDiscovery.registerTarget(testTarget);
+
   // Create a pretend service
   let service = {
     location: "http://mochi.test:8888/tests/robocop/simpleservice.xml",
     target: "test:service"
   };
 
   do_print("Force a detailed ping from a pretend service");
 
--- a/mobile/android/chrome/content/CastingApps.js
+++ b/mobile/android/chrome/content/CastingApps.js
@@ -1,26 +1,33 @@
 /* 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";
 
+// Define service targets. We should consider moving these to their respective
+// JSM files, but we left them here to allow for better lazy JSM loading.
+var rokuTarget = {
+  target: "roku:ecp",
+  factory: function(aService) {
+    Cu.import("resource://gre/modules/RokuApp.jsm");
+    return new RokuApp(aService);
+  }
+};
+
 var CastingApps = {
   _castMenuId: -1,
 
   init: function ca_init() {
     if (!this.isEnabled()) {
       return;
     }
 
-    // Register a service target
-    SimpleServiceDiscovery.registerTarget("roku:ecp", function(aService) {
-      Cu.import("resource://gre/modules/RokuApp.jsm");
-      return new RokuApp(aService);
-    });
+    // Register targets
+    SimpleServiceDiscovery.registerTarget(rokuTarget);
 
     // Search for devices continuously every 120 seconds
     SimpleServiceDiscovery.search(120 * 1000);
 
     this._castMenuId = NativeWindow.contextmenus.add(
       Strings.browser.GetStringFromName("contextmenu.castToScreen"),
       this.filterCast,
       this.openExternal.bind(this)
@@ -247,16 +254,20 @@ var CastingApps = {
           CastingApps.openExternal(video, 0, 0);
           return;
         }
       }
     }
   },
 
   _findCastableVideo: function _findCastableVideo(aBrowser) {
+      if (!aBrowser) {
+        return null;
+      }
+
       // Scan for a <video> being actively cast. Also look for a castable <video>
       // on the page.
       let castableVideo = null;
       let videos = aBrowser.contentDocument.querySelectorAll("video");
       for (let video of videos) {
         let unwrappedVideo = XPCNativeWrapper.unwrap(video);
         if (unwrappedVideo.mozIsCasting) {
           // This <video> is cast-active. Break out of loop.
@@ -446,9 +457,8 @@ var CastingApps = {
     }
 
     let status = aRemoteMedia.status;
     if (status == "completed") {
       this.closeExternal();
     }
   }
 };
-
--- a/mobile/android/modules/SimpleServiceDiscovery.jsm
+++ b/mobile/android/modules/SimpleServiceDiscovery.jsm
@@ -220,20 +220,38 @@ var SimpleServiceDiscovery = {
         if (service.lastPing != this._searchTimestamp) {
           Services.obs.notifyObservers(null, EVENT_SERVICE_LOST, service.uuid);
           this._services.delete(service.uuid);
         }
       }
     }
   },
 
-  registerTarget: function registerTarget(aTarget, aAppFactory) {
+  registerTarget: function registerTarget(aTarget) {
+    // We must have "target" and "factory" defined
+    if (!("target" in aTarget) || !("factory" in aTarget)) {
+      // Fatal for registration
+      throw "Registration requires a target and a location";
+    }
+
     // Only add if we don't already know about this target
-    if (!this._targets.has(aTarget)) {
-      this._targets.set(aTarget, { target: aTarget, factory: aAppFactory });
+    if (!this._targets.has(aTarget.target)) {
+      this._targets.set(aTarget.target, aTarget);
+    }
+  },
+
+  unregisterTarget: function unregisterTarget(aTarget) {
+    // We must have "target" and "factory" defined
+    if (!("target" in aTarget) || !("factory" in aTarget)) {
+      return;
+    }
+
+    // Only remove if we know about this target
+    if (this._targets.has(aTarget.target)) {
+      this._targets.delete(aTarget.target);
     }
   },
 
   findAppForService: function findAppForService(aService) {
     if (!aService || !aService.target) {
       return null;
     }
 
@@ -255,16 +273,39 @@ var SimpleServiceDiscovery = {
   get services() {
     let array = [];
     for (let [key, service] of this._services) {
       array.push(service);
     }
     return array;
   },
 
+  // Returns false if the service does not match the target's filters
+  _filterService: function _filterService(aService) {
+    let target = this._targets.get(aService.target);
+    if (!target) {
+      return false;
+    }
+
+    // If we have no filter, everything passes
+    if (!("filters" in target)) {
+      return true;
+    }
+
+    // If any filter fails, the service fails
+    let filters = target.filters;
+    for (let filter in filters) {
+      if (filter in aService && aService[filter] != filters[filter]) {
+        return false;
+      }
+    }
+
+    return true;
+  },
+
   _processService: function _processService(aService) {
     // Use the REST api to request more information about this service
     let xhr = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"].createInstance(Ci.nsIXMLHttpRequest);
     xhr.open("GET", aService.location, true);
     xhr.channel.loadFlags |= Ci.nsIRequest.INHIBIT_CACHING;
     xhr.overrideMimeType("text/xml");
 
     xhr.addEventListener("load", (function() {
@@ -273,16 +314,21 @@ var SimpleServiceDiscovery = {
         aService.appsURL = xhr.getResponseHeader("Application-URL");
         if (aService.appsURL && !aService.appsURL.endsWith("/"))
           aService.appsURL += "/";
         aService.friendlyName = doc.querySelector("friendlyName").textContent;
         aService.uuid = doc.querySelector("UDN").textContent;
         aService.manufacturer = doc.querySelector("manufacturer").textContent;
         aService.modelName = doc.querySelector("modelName").textContent;
 
+        // Filter out services that do not match the target filter
+        if (!this._filterService(aService)) {
+          return;
+        }
+
         // Only add and notify if we don't already know about this service
         if (!this._services.has(aService.uuid)) {
           this._services.set(aService.uuid, aService);
           Services.obs.notifyObservers(null, EVENT_SERVICE_FOUND, aService.uuid);
         }
 
         // Make sure we remember this service is not stale
         this._services.get(aService.uuid).lastPing = this._searchTimestamp;