mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
Bug 1062387 - Part 2. Implement DOM and JavaScript facing components of JS camera driver. r=mikeh r=bz
This commit is contained in:
parent
1f4b4c561d
commit
d8a5dc10d5
@ -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
|
||||
|
214
dom/camera/CameraTestHardware.js
Normal file
214
dom/camera/CameraTestHardware.js
Normal 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]);
|
2
dom/camera/CameraTestHardware.manifest
Normal file
2
dom/camera/CameraTestHardware.manifest
Normal file
@ -0,0 +1,2 @@
|
||||
component {fcb7b4cd-689e-453c-8a2c-611a45fa09ac} CameraTestHardware.js
|
||||
contract @mozilla.org/cameratesthardware;1 {fcb7b4cd-689e-453c-8a2c-611a45fa09ac}
|
@ -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)
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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',
|
||||
|
191
dom/camera/nsICameraTestHardware.idl
Normal file
191
dom/camera/nsICameraTestHardware.idl
Normal 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();
|
||||
};
|
||||
|
@ -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");
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user