Bug 774521 - Desktop Web Runtime UI implementation for mozGetUserMedia (Camera API). r=myk
authorMarco Castelluccio <mar.castelluccio@studenti.unina.it>
Fri, 11 Oct 2013 11:06:21 -0400
changeset 150511 562abc4f545e9db5cd855f7f4c5c27053688ad54
parent 150510 f75c25e53e53db061a8143e6cc8d66ea125afa26
child 150512 37a6eecf1aef396575fddaa98763cf169c87ae9c
push id3001
push userryanvm@gmail.com
push dateFri, 11 Oct 2013 21:03:10 +0000
treeherderfx-team@9ab188de8245 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmyk
bugs774521
milestone27.0a1
Bug 774521 - Desktop Web Runtime UI implementation for mozGetUserMedia (Camera API). r=myk
browser/installer/package-manifest.in
webapprt/Startup.jsm
webapprt/WebRTCHandler.jsm
webapprt/content/getUserMediaDialog.js
webapprt/content/getUserMediaDialog.xul
webapprt/jar.mn
webapprt/locales/en-US/webapprt/getUserMediaDialog.dtd
webapprt/locales/jar.mn
webapprt/moz.build
webapprt/test/chrome/browser_getUserMedia.js
webapprt/test/chrome/getUserMedia.html
webapprt/test/chrome/getUserMedia.webapp
webapprt/test/chrome/getUserMedia.webapp^headers^
webapprt/test/chrome/webapprt.ini
--- a/browser/installer/package-manifest.in
+++ b/browser/installer/package-manifest.in
@@ -781,16 +781,17 @@ bin/libfreebl_32int64_3.so
 @BINPATH@/webapprt/components/DirectoryProvider.js
 @BINPATH@/webapprt/components/PaymentUIGlue.js
 @BINPATH@/webapprt/components/components.manifest
 @BINPATH@/webapprt/defaults/preferences/prefs.js
 @BINPATH@/webapprt/modules/Startup.jsm
 @BINPATH@/webapprt/modules/WebappRT.jsm
 @BINPATH@/webapprt/modules/WebappsHandler.jsm
 @BINPATH@/webapprt/modules/RemoteDebugger.jsm
+@BINPATH@/webapprt/modules/WebRTCHandler.jsm
 #endif
 
 #ifdef MOZ_METRO
 @BINPATH@/components/MetroUIUtils.js
 @BINPATH@/components/MetroUIUtils.manifest
 [metro]
 ; gre resources
 @BINPATH@/CommandExecuteHandler@BIN_SUFFIX@
