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 id26896
push userkwierso@gmail.com
push dateThu, 05 Jun 2014 00:44:23 +0000
treeherdermozilla-central@882826199076 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerswesj
bugs1016785
milestone32.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 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;