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 164323 562abc4f545e9db5cd855f7f4c5c27053688ad54
parent 164322 f75c25e53e53db061a8143e6cc8d66ea125afa26
child 164324 37a6eecf1aef396575fddaa98763cf169c87ae9c
push id3066
push userakeybl@mozilla.com
push dateMon, 09 Dec 2013 19:58:46 +0000
treeherdermozilla-beta@a31a0dce83aa [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmyk
bugs774521
milestone27.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 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