Bug 1062387 - Part 2. Implement DOM and JavaScript facing components of JS camera driver. r=mikeh r=bz

This commit is contained in:
Andrew Osmond 2015-03-01 13:48:37 -05:00
parent 1f4b4c561d
commit d8a5dc10d5
10 changed files with 860 additions and 153 deletions

View File

@ -186,6 +186,9 @@
#ifdef MOZ_B2G_BT
@RESPATH@/components/dom_bluetooth.xpt
#endif
#ifdef MOZ_B2G_CAMERA
@BINPATH@/components/dom_camera.xpt
#endif
@RESPATH@/components/dom_canvas.xpt
@RESPATH@/components/dom_contacts.xpt
@RESPATH@/components/dom_alarm.xpt
@ -457,6 +460,12 @@
@RESPATH@/components/WifiWorker.manifest
#endif // MOZ_WIDGET_GONK
; Camera
#ifdef MOZ_B2G_CAMERA
@BINPATH@/components/CameraTestHardware.js
@BINPATH@/components/CameraTestHardware.manifest
#endif // MOZ_B2G_CAMERA
; Tethering
#ifdef MOZ_WIDGET_GONK
@RESPATH@/components/TetheringManager.js

View File

@ -0,0 +1,214 @@
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
/* 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";
const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/DOMRequestHelper.jsm");
const MOZ_CAMERATESTHW_CONTRACTID = "@mozilla.org/cameratesthardware;1";
const MOZ_CAMERATESTHW_CID = Components.ID("{fcb7b4cd-689e-453c-8a2c-611a45fa09ac}");
const DEBUG = false;
function debug(msg) {
if (DEBUG) {
dump('-*- MozCameraTestHardware: ' + msg + '\n');
}
}
function MozCameraTestHardware() {
this._params = {};
}
MozCameraTestHardware.prototype = {
classID: MOZ_CAMERATESTHW_CID,
contractID: MOZ_CAMERATESTHW_CONTRACTID,
classInfo: XPCOMUtils.generateCI({classID: MOZ_CAMERATESTHW_CID,
contractID: MOZ_CAMERATESTHW_CONTRACTID,
flags: Ci.nsIClassInfo.SINGLETON,
interfaces: [Ci.nsICameraTestHardware]}),
QueryInterface: XPCOMUtils.generateQI([Ci.nsICameraTestHardware]),
_params: null,
_window: null,
_mock: null,
_handler: null,
attach: function(mock) {
/* Waive xrays permits us to call functions provided to us
in the mock */
this._mock = Components.utils.waiveXrays(mock);
},
detach: function() {
this._mock = null;
},
/* Trigger a delegate handler attached to the test hardware
if given via attach. If there is no delegate attached, or
it does not provide a handler for this specific operation,
or the handler returns true, it will execute the default
behaviour. The handler may throw an exception in order to
return an error code from the driver call. */
_delegate: function(prop) {
return (this._mock && this._mock[prop] && !this._mock[prop]());
},
get params() {
return this._params;
},
set params(aParams) {
this._params = aParams;
},
setHandler: function(handler) {
this._handler = handler;
},
dispatchEvent: function(evt) {
if (this._handler) {
this._handler.handleEvent(evt);
}
},
reset: function(aWindow) {
this._window = aWindow;
this._mock = null;
this._params = {};
},
initCamera: function() {
this._delegate('init');
},
pushParameters: function(params) {
let oldParams = this._params;
this._params = {};
let s = params.split(';');
for(let i = 0; i < s.length; ++i) {
let parts = s[i].split('=');
if (parts.length == 2) {
this._params[parts[0]] = parts[1];
}
}
try {
this._delegate('pushParameters');
} catch(e) {
this._params = oldParams;
throw e;
}
},
pullParameters: function() {
this._delegate('pullParameters');
let ret = "";
for(let p in this._params) {
ret += p + "=" + this._params[p] + ";";
}
return ret;
},
autoFocus: function() {
if (!this._delegate('autoFocus')) {
this.fireAutoFocusComplete(true);
}
},
fireAutoFocusMoving: function(moving) {
let evt = new this._window.CameraStateChangeEvent('focus', { 'newState': moving ? 'focusing' : 'not_focusing' } );
this.dispatchEvent(evt);
},
fireAutoFocusComplete: function(state) {
let evt = new this._window.CameraStateChangeEvent('focus', { 'newState': state ? 'focused' : 'unfocused' } );
this.dispatchEvent(evt);
},
cancelAutoFocus: function() {
this._delegate('cancelAutoFocus');
},
fireShutter: function() {
let evt = new this._window.Event('shutter');
this.dispatchEvent(evt);
},
takePicture: function() {
if (!this._delegate('takePicture')) {
this.fireTakePictureComplete(new this._window.Blob(['foobar'], {'type': 'jpeg'}));
}
},
fireTakePictureComplete: function(blob) {
let evt = new this._window.BlobEvent('picture', {'data': blob});
this.dispatchEvent(evt);
},
fireTakePictureError: function() {
let evt = new this._window.ErrorEvent('error', {'message': 'picture'});
this.dispatchEvent(evt);
},
cancelTakePicture: function() {
this._delegate('cancelTakePicture');
},
startPreview: function() {
this._delegate('startPreview');
},
stopPreview: function() {
this._delegate('stopPreview');
},
startFaceDetection: function() {
this._delegate('startFaceDetection');
},
stopFaceDetection: function() {
this._delegate('stopFaceDetection');
},
fireFacesDetected: function(faces) {
/* This works around the fact that we can't have references to
dictionaries in a dictionary in WebIDL; we provide a boolean
to indicate whether or not the values for those features are
actually valid. */
let facesIf = [];
if (typeof(faces) === 'object' && typeof(faces.faces) === 'object') {
let self = this;
faces.faces.forEach(function(face) {
face.hasLeftEye = face.hasOwnProperty('leftEye') && face.leftEye != null;
face.hasRightEye = face.hasOwnProperty('rightEye') && face.rightEye != null;
face.hasMouth = face.hasOwnProperty('mouth') && face.mouth != null;
facesIf.push(new self._window.CameraDetectedFace(face));
});
}
let evt = new this._window.CameraFacesDetectedEvent('facesdetected', {'faces': facesIf});
this.dispatchEvent(evt);
},
startRecording: function() {
this._delegate('startRecording');
},
stopRecording: function() {
this._delegate('stopRecording');
},
fireSystemError: function() {
let evt = new this._window.ErrorEvent('error', {'message': 'system'});
this.dispatchEvent(evt);
},
};
this.NSGetFactory = XPCOMUtils.generateNSGetFactory([MozCameraTestHardware]);