--- a/webapprt/Startup.jsm
+++ b/webapprt/Startup.jsm
@@ -20,16 +20,17 @@ Cu.import("resource://gre/modules/Permis
 Cu.import('resource://gre/modules/Payment.jsm');
 Cu.import("resource://gre/modules/Task.jsm");
 Cu.import("resource://gre/modules/Promise.jsm");
 Cu.import("resource://gre/modules/osfile.jsm");
 
 // Initialize window-independent handling of webapps- notifications.
 Cu.import("resource://webapprt/modules/WebappsHandler.jsm");
 Cu.import("resource://webapprt/modules/WebappRT.jsm");
+Cu.import("resource://webapprt/modules/WebRTCHandler.jsm");
 
 const PROFILE_DIR = OS.Constants.Path.profileDir;
 
 function isFirstRunOrUpdate() {
   let savedBuildID = null;
   try {
     savedBuildID = Services.prefs.getCharPref("webapprt.buildID");
   } catch (e) {}
new file mode 100644
--- /dev/null
+++ b/webapprt/WebRTCHandler.jsm
@@ -0,0 +1,103 @@
+/* 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 = [];
+
+let Cc = Components.classes;
+let Ci = Components.interfaces;
+let Cu = Components.utils;
+
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+function handleRequest(aSubject, aTopic, aData) {
+  let { windowID, callID } = aSubject;
+  let constraints = aSubject.getConstraints();
+  let contentWindow = Services.wm.getOuterWindowWithId(windowID);
+
+  contentWindow.navigator.mozGetUserMediaDevices(
+    constraints,
+    function (devices) {
+      prompt(contentWindow, callID, constraints.audio,
+             constraints.video || constraints.picture,
+             devices);
+    },
+    function (error) {
+      denyRequest(callID, error);
+    });
+}
+
+function prompt(aWindow, aCallID, aAudioRequested, aVideoRequested, aDevices) {
+  let audioDevices = [];
+  let videoDevices = [];
+  for (let device of aDevices) {
+    device = device.QueryInterface(Ci.nsIMediaDevice);
+    switch (device.type) {
+      case "audio":
+        if (aAudioRequested) {
+          audioDevices.push(device);
+        }
+        break;
+
+      case "video":
+        if (aVideoRequested) {
+          videoDevices.push(device);
+        }
+        break;
+    }
+  }
+
+  if (audioDevices.length == 0 && videoDevices.length == 0) {
+    denyRequest(aCallID);
+    return;
+  }
+
+  let params = {
+                 videoDevices: videoDevices,
+                 audioDevices: audioDevices,
+                 out: null
+               };
+  aWindow.openDialog("chrome://webapprt/content/getUserMediaDialog.xul", "",
+                     "chrome, dialog, modal", params).focus();
+
+  if (!params.out) {
+    denyRequest(aCallID);
+    return;
+  }
+
+  let allowedDevices = Cc["@mozilla.org/supports-array;1"].
+                       createInstance(Ci.nsISupportsArray);
+  let videoIndex = params.out.video;
+  let audioIndex = params.out.audio;
+
+  if (videoIndex != -1) {
+    allowedDevices.AppendElement(videoDevices[videoIndex]);
+  }
+
+  if (audioIndex != -1) {
+    allowedDevices.AppendElement(audioDevices[audioIndex]);
+  }
+
+  if (allowedDevices.Count()) {
+    Services.obs.notifyObservers(allowedDevices, "getUserMedia:response:allow",
+                                 aCallID);
+  } else {
+    denyRequest(aCallID);
+  }
+}
+
+function denyRequest(aCallID, aError) {
+  let msg = null;
+  if (aError) {
+    msg = Cc["@mozilla.org/supports-string;1"].
+          createInstance(Ci.nsISupportsString);
+    msg.data = aError;
+  }
+
+  Services.obs.notifyObservers(msg, "getUserMedia:response:deny", aCallID);
+}
+
+Services.obs.addObserver(handleRequest, "getUserMedia:request", false);
new file mode 100644
--- /dev/null
+++ b/webapprt/content/getUserMediaDialog.js
@@ -0,0 +1,38 @@
+/* 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/. */
+
+function onOK() {
+  window.arguments[0].out = {
+    video: document.getElementById("video").selectedItem.value,
+    audio: document.getElementById("audio").selectedItem.value
+  };
+
+  return true;
+}
+
+function onLoad() {
+  let videoDevices = window.arguments[0].videoDevices;
+  if (videoDevices.length) {
+    let videoMenu = document.getElementById("video");
+    for (let i = 0; i < videoDevices.length; i++) {
+      videoMenu.appendItem(videoDevices[i].name, i);
+    }
+    videoMenu.selectedIndex = 1;
+  } else {
+    document.getElementById("videoGroup").hidden = true;
+  }
+
+  let audioDevices = window.arguments[0].audioDevices;
+  if (audioDevices.length) {
+    let audioMenu = document.getElementById("audio");
+    for (let i = 0; i < audioDevices.length; i++) {
+      audioMenu.appendItem(audioDevices[i].name, i);
+    }
+    audioMenu.selectedIndex = 1;
+  } else {
+    document.getElementById("audioGroup").hidden = true;
+  }
+
+  window.sizeToContent();
+}
new file mode 100644
--- /dev/null
+++ b/webapprt/content/getUserMediaDialog.xul
@@ -0,0 +1,45 @@
+<?xml version="1.0"?>
+
+<!-- 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/.  -->
+
+<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
+
+<!DOCTYPE dialog [
+<!ENTITY % gum-askDTD SYSTEM "chrome://webapprt/locale/getUserMediaDialog.dtd">
+%gum-askDTD;
+]>
+
+<dialog id="getUserMediaDialog" title="&getUserMediaDialog.title;"
+        xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+        buttons="accept,cancel"
+        buttonlabelaccept="&getUserMediaDialog.buttonlabelaccept;"
+        buttonaccesskeyaccept="&getUserMediaDialog.buttonaccesskeyaccept;"
+        onload="onLoad()"
+        ondialogaccept="return onOK()"
+        buttonlabelcancel="&getUserMediaDialog.buttonlabelcancel;"
+        buttonaccesskeycancel="&getUserMediaDialog.buttonaccesskeycancel;">
+
+<script type="application/javascript"
+        src="chrome://webapprt/content/getUserMediaDialog.js"/>
+
+  <groupbox id="videoGroup" flex="1">
+    <caption label="&getUserMediaDialog.video.label;"/>
+    <menulist id="video">
+      <menupopup>
+        <menuitem label="&getUserMediaDialog.video.noVideo;" value="-1"/>
+      </menupopup>
+    </menulist>
+  </groupbox>
+
+  <groupbox id="audioGroup" flex="1">
+    <caption label="&getUserMediaDialog.audio.label;"/>
+    <menulist id="audio">
+      <menupopup>
+        <menuitem label="&getUserMediaDialog.audio.noAudio;" value="-1"/>
+      </menupopup>
+    </menulist>
+  </groupbox>
+
+</dialog>
--- a/webapprt/jar.mn
+++ b/webapprt/jar.mn
@@ -1,12 +1,14 @@
 # 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/.
 
 webapprt.jar:
 % content webapprt %content/
 * content/webapp.js                     (content/webapp.js)
 * content/webapp.xul                    (content/webapp.xul)
+  content/getUserMediaDialog.xul        (content/getUserMediaDialog.xul)
+  content/getUserMediaDialog.js         (content/getUserMediaDialog.js)
   content/mochitest-shared.js           (content/mochitest-shared.js)
   content/mochitest.js                  (content/mochitest.js)
   content/mochitest.xul                 (content/mochitest.xul)
   content/dbg-webapp-actors.js          (content/dbg-webapp-actors.js)
new file mode 100644
--- /dev/null
+++ b/webapprt/locales/en-US/webapprt/getUserMediaDialog.dtd
@@ -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/.  -->
+
+<!-- LOCALIZATION NOTE: These are localized strings for the getUserMedia dialog
+   - to ask permissions in the webapp runtime. -->
+
+<!ENTITY getUserMediaDialog.title                 "Media Sharing">
+<!ENTITY getUserMediaDialog.buttonlabelaccept     "Share">
+<!ENTITY getUserMediaDialog.buttonaccesskeyaccept "S">
+<!ENTITY getUserMediaDialog.buttonlabelcancel     "Cancel">
+<!ENTITY getUserMediaDialog.buttonaccesskeycancel "n">
+
+<!ENTITY getUserMediaDialog.video.label           "Select camera">
+<!ENTITY getUserMediaDialog.video.noVideo         "No video">
+<!ENTITY getUserMediaDialog.audio.label           "Select microphone">
+<!ENTITY getUserMediaDialog.audio.noAudio         "No audio">
--- a/webapprt/locales/jar.mn
+++ b/webapprt/locales/jar.mn
@@ -2,10 +2,11 @@
 # 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/.
 
 @AB_CD@.jar:
 % locale webapprt @AB_CD@ %locale/webapprt/
     locale/webapprt/webapp.dtd                     (%webapprt/webapp.dtd)
     locale/webapprt/webapp.properties              (%webapprt/webapp.properties)
+    locale/webapprt/getUserMediaDialog.dtd         (%webapprt/getUserMediaDialog.dtd)
 
 % locale branding @AB_CD@ resource://webappbranding/
--- a/webapprt/moz.build
+++ b/webapprt/moz.build
@@ -19,15 +19,16 @@ EXTRA_COMPONENTS += [
     'DirectoryProvider.js',
     'PaymentUIGlue.js',
     'components.manifest',
 ]
 
 EXTRA_JS_MODULES += [
     'RemoteDebugger.jsm',
     'Startup.jsm',
+    'WebRTCHandler.jsm',
     'WebappRT.jsm',
     'WebappsHandler.jsm',
 ]
 
 MOCHITEST_WEBAPPRT_CHROME_MANIFESTS += ['test/chrome/webapprt.ini']
 MOCHITEST_MANIFESTS += ['test/content/mochitest.ini']
 
new file mode 100644
--- /dev/null
+++ b/webapprt/test/chrome/browser_getUserMedia.js
@@ -0,0 +1,38 @@
+Cu.import("resource://gre/modules/Services.jsm");
+
+function test() {
+  waitForExplicitFinish();
+
+  let openedWindows = 0;
+
+  let winObserver = function(win, topic) {
+    if (topic == "domwindowopened") {
+      win.addEventListener("load", function onLoadWindow() {
+        win.removeEventListener("load", onLoadWindow, false);
+        openedWindows++;
+        if (openedWindows == 2) {
+          win.close();
+        }
+      }, false);
+    }
+  }
+
+  Services.ww.registerNotification(winObserver);
+
+  let mutObserver = null;
+
+  loadWebapp("getUserMedia.webapp", undefined, function onLoad() {
+    let msg = gAppBrowser.contentDocument.getElementById("msg");
+    mutObserver = new MutationObserver(function(mutations) {
+      is(msg.textContent, "PERMISSION_DENIED", "getUserMedia permission denied.");
+      is(openedWindows, 2, "Prompt shown.");
+      finish();
+    });
+    mutObserver.observe(msg, { childList: true });
+  });
+
+  registerCleanupFunction(function() {
+    Services.ww.unregisterNotification(winObserver);
+    mutObserver.disconnect();
+  });
+}
new file mode 100644
--- /dev/null
+++ b/webapprt/test/chrome/getUserMedia.html
@@ -0,0 +1,21 @@
+<!DOCTYPE HTML>
+<html>
+  <head>
+    <title>getUserMedia Test App</title>
+    <meta charset="utf-8">
+  </head>
+  <body>
+    <span id="msg"></span>
+    <script>
+      navigator.mozGetUserMedia({ video: true, audio: true },
+        function(localMediaStream) {
+          document.getElementById("msg").textContent = window.URL.createObjectURL(localMediaStream);
+        },
+
+        function(err) {
+          document.getElementById("msg").textContent = err;
+        });
+    </script>
+    <h1>getUserMedia Test App</h1>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/webapprt/test/chrome/getUserMedia.webapp
@@ -0,0 +1,5 @@
+{
+  "name": "getUserMedia Test App",
+  "description": "an app for testing getUserMedia",
+  "launch_path": "/webapprtChrome/webapprt/test/chrome/getUserMedia.html"
+}
new file mode 100644
--- /dev/null
+++ b/webapprt/test/chrome/getUserMedia.webapp^headers^
@@ -0,0 +1,1 @@
+Content-Type: application/x-web-app-manifest+json
--- a/webapprt/test/chrome/webapprt.ini
+++ b/webapprt/test/chrome/webapprt.ini
@@ -21,18 +21,23 @@ support-files =
   geolocation-prompt-noperm.html
   debugger.webapp
   debugger.webapp^headers^
   debugger.html
   mozpay.webapp
   mozpay.webapp^headers^
   mozpay.html
   mozpay-success.html
+  getUserMedia.webapp
+  getUserMedia.webapp^headers^
+  getUserMedia.html
 
 
 [browser_sample.js]
 [browser_window-title.js]
 [browser_webperm.js]
 [browser_noperm.js]
 [browser_geolocation-prompt-perm.js]
 [browser_geolocation-prompt-noperm.js]
 [browser_debugger.js]
 [browser_mozpay.js]
+[browser_getUserMedia.js]
+skip-if = true