View File

@ -0,0 +1,2 @@
component {fcb7b4cd-689e-453c-8a2c-611a45fa09ac} CameraTestHardware.js
contract @mozilla.org/cameratesthardware;1 {fcb7b4cd-689e-453c-8a2c-611a45fa09ac}

View File

@ -31,6 +31,40 @@ DOMCameraDetectedFace::WrapObject(JSContext* aCx)
return CameraDetectedFaceBinding::Wrap(aCx, this);
}
/* static */
already_AddRefed<DOMCameraDetectedFace>
DOMCameraDetectedFace::Constructor(const GlobalObject& aGlobal,
const dom::CameraDetectedFaceInit& aFace,
ErrorResult& aRv)
{
nsCOMPtr<nsISupports> s = do_QueryInterface(aGlobal.GetAsSupports());
nsRefPtr<DOMCameraDetectedFace> face = new DOMCameraDetectedFace(s, aFace);
return face.forget();
}
DOMCameraDetectedFace::DOMCameraDetectedFace(nsISupports* aParent,
const dom::CameraDetectedFaceInit& aFace)
: mParent(aParent)
, mId(aFace.mId)
, mScore(aFace.mScore)
, mBounds(new DOMRect(this))
{
mBounds->SetRect(aFace.mBounds.mLeft,
aFace.mBounds.mTop,
aFace.mBounds.mRight - aFace.mBounds.mLeft,
aFace.mBounds.mBottom - aFace.mBounds.mTop);
if (aFace.mHasLeftEye) {
mLeftEye = new DOMPoint(this, aFace.mLeftEye.mX, aFace.mLeftEye.mY);
}
if (aFace.mHasRightEye) {
mRightEye = new DOMPoint(this, aFace.mRightEye.mX, aFace.mRightEye.mY);
}
if (aFace.mHasMouth) {
mMouth = new DOMPoint(this, aFace.mMouth.mX, aFace.mMouth.mY);
}
}
DOMCameraDetectedFace::DOMCameraDetectedFace(nsISupports* aParent,
const ICameraControl::Face& aFace)
: mParent(aParent)

View File

@ -30,6 +30,10 @@ public:
// Great Renaming proposed in bug 983177.
static bool HasSupport(JSContext* aCx, JSObject* aGlobal);
static already_AddRefed<DOMCameraDetectedFace> Constructor(const GlobalObject& aGlobal,
const dom::CameraDetectedFaceInit& aFace,
ErrorResult& aRv);
DOMCameraDetectedFace(nsISupports* aParent, const ICameraControl::Face& aFace);
uint32_t Id() { return mId; }
@ -54,6 +58,7 @@ public:
virtual JSObject* WrapObject(JSContext* aCx) MOZ_OVERRIDE;
protected:
DOMCameraDetectedFace(nsISupports* aParent, const dom::CameraDetectedFaceInit& aFace);
virtual ~DOMCameraDetectedFace() { }
nsCOMPtr<nsISupports> mParent;

View File

@ -17,7 +17,6 @@
#include "nsDOMClassInfo.h"
#include "CameraCommon.h"
#include "mozilla/dom/BindingUtils.h"
#include "mozilla/dom/CameraManagerBinding.h"
#include "mozilla/dom/PermissionMessageUtils.h"
using namespace mozilla;

View File

@ -25,6 +25,12 @@ UNIFIED_SOURCES += [
]
if CONFIG['MOZ_B2G_CAMERA']:
XPIDL_SOURCES += [
'nsICameraTestHardware.idl',
]
XPIDL_MODULE = 'dom_camera'
UNIFIED_SOURCES += [
'GonkCameraControl.cpp',
'GonkCameraHwMgr.cpp',
@ -36,6 +42,11 @@ if CONFIG['MOZ_B2G_CAMERA']:
'TestGonkCameraControl.cpp',
'TestGonkCameraHardware.cpp',
]
EXTRA_COMPONENTS += [
'CameraTestHardware.js',
'CameraTestHardware.manifest',
]
else:
UNIFIED_SOURCES += [
'FallbackCameraControl.cpp',

View File

@ -0,0 +1,191 @@
/* 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/. */
#include "nsISupports.idl"
interface nsIDOMBlob;
interface nsIDOMEventListener;
[scriptable, uuid(2e567730-f164-49d7-b975-862caa4425a5)]
interface nsICameraTestHardware : nsISupports
{
/* The following methods are intended to be used by the test cases
written in JavaScript to define the behaviour of the hardware: */
/* Attach a delegate handler object such that the test hardware
will call the given handlers for the given operations to decide
what to do. This allows a test case to define specific behaviours
on a fine grained basis.
The following handlers may be supplied as properties of the
given delagate handler object:
autoFocus
cancelAutoFocus
cancelTakePicture
init
pushParameters
pullParameters
startFaceDetection
startPreview
startRecording
stopFaceDetection
stopPreview
stopRecording
takePicture
Implementation notes for handlers:
- If the handler throws an error, we will the return code
of the driver operation.
- If the handler returns true, we will perform the default
action (if any) for the operation. */
void attach(in jsval mock);
/* Detach a delegate handler object such that the test hardware
will revert to default behaviour when a function is called. */
void detach();
/* Reset the state of the test hardware back to the initial state.
This is useful when one test case has been completed and we need
a clean slate for the next. */
void reset(in jsval window);
/* Trigger an OnAutoFocusMoving callback at the Gonk layer.
state is a boolean indicating where or not the camera focus
is moving. */
void fireAutoFocusComplete(in boolean state);
/* Trigger an OnAutoFocusComplete callback at the Gonk layer.
state is a boolean indicating where or not the camera is focused. */
void fireAutoFocusMoving(in boolean moving);
/* Trigger an OnTakePictureComplete callback at the Gonk layer.
blob should be a Blob object. The actual content of the blob
is unimportant since nothing processes it as an image internally. */
void fireTakePictureComplete(in nsIDOMBlob picture);
/* Trigger an OnTakePictureError callback at the Gonk layer. */
void fireTakePictureError();
/* Trigger an OnSystemError callback at the Gonk layer. */
void fireSystemError();
/* Trigger an OnShutter callback at the Gonk layer. */
void fireShutter();
/* Trigger an OnFacesDetected callback at the Gonk layer.
faces is an array of CameraDetectedFaceInit dictionaries although
hasLeftEye, hasRightEye and hasMouth may be omitted and will be
implied by the presence/absence of leftEye, rightEye and mouth. */
void fireFacesDetected(in jsval faces);
/* Object which stores the camera parameters read/written by the
camera control layer from the hardware. The test case may set
its own values to control the behaviour of the camera middleware.
E.g. params['preview-sizes'] = '320x240,640x480'; */
attribute jsval params;
/* The following methods are intended to be used by the Gonk layer
in order to call back into JavaScript to get test case defined
behaviour: */
/* Set a handler to capture asynchronous events triggered by the
test case via the fireXXX methods. E.g.:
nsCOMPtr<nsICameraHardware> wrapper =
do_GetService("@mozilla.org/cameratesthardware;1");
nsCOMPtr<nsIDOMEventListener> listener = new HwListener();
wrapper->setHander(listener);
where
class HwListener : public nsIDOMEventListener {
NS_IMETHODIMP HandleEvent(nsIDOMEvent *aEvent) {
nsString type;
aEvent->GetType(&type);
if (aEvent.EqualsLiteral("focus")) {
...
} else {
...
}
}
};
The following event types may be generated:
focus: CameraStateChangeEvent where newState should map
to the OnAutoFocusComplete and OnAutoFocusMoving callbacks:
-- focused: OnAutoFocusComplete(false)
-- unfocused: OnAutoFocusComplete(true)
-- focusing: OnAutoFocusMoving(true)
-- not_focusing: OnAutoFocusMoving(false)
picture: BlobEvent which contains the picture type and
data corresponding to the OnTakePictureComplete callback.
error: ErrorEvent corresponding to the various error callbacks,
where the message is:
-- picture: OnTakePictureError()
-- system: OnSystemError(100, 0)
facesdetected: CameraFacesDetectedEvent which contains the
faces data corresponding to OnFacesDetected callback.
shutter: Event which corresponds to the OnShutter callback. */
void setHandler(in nsIDOMEventListener handler);
/* Execute an intercepted Init() driver call. */
void initCamera();
/* Execute an intercepted AutoFocus() driver call. Default behaviour is
to trigger OnAutoFocusComplete where the camera is focused. */
void autoFocus();
/* Execute an intercepted CancelAutoFocus() driver call. */
void cancelAutoFocus();
/* Execute an intercepted StartFaceDetection() driver call. */
void startFaceDetection();
/* Execute an intercepted StopFaceDetection() driver call. */
void stopFaceDetection();
/* Execute an intercepted TakePicture() driver call. Default behaviour is
to trigger OnTakePictureComplete with a fake jpeg blob. */
void takePicture();
/* Execute an intercepted CancelTakePicture() driver call. */
void cancelTakePicture();
/* Execute an intercepted StartPreview() driver call. */
void startPreview();
/* Execute an intercepted StopPreview() driver call. */
void stopPreview();
/* Execute an intercepted StartRecording() driver call. */
void startRecording();
/* Execute an intercepted StopRecording() driver call. */
void stopRecording();
/* Execute an intercepted PushParameters() driver call. If the delegate
handler throws an error, it will restore the old parameters.
When the delegate is called, the new proposed parameters are
placed in this.params. */
void pushParameters(in DOMString params);
/* Execute an intercepted PullParameters() driver call. Unless the delegate
handler throws an error, it will return an assembled parameter
list derived from the this.params hash table. */
DOMString pullParameters();
};

View File

@ -1,169 +1,396 @@
var CameraTest = (function() {
'use strict';
function isDefinedObj(obj) {
return typeof(obj) !== 'undefined' && obj != null;
}
/**
* 'camera.control.test.enabled' is queried in Gecko to enable different
* test modes in the camera stack. The only currently-supported setting
* is 'hardware', which wraps the Gonk camera abstraction class in a
* shim class that supports injecting hardware camera API failures into
* the execution path.
*
* The affected API is specified by the 'camera.control.test.hardware'
* pref. Currently supported values should be determined by inspecting
* TestGonkCameraHardware.cpp.
*
* Some API calls are simple: e.g. 'start-recording-failure' will cause
* the DOM-facing startRecording() call to fail. More complex tests like
* 'take-picture-failure' will cause the takePicture() API to fail, while
* 'take-picture-process-failure' will simulate a failure of the
* asynchronous picture-taking process, even if the initial API call
* path seems to have succeeded.
*
* If 'camera.control.test.hardware.gonk.parameters' is set, it will cause
* the contents of that string to be appended to the string of parameters
* pulled from the Gonk camera library. This allows tests to inject fake
* settings/capabilities for features not supported by the emulator. These
* parameters are one or more semicolon-delimited key=value pairs, e.g. to
* pretend the emulator supports zoom:
*
* zoom-ratios=100,150,200,300,400;max-zoom=4
*
* This means (of course) that neither the key not the value tokens can
* contain either equals signs or semicolons. The test shim doesn't enforce
* this so that we can test getting junk from the camera library as well.
*/
const PREF_TEST_ENABLED = "camera.control.test.enabled";
const PREF_TEST_HARDWARE = "camera.control.test.hardware";
const PREF_TEST_EXTRA_PARAMETERS = "camera.control.test.hardware.gonk.parameters";
const PREF_TEST_FAKE_LOW_MEMORY = "camera.control.test.is_low_memory";
var oldTestEnabled;
var oldTestHw;
var testMode;
function isDefined(obj) {
return typeof(obj) !== 'undefined';
}
function testHardwareSetFakeParameters(parameters, callback) {
SpecialPowers.pushPrefEnv({'set': [[PREF_TEST_EXTRA_PARAMETERS, parameters]]}, function() {
var setParams = SpecialPowers.getCharPref(PREF_TEST_EXTRA_PARAMETERS);
ise(setParams, parameters, "Extra test parameters '" + setParams + "'");
if (callback) {
callback(setParams);
}
});
}
/* This is a simple test suite class removing the need to
write a lot of boilerplate for camera tests. It can
manage the platform configurations for testing, any
cleanup required, and common actions such as fetching
the camera or waiting for the preview to be completed.
function testHardwareClearFakeParameters(callback) {
SpecialPowers.pushPrefEnv({'clear': [[PREF_TEST_EXTRA_PARAMETERS]]}, callback);
}
To create the suite:
var suite = new CameraTestSuite();
function testHardwareSetFakeLowMemoryPlatform(callback) {
SpecialPowers.pushPrefEnv({'set': [[PREF_TEST_FAKE_LOW_MEMORY, true]]}, function() {
var setParams = SpecialPowers.getBoolPref(PREF_TEST_FAKE_LOW_MEMORY);
ise(setParams, true, "Fake low memory platform");
if (callback) {
callback(setParams);
}
});
}
To add a test case to the suite:
suite.test('test-name', function() {
function startAutoFocus(p) {
return suite.camera.autoFocus();
}
function testHardwareClearFakeLowMemoryPlatform(callback) {
SpecialPowers.pushPrefEnv({'clear': [[PREF_TEST_FAKE_LOW_MEMORY]]}, callback);
}
return suite.getCamera()
.then(startAutoFocus, suite.rejectGetCamera);
});
function testHardwareSet(test, callback) {
SpecialPowers.pushPrefEnv({'set': [[PREF_TEST_HARDWARE, test]]}, function() {
var setTest = SpecialPowers.getCharPref(PREF_TEST_HARDWARE);
ise(setTest, test, "Test subtype set to " + setTest);
if (callback) {
callback(setTest);
}
});
}
Finally, to execute the test cases:
suite.setup()
.then(suite.run);
function testHardwareDone(callback) {
testMode = null;
if (oldTestHw) {
SpecialPowers.pushPrefEnv({'set': [[PREF_TEST_HARDWARE, oldTestHw]]}, callback);
} else {
SpecialPowers.pushPrefEnv({'clear': [[PREF_TEST_HARDWARE]]}, callback);
Behind the scenes, suite configured the native camera
to use the JS hardware, setup that hardware such that
the getCamera would succeed, got a camera control
reference and saved it to suite.camera, and after the
tests were finished, it reset any modified state,
released the camera object, and concluded the mochitest
appropriately.
*/
function CameraTestSuite() {
SimpleTest.waitForExplicitFinish();
this._window = window;
this._document = document;
this.viewfinder = document.getElementById('viewfinder');
this._tests = [];
this.hwType = '';
/* Ensure that the this pointer is bound to all functions so that
they may be used as promise resolve/reject handlers without any
special effort, permitting code like this:
getCamera().catch(suite.rejectGetCamera);
instead of:
getCamera().catch(suite.rejectGetCamera.bind(suite));
*/
this.setup = this._setup.bind(this);
this.teardown = this._teardown.bind(this);
this.test = this._test.bind(this);
this.run = this._run.bind(this);
this.waitPreviewStarted = this._waitPreviewStarted.bind(this);
this.waitParameterPush = this._waitParameterPush.bind(this);
this.initJsHw = this._initJsHw.bind(this);
this.getCamera = this._getCamera.bind(this);
this.setLowMemoryPlatform = this._setLowMemoryPlatform.bind(this);
this.logError = this._logError.bind(this);
this.expectedError = this._expectedError.bind(this);
this.expectedRejectGetCamera = this._expectedRejectGetCamera.bind(this);
this.expectedRejectAutoFocus = this._expectedRejectAutoFocus.bind(this);
this.expectedRejectTakePicture = this._expectedRejectTakePicture.bind(this);
this.rejectGetCamera = this._rejectGetCamera.bind(this);
this.rejectRelease = this._rejectRelease.bind(this);
this.rejectAutoFocus = this._rejectAutoFocus.bind(this);
this.rejectTakePicture = this._rejectTakePicture.bind(this);
this.rejectPreviewStarted = this._rejectPreviewStarted.bind(this);
var self = this;
this._window.addEventListener('beforeunload', function() {
if (isDefinedObj(self.viewfinder)) {
self.viewfinder.mozSrcObject = null;
}
}
function testBegin(mode, callback) {
SimpleTest.waitForExplicitFinish();
try {
oldTestEnabled = SpecialPowers.getCharPref(PREF_TEST_ENABLED);
} catch(e) { }
SpecialPowers.pushPrefEnv({'set': [[PREF_TEST_ENABLED, mode]]}, function() {
var setMode = SpecialPowers.getCharPref(PREF_TEST_ENABLED);
ise(setMode, mode, "Test mode set to " + setMode);
if (setMode === "hardware") {
self.hw = null;
if (isDefinedObj(self.camera)) {
ok(false, 'window unload triggered camera release instead of test completion');
self.camera.release();
self.camera = null;
}
});
}
CameraTestSuite.prototype = {
camera: null,
hw: null,
_lowMemSet: false,
/* Returns a promise which is resolved when the test suite is ready
to be executing individual test cases. One may provide the expected
hardware type here if desired; the default is to use the JS test
hardware. Use '' for the native emulated camera hardware. */
_setup: function(hwType) {
if (!isDefined(hwType)) {
hwType = 'hardware';
}
this._hwType = hwType;
return new Promise(function(resolve, reject) {
SpecialPowers.pushPrefEnv({'set': [['camera.control.test.enabled', hwType]]}, function() {
resolve();
});
});
},
/* Returns a promise which is resolved when all of the SpecialPowers
parameters that were set while testing are flushed. This includes
camera.control.test.enabled and camera.control.test.is_low_memory. */
_teardown: function() {
return new Promise(function(resolve, reject) {
SpecialPowers.flushPrefEnv(function() {
resolve();
});
});
},
/* Returns a promise which is resolved when the set low memory
parameter is set. If no value is given, it defaults to true.
This is intended to be used inside a test case at the beginning
of its promise chain to configure the platform as desired. */
_setLowMemoryPlatform: function(val) {
if (typeof(val) === 'undefined') {
val = true;
}
if (this._lowMemSet === val) {
return Promise.resolve();
}
var self = this;
return new Promise(function(resolve, reject) {
SpecialPowers.pushPrefEnv({'set': [['camera.control.test.is_low_memory', val]]}, function() {
self._lowMemSet = val;
resolve();
});
}).catch(function(e) {
return self.logError('set low memory ' + val + ' failed', e);
});
},
/* Add a test case to the test suite to be executed later. */
_test: function(aName, aCb) {
this._tests.push({
name: aName,
cb: aCb
});
},
/* Execute all test cases (after setup is called). */
_run: function() {
var test = this._tests.shift();
var self = this;
if (test) {
info(test.name + ' started');
function runNextTest() {
self.run();
}
function resetLowMem() {
return self.setLowMemoryPlatform(false);
}
function postTest(pass) {
ok(pass, test.name + ' finished');
var camera = self.camera;
self.viewfinder.mozSrcObject = null;
self.camera = null;
if (!isDefinedObj(camera)) {
return Promise.resolve();
}
function handler(e) {
ok(typeof(e) === 'undefined', 'camera released');
return Promise.resolve();
}
return camera.release().then(handler).catch(handler);
}
this.initJsHw();
var testPromise;
try {
testPromise = test.cb();
if (!isDefinedObj(testPromise)) {
testPromise = Promise.resolve();
}
} catch(e) {
ok(false, 'caught exception while running test: ' + e);
testPromise = Promise.reject(e);
}
testPromise
.then(function(p) {
return postTest(true);
}, function(e) {
self.logError('unhandled error', e);
return postTest(false);
})
.then(resetLowMem, resetLowMem)
.then(runNextTest, runNextTest);
} else {
ok(true, 'all tests completed');
var finish = SimpleTest.finish.bind(SimpleTest);
this.teardown().then(finish, finish);
}
},
/* If the JS hardware is in use, get (and possibly initialize)
the service XPCOM object. The native Gonk layers are able
to get it via the same mechanism. Save a reference to it
so that the test case may manipulate it as it sees fit in
this.hw. Minimal setup is done for the test hardware such
that the camera is able to be brought up without issue.
This function has no effect if the JS hardware is not used. */
_initJsHw: function() {
if (this._hwType === 'hardware') {
this.hw = SpecialPowers.Cc['@mozilla.org/cameratesthardware;1']
.getService(SpecialPowers.Ci.nsICameraTestHardware);
this.hw.reset(this._window);
/* Minimum parameters required to get camera started */
this.hw.params['preview-size'] = '320x240';
this.hw.params['preview-size-values'] = '320x240';
this.hw.params['picture-size-values'] = '320x240';
} else {
this.hw = null;
}
},
/* Returns a promise which resolves when the camera has
been successfully opened with the given name and
configuration. If no name is given, it uses the first
camera in the list from the camera manager. */
_getCamera: function(name, config) {
var cameraManager = navigator.mozCameras;
if (!isDefined(name)) {
name = cameraManager.getListOfCameras()[0];
}
var self = this;
return cameraManager.getCamera(name, config).then(
function(p) {
ok(isDefinedObj(p) && isDefinedObj(p.camera), 'got camera');
self.camera = p.camera;
/* Ensure a followup promise can verify config by
returning the same parameter again. */
return Promise.resolve(p);
}
);
},
/* Returns a promise which resolves when the camera has
successfully started the preview and is bound to the
given viewfinder object. Note that this requires that
a video element be present with the ID 'viewfinder'. */
_waitPreviewStarted: function() {
var self = this;
return new Promise(function(resolve, reject) {
function onPreviewStateChange(e) {
try {
oldTestHw = SpecialPowers.getCharPref(PREF_TEST_HARDWARE);
} catch(e) { }
testMode = {
set: testHardwareSet,
setFakeParameters: testHardwareSetFakeParameters,
clearFakeParameters: testHardwareClearFakeParameters,
setFakeLowMemoryPlatform: testHardwareSetFakeLowMemoryPlatform,
clearFakeLowMemoryPlatform: testHardwareClearFakeLowMemoryPlatform,
done: testHardwareDone
};
if (callback) {
callback(testMode);
if (e.newState === 'started') {
ok(true, 'viewfinder is ready and playing');
self.camera.removeEventListener('previewstatechange', onPreviewStateChange);
resolve();
}
} catch(e) {
reject(e);
}
}
if (!isDefinedObj(self.viewfinder)) {
reject(new Error('no viewfinder object'));
return;
}
self.viewfinder.mozSrcObject = self.camera;
self.viewfinder.play();
self.camera.addEventListener('previewstatechange', onPreviewStateChange);
});
}
},
function testEnd(callback) {
// A chain of clean-up functions....
function allCleanedUp() {
SimpleTest.finish();
if (callback) {
callback();
}
}
/* Returns a promise which resolves when the camera hardware
has received a push parameters request. This is useful
when setting camera parameters from the application and
you want confirmation when the operation is complete if
there is no asynchronous notification provided. */
_waitParameterPush: function() {
var self = this;
function cleanUpTestEnabled() {
var next = allCleanedUp;
if (oldTestEnabled) {
SpecialPowers.pushPrefEnv({'set': [[PREF_TEST_ENABLED, oldTestEnabled]]}, next);
} else {
SpecialPowers.pushPrefEnv({'clear': [[PREF_TEST_ENABLED]]}, next);
}
}
function cleanUpTest() {
var next = cleanUpTestEnabled;
if (testMode) {
testMode.done(next);
testMode = null;
} else {
next();
}
}
function cleanUpLowMemoryPlatform() {
var next = cleanUpTest;
if (testMode) {
testMode.clearFakeLowMemoryPlatform(next);
} else {
next();
}
}
function cleanUpExtraParameters() {
var next = cleanUpLowMemoryPlatform;
if (testMode) {
testMode.clearFakeParameters(next);
} else {
next();
}
}
return new Promise(function(resolve, reject) {
self.hw.attach({
'pushParameters': function() {
self._window.setTimeout(resolve);
}
});
});
},
cleanUpExtraParameters();
}
/* When an error occurs in the promise chain, all of the relevant rejection
functions will be triggered. Most of the time however we only want the
first rejection to be handled and then let the failure trickle down the
chain to terminate the test. There is no way to exit a promise chain
early so the convention is to handle the error in the first reject and
then give an empty error for subsequent reject handlers so they know
it is not for them.
ise(SpecialPowers.sanityCheck(), "foo", "SpecialPowers passed sanity check");
return {
begin: testBegin,
end: testEnd
};
For example:
function rejectSomething(e) {
return suite.logError('something call failed');
}
})();
getCamera()
.then(, suite.rejectGetCamera)
.then(something)
.then(, rejectSomething)
If the getCamera promise is rejected, suite.rejectGetCamera reports an
error, but rejectSomething remains silent. */
_logError: function(msg, e) {
if (isDefined(e)) {
ok(false, msg + ': ' + e);
}
// Make sure the error is undefined for later handlers
return Promise.reject();
},
/* The reject handlers below are intended to be used
when a test case does not expect a particular call
to fail but otherwise does not require any special
handling of that situation beyond failing the test
case and logging why.*/
_rejectGetCamera: function(e) {
return this.logError('get camera failed', e);
},
_rejectRelease: function(e) {
return this.logError('release camera failed', e);
},
_rejectAutoFocus: function(e) {
return this.logError('auto focus failed', e);
},
_rejectTakePicture: function(e) {
return this.logError('take picture failed', e);
},
_rejectPreviewStarted: function(e) {
return this.logError('preview start failed', e);
},
/* The success handlers below are intended to be used
when a test case does not expect a particular call
to succed but otherwise does not require any special
handling of that situation beyond failing the test
case and logging why.*/
_expectedError: function(msg) {
ok(false, msg);
/* Since the original promise was technically resolved
we actually want to pass up a rejection to try and
end the test case sooner */
return Promise.reject();
},
_expectedRejectGetCamera: function(p) {
/* Copy handle to ensure it gets released at the end
of the test case */
self.camera = p.camera;
return this.expectedError('expected get camera to fail');
},
_expectedRejectAutoFocus: function(p) {
return this.expectedError('expected auto focus to fail');
},
_expectedRejectTakePicture: function(p) {
return this.expectedError('expected take picture to fail');
},
};
ise(SpecialPowers.sanityCheck(), "foo", "SpecialPowers passed sanity check");

View File

@ -423,7 +423,9 @@ interface CameraControl : MediaStream
'mouth' is the coordinates of the detected mouth; null if not supported or
detected. Same boundary conditions as 'leftEye'.
*/
[Pref="camera.control.face_detection.enabled", Func="DOMCameraDetectedFace::HasSupport"]
[Pref="camera.control.face_detection.enabled",
Func="DOMCameraDetectedFace::HasSupport",
Constructor(optional CameraDetectedFaceInit initDict)]
interface CameraDetectedFace
{
readonly attribute unsigned long id;
@ -442,6 +444,19 @@ interface CameraDetectedFace
readonly attribute DOMPoint? mouth;
};
dictionary CameraDetectedFaceInit
{
unsigned long id = 0;
unsigned long score = 100;
CameraRegion bounds;
boolean hasLeftEye = false;
DOMPointInit leftEye;
boolean hasRightEye = false;
DOMPointInit rightEye;
boolean hasMouth = false;
DOMPointInit mouth;
};
callback CameraFaceDetectionCallback = void (sequence<CameraDetectedFace> faces);
partial interface CameraControl