зеркало из https://github.com/mozilla/gecko-dev.git
Merge mozilla-central to fx-team
This commit is contained in:
Коммит
5a7bf7c7b3
|
@ -5,7 +5,7 @@
|
|||
MOZ_APP_BASENAME=B2G
|
||||
MOZ_APP_VENDOR=Mozilla
|
||||
|
||||
MOZ_APP_VERSION=46.0a1
|
||||
MOZ_APP_VERSION=$FIREFOX_VERSION
|
||||
MOZ_APP_UA_NAME=Firefox
|
||||
|
||||
MOZ_UA_OS_AGNOSTIC=1
|
||||
|
|
|
@ -279,6 +279,7 @@ function prompt(aBrowser, aRequest) {
|
|||
requestTypes: requestTypes} = aRequest;
|
||||
let uri = Services.io.newURI(aRequest.documentURI, null, null);
|
||||
let host = getHost(uri);
|
||||
let principal = Services.scriptSecurityManager.createCodebasePrincipal(uri, {});
|
||||
let chromeDoc = aBrowser.ownerDocument;
|
||||
let chromeWin = chromeDoc.defaultView;
|
||||
let stringBundle = chromeWin.gNavigatorBundle;
|
||||
|
@ -374,7 +375,16 @@ function prompt(aBrowser, aRequest) {
|
|||
if (micPerm == perms.PROMPT_ACTION)
|
||||
micPerm = perms.UNKNOWN_ACTION;
|
||||
|
||||
let camPermanentPerm = perms.testExactPermanentPermission(principal, "camera");
|
||||
let camPerm = perms.testExactPermission(uri, "camera");
|
||||
|
||||
// Session approval given but never used to allocate a camera, remove
|
||||
// and ask again
|
||||
if (camPerm && !camPermanentPerm) {
|
||||
perms.remove(uri, "camera");
|
||||
camPerm = perms.UNKNOWN_ACTION;
|
||||
}
|
||||
|
||||
if (camPerm == perms.PROMPT_ACTION)
|
||||
camPerm = perms.UNKNOWN_ACTION;
|
||||
|
||||
|
@ -515,11 +525,14 @@ function prompt(aBrowser, aRequest) {
|
|||
let listId = "webRTC-select" + (sharingScreen ? "Window" : "Camera") + "-menulist";
|
||||
let videoDeviceIndex = chromeDoc.getElementById(listId).value;
|
||||
let allowCamera = videoDeviceIndex != "-1";
|
||||
if (allowCamera)
|
||||
if (allowCamera) {
|
||||
allowedDevices.push(videoDeviceIndex);
|
||||
if (aRemember) {
|
||||
perms.add(uri, "camera",
|
||||
allowCamera ? perms.ALLOW_ACTION : perms.DENY_ACTION);
|
||||
// Session permission will be removed after use
|
||||
// (it's really one-shot, not for the entire session)
|
||||
perms.add(uri, "camera", perms.ALLOW_ACTION,
|
||||
aRemember ? perms.EXPIRE_NEVER : perms.EXPIRE_SESSION);
|
||||
} else if (aRemember) {
|
||||
perms.add(uri, "camera", perms.DENY_ACTION);
|
||||
}
|
||||
}
|
||||
if (audioDevices.length) {
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
#include "mozilla/Attributes.h"
|
||||
#include "mozilla/ErrorResult.h"
|
||||
#include "mozilla/dom/BindingDeclarations.h"
|
||||
#include "mozilla/dom/Element.h"
|
||||
#include "mozilla/RefPtr.h"
|
||||
#include "nsCSSPseudoElements.h"
|
||||
#include "nsWrapperCache.h"
|
||||
|
|
|
@ -23,16 +23,17 @@ FormData::FormData(nsISupports* aOwner)
|
|||
|
||||
namespace {
|
||||
|
||||
already_AddRefed<Blob>
|
||||
GetBlobForFormDataStorage(Blob& aBlob, const Optional<nsAString>& aFilename,
|
||||
ErrorResult& aRv)
|
||||
already_AddRefed<File>
|
||||
GetOrCreateFileCalledBlob(Blob& aBlob, ErrorResult& aRv)
|
||||
{
|
||||
if (!aFilename.WasPassed()) {
|
||||
RefPtr<Blob> blob = &aBlob;
|
||||
return blob.forget();
|
||||
// If this is file, we can just use it
|
||||
RefPtr<File> file = aBlob.ToFile();
|
||||
if (file) {
|
||||
return file.forget();
|
||||
}
|
||||
|
||||
RefPtr<File> file = aBlob.ToFile(aFilename.Value(), aRv);
|
||||
// Forcing 'blob' as filename
|
||||
file = aBlob.ToFile(NS_LITERAL_STRING("blob"), aRv);
|
||||
if (NS_WARN_IF(aRv.Failed())) {
|
||||
return nullptr;
|
||||
}
|
||||
|
@ -40,6 +41,23 @@ GetBlobForFormDataStorage(Blob& aBlob, const Optional<nsAString>& aFilename,
|
|||
return file.forget();
|
||||
}
|
||||
|
||||
already_AddRefed<File>
|
||||
GetBlobForFormDataStorage(Blob& aBlob, const Optional<nsAString>& aFilename,
|
||||
ErrorResult& aRv)
|
||||
{
|
||||
// Forcing a filename
|
||||
if (aFilename.WasPassed()) {
|
||||
RefPtr<File> file = aBlob.ToFile(aFilename.Value(), aRv);
|
||||
if (NS_WARN_IF(aRv.Failed())) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return file.forget();
|
||||
}
|
||||
|
||||
return GetOrCreateFileCalledBlob(aBlob, aRv);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
|
@ -102,12 +120,12 @@ FormData::Append(const nsAString& aName, Blob& aBlob,
|
|||
const Optional<nsAString>& aFilename,
|
||||
ErrorResult& aRv)
|
||||
{
|
||||
RefPtr<Blob> blob = GetBlobForFormDataStorage(aBlob, aFilename, aRv);
|
||||
RefPtr<File> file = GetBlobForFormDataStorage(aBlob, aFilename, aRv);
|
||||
if (NS_WARN_IF(aRv.Failed())) {
|
||||
return;
|
||||
}
|
||||
|
||||
AddNameBlobPair(aName, blob);
|
||||
AddNameBlobPair(aName, file);
|
||||
}
|
||||
|
||||
void
|
||||
|
@ -165,8 +183,14 @@ FormData::AddNameBlobPair(const nsAString& aName, Blob* aBlob)
|
|||
{
|
||||
MOZ_ASSERT(aBlob);
|
||||
|
||||
ErrorResult rv;
|
||||
RefPtr<File> file = GetOrCreateFileCalledBlob(*aBlob, rv);
|
||||
if (NS_WARN_IF(rv.Failed())) {
|
||||
return rv.StealNSResult();
|
||||
}
|
||||
|
||||
FormDataTuple* data = mFormData.AppendElement();
|
||||
SetNameBlobPair(data, aName, aBlob);
|
||||
SetNameFilePair(data, aName, file);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
|
@ -199,12 +223,12 @@ FormData::Set(const nsAString& aName, Blob& aBlob,
|
|||
{
|
||||
FormDataTuple* tuple = RemoveAllOthersAndGetFirstFormDataTuple(aName);
|
||||
if (tuple) {
|
||||
RefPtr<Blob> blob = GetBlobForFormDataStorage(aBlob, aFilename, aRv);
|
||||
RefPtr<File> file = GetBlobForFormDataStorage(aBlob, aFilename, aRv);
|
||||
if (NS_WARN_IF(aRv.Failed())) {
|
||||
return;
|
||||
}
|
||||
|
||||
SetNameBlobPair(tuple, aName, blob);
|
||||
SetNameFilePair(tuple, aName, file);
|
||||
} else {
|
||||
Append(aName, aBlob, aFilename, aRv);
|
||||
}
|
||||
|
@ -253,15 +277,15 @@ FormData::SetNameValuePair(FormDataTuple* aData,
|
|||
}
|
||||
|
||||
void
|
||||
FormData::SetNameBlobPair(FormDataTuple* aData,
|
||||
FormData::SetNameFilePair(FormDataTuple* aData,
|
||||
const nsAString& aName,
|
||||
Blob* aBlob)
|
||||
File* aFile)
|
||||
{
|
||||
MOZ_ASSERT(aData);
|
||||
MOZ_ASSERT(aBlob);
|
||||
MOZ_ASSERT(aFile);
|
||||
|
||||
aData->name = aName;
|
||||
aData->value.SetAsBlob() = aBlob;
|
||||
aData->value.SetAsBlob() = aFile;
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
|
|
|
@ -47,9 +47,9 @@ private:
|
|||
const nsAString& aName,
|
||||
const nsAString& aValue);
|
||||
|
||||
void SetNameBlobPair(FormDataTuple* aData,
|
||||
void SetNameFilePair(FormDataTuple* aData,
|
||||
const nsAString& aName,
|
||||
Blob* aBlob);
|
||||
File* aFile);
|
||||
|
||||
public:
|
||||
explicit FormData(nsISupports* aOwner = nullptr);
|
||||
|
|
|
@ -5,7 +5,6 @@
|
|||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "ImageEncoder.h"
|
||||
#include "mozilla/ClearOnShutdown.h"
|
||||
#include "mozilla/dom/CanvasRenderingContext2D.h"
|
||||
#include "mozilla/gfx/2D.h"
|
||||
#include "mozilla/gfx/DataSurfaceHelpers.h"
|
||||
|
@ -491,6 +490,38 @@ ImageEncoder::GetImageEncoder(nsAString& aType)
|
|||
return encoder.forget();
|
||||
}
|
||||
|
||||
class EncoderThreadPoolTerminator final : public nsIObserver
|
||||
{
|
||||
public:
|
||||
NS_DECL_ISUPPORTS
|
||||
|
||||
NS_IMETHODIMP Observe(nsISupports *, const char *topic, const char16_t *) override
|
||||
{
|
||||
NS_ASSERTION(!strcmp(topic, "xpcom-shutdown-threads"),
|
||||
"Unexpected topic");
|
||||
if (ImageEncoder::sThreadPool) {
|
||||
ImageEncoder::sThreadPool->Shutdown();
|
||||
ImageEncoder::sThreadPool = nullptr;
|
||||
}
|
||||
return NS_OK;
|
||||
}
|
||||
private:
|
||||
~EncoderThreadPoolTerminator() {}
|
||||
};
|
||||
|
||||
NS_IMPL_ISUPPORTS(EncoderThreadPoolTerminator, nsIObserver)
|
||||
|
||||
static void
|
||||
RegisterEncoderThreadPoolTerminatorObserver()
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
|
||||
NS_ASSERTION(os, "do_GetService failed");
|
||||
os->AddObserver(new EncoderThreadPoolTerminator(),
|
||||
"xpcom-shutdown-threads",
|
||||
false);
|
||||
}
|
||||
|
||||
/* static */
|
||||
nsresult
|
||||
ImageEncoder::EnsureThreadPool()
|
||||
|
@ -498,12 +529,13 @@ ImageEncoder::EnsureThreadPool()
|
|||
if (!sThreadPool) {
|
||||
nsCOMPtr<nsIThreadPool> threadPool = do_CreateInstance(NS_THREADPOOL_CONTRACTID);
|
||||
sThreadPool = threadPool;
|
||||
|
||||
if (!NS_IsMainThread()) {
|
||||
NS_DispatchToMainThread(NS_NewRunnableFunction([]() -> void {
|
||||
ClearOnShutdown(&sThreadPool);
|
||||
RegisterEncoderThreadPoolTerminatorObserver();
|
||||
}));
|
||||
} else {
|
||||
ClearOnShutdown(&sThreadPool);
|
||||
RegisterEncoderThreadPoolTerminatorObserver();
|
||||
}
|
||||
|
||||
const uint32_t kThreadLimit = 2;
|
||||
|
|
|
@ -114,6 +114,7 @@ private:
|
|||
static StaticRefPtr<nsIThreadPool> sThreadPool;
|
||||
|
||||
friend class EncodingRunnable;
|
||||
friend class EncoderThreadPoolTerminator;
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
@ -507,8 +507,13 @@ AutoJSAPI::ReportException()
|
|||
// In this case, we enter the privileged junk scope and don't dispatch any
|
||||
// error events.
|
||||
JS::Rooted<JSObject*> errorGlobal(cx(), JS::CurrentGlobalOrNull(cx()));
|
||||
if (!errorGlobal)
|
||||
errorGlobal = xpc::PrivilegedJunkScope();
|
||||
if (!errorGlobal) {
|
||||
if (mIsMainThread) {
|
||||
errorGlobal = xpc::PrivilegedJunkScope();
|
||||
} else {
|
||||
errorGlobal = workers::GetCurrentThreadWorkerGlobal();
|
||||
}
|
||||
}
|
||||
JSAutoCompartment ac(cx(), errorGlobal);
|
||||
JS::Rooted<JS::Value> exn(cx());
|
||||
js::ErrorReport jsReport(cx());
|
||||
|
|
|
@ -436,6 +436,7 @@ LOCAL_INCLUDES += [
|
|||
'/dom/ipc',
|
||||
'/dom/storage',
|
||||
'/dom/svg',
|
||||
'/dom/u2f',
|
||||
'/dom/workers',
|
||||
'/dom/xbl',
|
||||
'/dom/xml',
|
||||
|
|
|
@ -226,6 +226,7 @@
|
|||
#include "mozilla/dom/NavigatorBinding.h"
|
||||
#include "mozilla/dom/ImageBitmap.h"
|
||||
#include "mozilla/dom/ServiceWorkerRegistration.h"
|
||||
#include "mozilla/dom/U2F.h"
|
||||
#ifdef HAVE_SIDEBAR
|
||||
#include "mozilla/dom/ExternalBinding.h"
|
||||
#endif
|
||||
|
@ -1885,6 +1886,7 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INTERNAL(nsGlobalWindow)
|
|||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mStatusbar)
|
||||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mScrollbars)
|
||||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCrypto)
|
||||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mU2F)
|
||||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mConsole)
|
||||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mExternal)
|
||||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mMozSelfSupport)
|
||||
|
@ -1959,6 +1961,7 @@ NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsGlobalWindow)
|
|||
NS_IMPL_CYCLE_COLLECTION_UNLINK(mStatusbar)
|
||||
NS_IMPL_CYCLE_COLLECTION_UNLINK(mScrollbars)
|
||||
NS_IMPL_CYCLE_COLLECTION_UNLINK(mCrypto)
|
||||
NS_IMPL_CYCLE_COLLECTION_UNLINK(mU2F)
|
||||
NS_IMPL_CYCLE_COLLECTION_UNLINK(mConsole)
|
||||
NS_IMPL_CYCLE_COLLECTION_UNLINK(mExternal)
|
||||
NS_IMPL_CYCLE_COLLECTION_UNLINK(mMozSelfSupport)
|
||||
|
@ -4348,6 +4351,23 @@ nsGlobalWindow::GetCrypto(ErrorResult& aError)
|
|||
return mCrypto;
|
||||
}
|
||||
|
||||
mozilla::dom::U2F*
|
||||
nsGlobalWindow::GetU2f(ErrorResult& aError)
|
||||
{
|
||||
MOZ_RELEASE_ASSERT(IsInnerWindow());
|
||||
|
||||
if (!mU2F) {
|
||||
RefPtr<U2F> u2f = new U2F();
|
||||
u2f->Init(AsInner(), aError);
|
||||
if (NS_WARN_IF(aError.Failed())) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
mU2F = u2f;
|
||||
}
|
||||
return mU2F;
|
||||
}
|
||||
|
||||
nsIControllers*
|
||||
nsGlobalWindow::GetControllersOuter(ErrorResult& aError)
|
||||
{
|
||||
|
|
|
@ -106,7 +106,6 @@ class Crypto;
|
|||
class External;
|
||||
class Function;
|
||||
class Gamepad;
|
||||
class VRDevice;
|
||||
class MediaQueryList;
|
||||
class MozSelfSupport;
|
||||
class Navigator;
|
||||
|
@ -117,6 +116,8 @@ struct RequestInit;
|
|||
class RequestOrUSVString;
|
||||
class Selection;
|
||||
class SpeechSynthesis;
|
||||
class U2F;
|
||||
class VRDevice;
|
||||
class WakeLock;
|
||||
#if defined(MOZ_WIDGET_ANDROID) || defined(MOZ_WIDGET_GONK)
|
||||
class WindowOrientationObserver;
|
||||
|
@ -1071,6 +1072,7 @@ public:
|
|||
void SizeToContentOuter(mozilla::ErrorResult& aError, bool aCallerIsChrome);
|
||||
void SizeToContent(mozilla::ErrorResult& aError);
|
||||
mozilla::dom::Crypto* GetCrypto(mozilla::ErrorResult& aError);
|
||||
mozilla::dom::U2F* GetU2f(mozilla::ErrorResult& aError);
|
||||
nsIControllers* GetControllersOuter(mozilla::ErrorResult& aError);
|
||||
nsIControllers* GetControllers(mozilla::ErrorResult& aError);
|
||||
nsresult GetControllers(nsIControllers** aControllers) override;
|
||||
|
@ -1766,6 +1768,7 @@ protected:
|
|||
nsString mDefaultStatus;
|
||||
RefPtr<nsGlobalWindowObserver> mObserver; // Inner windows only.
|
||||
RefPtr<mozilla::dom::Crypto> mCrypto;
|
||||
RefPtr<mozilla::dom::U2F> mU2F;
|
||||
RefPtr<mozilla::dom::cache::CacheStorage> mCacheStorage;
|
||||
RefPtr<mozilla::dom::Console> mConsole;
|
||||
// We need to store an nsISupports pointer to this object because the
|
||||
|
|
|
@ -2757,22 +2757,27 @@ nsXMLHttpRequest::Send(nsIVariant* aVariant, const Nullable<RequestBody>& aBody)
|
|||
AddLoadFlags(mChannel, nsIChannel::LOAD_EXPLICIT_CREDENTIALS);
|
||||
}
|
||||
|
||||
// When we are sync loading, we need to bypass the local cache when it would
|
||||
// otherwise block us waiting for exclusive access to the cache. If we don't
|
||||
// do this, then we could dead lock in some cases (see bug 309424).
|
||||
//
|
||||
// Also don't block on the cache entry on async if it is busy - favoring parallelism
|
||||
// over cache hit rate for xhr. This does not disable the cache everywhere -
|
||||
// only in cases where more than one channel for the same URI is accessed
|
||||
// simultanously.
|
||||
// Bypass the network cache in cases where it makes no sense:
|
||||
// POST responses are always unique, and we provide no API that would
|
||||
// allow our consumers to specify a "cache key" to access old POST
|
||||
// responses, so they are not worth caching.
|
||||
if (method.EqualsLiteral("POST")) {
|
||||
AddLoadFlags(mChannel,
|
||||
nsICachingChannel::LOAD_BYPASS_LOCAL_CACHE |
|
||||
nsIRequest::INHIBIT_CACHING);
|
||||
} else {
|
||||
// When we are sync loading, we need to bypass the local cache when it would
|
||||
// otherwise block us waiting for exclusive access to the cache. If we don't
|
||||
// do this, then we could dead lock in some cases (see bug 309424).
|
||||
//
|
||||
// Also don't block on the cache entry on async if it is busy - favoring parallelism
|
||||
// over cache hit rate for xhr. This does not disable the cache everywhere -
|
||||
// only in cases where more than one channel for the same URI is accessed
|
||||
// simultanously.
|
||||
|
||||
AddLoadFlags(mChannel,
|
||||
nsICachingChannel::LOAD_BYPASS_LOCAL_CACHE_IF_BUSY);
|
||||
|
||||
// While it would be optimal to bypass the cache in case of POST requests
|
||||
// since they are never cached, our ServiceWorker interception implementation
|
||||
// on single-process systems is implemented via the HTTP cache, so DO NOT
|
||||
// bypass the cache based on method!
|
||||
AddLoadFlags(mChannel,
|
||||
nsICachingChannel::LOAD_BYPASS_LOCAL_CACHE_IF_BUSY);
|
||||
}
|
||||
|
||||
// Since we expect XML data, set the type hint accordingly
|
||||
// if the channel doesn't know any content type.
|
||||
|
|
|
@ -36,7 +36,7 @@ function testFile(file, contents, test) {
|
|||
[{ name: "hello", value: "world"},
|
||||
{ name: "myfile",
|
||||
value: contents,
|
||||
fileName: file.name || "",
|
||||
fileName: file.name || "blob",
|
||||
contentType: file.type || "application/octet-stream" }]);
|
||||
testHasRun();
|
||||
}
|
||||
|
|
|
@ -3247,8 +3247,7 @@ UnprivilegedJunkScopeOrWorkerGlobal()
|
|||
return xpc::UnprivilegedJunkScope();
|
||||
}
|
||||
|
||||
return workers::GetCurrentThreadWorkerPrivate()->
|
||||
GlobalScope()->GetGlobalJSObject();
|
||||
return workers::GetCurrentThreadWorkerGlobal();
|
||||
}
|
||||
} // namespace binding_detail
|
||||
|
||||
|
|
|
@ -1136,11 +1136,13 @@ class CGHeaders(CGWrapper):
|
|||
if desc.interface.maplikeOrSetlikeOrIterable:
|
||||
# We need ToJSValue.h for maplike/setlike type conversions
|
||||
bindingHeaders.add("mozilla/dom/ToJSValue.h")
|
||||
# Add headers for the key and value types of the maplike, since
|
||||
# they'll be needed for convenience functions
|
||||
addHeadersForType((desc.interface.maplikeOrSetlikeOrIterable.keyType,
|
||||
desc, None))
|
||||
if desc.interface.maplikeOrSetlikeOrIterable.valueType:
|
||||
# Add headers for the key and value types of the
|
||||
# maplike/setlike/iterable, since they'll be needed for
|
||||
# convenience functions
|
||||
if desc.interface.maplikeOrSetlikeOrIterable.hasKeyType():
|
||||
addHeadersForType((desc.interface.maplikeOrSetlikeOrIterable.keyType,
|
||||
desc, None))
|
||||
if desc.interface.maplikeOrSetlikeOrIterable.hasValueType():
|
||||
addHeadersForType((desc.interface.maplikeOrSetlikeOrIterable.valueType,
|
||||
desc, None))
|
||||
|
||||
|
@ -2281,34 +2283,49 @@ class MethodDefiner(PropertyDefiner):
|
|||
"condition": MemberCondition()
|
||||
})
|
||||
|
||||
# Generate the maplike/setlike iterator, if one wasn't already
|
||||
# generated by a method. If we already have an @@iterator symbol, fail.
|
||||
if descriptor.interface.maplikeOrSetlikeOrIterable:
|
||||
if hasIterator(methods, self.regular):
|
||||
raise TypeError("Cannot have maplike/setlike/iterable interface with "
|
||||
"other members that generate @@iterator "
|
||||
"on interface %s, such as indexed getters "
|
||||
"or aliased functions." %
|
||||
self.descriptor.interface.identifier.name)
|
||||
for m in methods:
|
||||
if (m.isMaplikeOrSetlikeOrIterableMethod() and
|
||||
(((m.maplikeOrSetlikeOrIterable.isMaplike() or
|
||||
(m.maplikeOrSetlikeOrIterable.isIterable() and
|
||||
m.maplikeOrSetlikeOrIterable.hasValueType())) and
|
||||
m.identifier.name == "entries") or
|
||||
(((m.maplikeOrSetlikeOrIterable.isSetlike() or
|
||||
(m.maplikeOrSetlikeOrIterable.isIterable() and
|
||||
not m.maplikeOrSetlikeOrIterable.hasValueType()))) and
|
||||
m.identifier.name == "values"))):
|
||||
self.regular.append({
|
||||
"name": "@@iterator",
|
||||
"methodName": m.identifier.name,
|
||||
"length": methodLength(m),
|
||||
"flags": "0",
|
||||
"condition": PropertyDefiner.getControllingCondition(m,
|
||||
descriptor),
|
||||
})
|
||||
break
|
||||
# Generate the keys/values/entries aliases for value iterables.
|
||||
maplikeOrSetlikeOrIterable = descriptor.interface.maplikeOrSetlikeOrIterable
|
||||
if (not static and
|
||||
maplikeOrSetlikeOrIterable and
|
||||
maplikeOrSetlikeOrIterable.isIterable() and
|
||||
maplikeOrSetlikeOrIterable.isValueIterator()):
|
||||
# Add our keys/values/entries/forEach
|
||||
self.regular.append({
|
||||
"name": "keys",
|
||||
"methodInfo": False,
|
||||
"selfHostedName": "ArrayKeys",
|
||||
"length": 0,
|
||||
"flags": "JSPROP_ENUMERATE",
|
||||
"condition": PropertyDefiner.getControllingCondition(m,
|
||||
descriptor)
|
||||
})
|
||||
self.regular.append({
|
||||
"name": "values",
|
||||
"methodInfo": False,
|
||||
"selfHostedName": "ArrayValues",
|
||||
"length": 0,
|
||||
"flags": "JSPROP_ENUMERATE",
|
||||
"condition": PropertyDefiner.getControllingCondition(m,
|
||||
descriptor)
|
||||
})
|
||||
self.regular.append({
|
||||
"name": "entries",
|
||||
"methodInfo": False,
|
||||
"selfHostedName": "ArrayEntries",
|
||||
"length": 0,
|
||||
"flags": "JSPROP_ENUMERATE",
|
||||
"condition": PropertyDefiner.getControllingCondition(m,
|
||||
descriptor)
|
||||
})
|
||||
self.regular.append({
|
||||
"name": "forEach",
|
||||
"methodInfo": False,
|
||||
"selfHostedName": "ArrayForEach",
|
||||
"length": 0,
|
||||
"flags": "JSPROP_ENUMERATE",
|
||||
"condition": PropertyDefiner.getControllingCondition(m,
|
||||
descriptor)
|
||||
})
|
||||
|
||||
if not static:
|
||||
stringifier = descriptor.operations['Stringifier']
|
||||
|
@ -13073,8 +13090,9 @@ class CGForwardDeclarations(CGWrapper):
|
|||
# arguments to helper functions, and they'll need to be forward
|
||||
# declared in the header.
|
||||
if d.interface.maplikeOrSetlikeOrIterable:
|
||||
builder.forwardDeclareForType(d.interface.maplikeOrSetlikeOrIterable.keyType,
|
||||
config)
|
||||
if d.interface.maplikeOrSetlikeOrIterable.hasKeyType():
|
||||
builder.forwardDeclareForType(d.interface.maplikeOrSetlikeOrIterable.keyType,
|
||||
config)
|
||||
if d.interface.maplikeOrSetlikeOrIterable.hasValueType():
|
||||
builder.forwardDeclareForType(d.interface.maplikeOrSetlikeOrIterable.valueType,
|
||||
config)
|
||||
|
@ -15802,6 +15820,31 @@ class CGIterableMethodGenerator(CGGeneric):
|
|||
using CGCallGenerator.
|
||||
"""
|
||||
def __init__(self, descriptor, iterable, methodName):
|
||||
if methodName == "forEach":
|
||||
CGGeneric.__init__(self, fill(
|
||||
"""
|
||||
if (!JS::IsCallable(arg0)) {
|
||||
ThrowErrorMessage(cx, MSG_NOT_CALLABLE, "Argument 1 of ${ifaceName}.forEach");
|
||||
return false;
|
||||
}
|
||||
JS::AutoValueArray<3> callArgs(cx);
|
||||
callArgs[2].setObject(*obj);
|
||||
JS::Rooted<JS::Value> ignoredReturnVal(cx);
|
||||
for (size_t i = 0; i < self->GetIterableLength(); ++i) {
|
||||
if (!ToJSValue(cx, self->GetValueAtIndex(i), callArgs[0])) {
|
||||
return false;
|
||||
}
|
||||
if (!ToJSValue(cx, self->GetKeyAtIndex(i), callArgs[1])) {
|
||||
return false;
|
||||
}
|
||||
if (!JS::Call(cx, arg1, arg0, JS::HandleValueArray(callArgs),
|
||||
&ignoredReturnVal)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
""",
|
||||
ifaceName=descriptor.interface.identifier.name))
|
||||
return
|
||||
CGGeneric.__init__(self, fill(
|
||||
"""
|
||||
typedef ${iterClass} itrType;
|
||||
|
|
|
@ -908,8 +908,5 @@ def getAllTypes(descriptors, dictionaries, callbacks):
|
|||
def iteratorNativeType(descriptor):
|
||||
assert descriptor.interface.isIterable()
|
||||
iterableDecl = descriptor.interface.maplikeOrSetlikeOrIterable
|
||||
if iterableDecl.valueType is None:
|
||||
iterClass = "OneTypeIterableIterator"
|
||||
else:
|
||||
iterClass = "TwoTypeIterableIterator"
|
||||
return "mozilla::dom::%s<%s>" % (iterClass, descriptor.nativeType)
|
||||
assert iterableDecl.isPairIterator()
|
||||
return "mozilla::dom::IterableIterator<%s>" % descriptor.nativeType
|
||||
|
|
|
@ -6,19 +6,17 @@
|
|||
|
||||
/**
|
||||
* The IterableIterator class is used for WebIDL interfaces that have a
|
||||
* iterable<> member defined. It handles the ES6 Iterator-like functions that
|
||||
* are generated for the iterable interface.
|
||||
* iterable<> member defined with two types (so a pair iterator). It handles
|
||||
* the ES6 Iterator-like functions that are generated for the iterable
|
||||
* interface.
|
||||
*
|
||||
* For iterable interfaces, the implementation class will need to
|
||||
* implement these two functions:
|
||||
* For iterable interfaces with a pair iterator, the implementation class will
|
||||
* need to implement these two functions:
|
||||
*
|
||||
* - size_t GetIterableLength()
|
||||
* - Returns the number of elements available to iterate over
|
||||
* - [type] GetValueAtIndex(size_t index)
|
||||
* - Returns the value at the requested index.
|
||||
*
|
||||
* If this is a two-type iterator, then the implementation class will also need to implement:
|
||||
*
|
||||
* - [type] GetKeyAtIndex(size_t index)
|
||||
* - Returns the key at the requested index
|
||||
*
|
||||
|
@ -60,13 +58,77 @@ protected:
|
|||
};
|
||||
|
||||
template <typename T>
|
||||
class IterableIterator : public IterableIteratorBase
|
||||
class IterableIterator final : public IterableIteratorBase
|
||||
{
|
||||
public:
|
||||
explicit IterableIterator(T* aIterableObj)
|
||||
typedef bool (*WrapFunc)(JSContext* aCx,
|
||||
IterableIterator<T>* aObject,
|
||||
JS::Handle<JSObject*> aGivenProto,
|
||||
JS::MutableHandle<JSObject*> aReflector);
|
||||
|
||||
explicit IterableIterator(T* aIterableObj,
|
||||
IterableIteratorType aIteratorType,
|
||||
WrapFunc aWrapFunc)
|
||||
: mIterableObj(aIterableObj)
|
||||
, mIteratorType(aIteratorType)
|
||||
, mWrapFunc(aWrapFunc)
|
||||
, mIndex(0)
|
||||
{
|
||||
MOZ_ASSERT(mIterableObj);
|
||||
MOZ_ASSERT(mWrapFunc);
|
||||
}
|
||||
|
||||
void
|
||||
Next(JSContext* aCx, JS::MutableHandle<JSObject*> aResult, ErrorResult& aRv)
|
||||
{
|
||||
JS::Rooted<JS::Value> value(aCx, JS::UndefinedValue());
|
||||
if (mIndex >= this->mIterableObj->GetIterableLength()) {
|
||||
DictReturn(aCx, aResult, true, value, aRv);
|
||||
return;
|
||||
}
|
||||
switch (mIteratorType) {
|
||||
case IterableIteratorType::Keys:
|
||||
{
|
||||
if (!ToJSValue(aCx, this->mIterableObj->GetKeyAtIndex(mIndex), &value)) {
|
||||
aRv.Throw(NS_ERROR_FAILURE);
|
||||
return;
|
||||
}
|
||||
DictReturn(aCx, aResult, false, value, aRv);
|
||||
break;
|
||||
}
|
||||
case IterableIteratorType::Values:
|
||||
{
|
||||
if (!ToJSValue(aCx, this->mIterableObj->GetValueAtIndex(mIndex), &value)) {
|
||||
aRv.Throw(NS_ERROR_FAILURE);
|
||||
return;
|
||||
}
|
||||
DictReturn(aCx, aResult, false, value, aRv);
|
||||
break;
|
||||
}
|
||||
case IterableIteratorType::Entries:
|
||||
{
|
||||
JS::Rooted<JS::Value> key(aCx);
|
||||
if (!ToJSValue(aCx, this->mIterableObj->GetKeyAtIndex(mIndex), &key)) {
|
||||
aRv.Throw(NS_ERROR_FAILURE);
|
||||
return;
|
||||
}
|
||||
if (!ToJSValue(aCx, this->mIterableObj->GetValueAtIndex(mIndex), &value)) {
|
||||
aRv.Throw(NS_ERROR_FAILURE);
|
||||
return;
|
||||
}
|
||||
KeyAndValueReturn(aCx, key, value, aResult, aRv);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
MOZ_CRASH("Invalid iterator type!");
|
||||
}
|
||||
++mIndex;
|
||||
}
|
||||
|
||||
bool
|
||||
WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto, JS::MutableHandle<JSObject*> aObj)
|
||||
{
|
||||
return (*mWrapFunc)(aCx, this, aGivenProto, aObj);
|
||||
}
|
||||
|
||||
protected:
|
||||
|
@ -128,161 +190,6 @@ protected:
|
|||
|
||||
// Binding Implementation object that we're iterating over.
|
||||
RefPtr<T> mIterableObj;
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
class OneTypeIterableIterator final : public IterableIterator<T>
|
||||
{
|
||||
public:
|
||||
typedef typename IterableIterator<T>::IterableIteratorType IterableIteratorType;
|
||||
using IterableIterator<T>::DictReturn;
|
||||
using IterableIterator<T>::KeyAndValueReturn;
|
||||
typedef bool (*WrapFunc)(JSContext* aCx,
|
||||
OneTypeIterableIterator<T>* aObject,
|
||||
JS::Handle<JSObject*> aGivenProto,
|
||||
JS::MutableHandle<JSObject*> aReflector);
|
||||
|
||||
OneTypeIterableIterator(T* aIterableObj,
|
||||
IterableIteratorType aIteratorType,
|
||||
WrapFunc aWrapFunc)
|
||||
: IterableIterator<T>(aIterableObj)
|
||||
, mIteratorType(aIteratorType)
|
||||
, mWrapFunc(aWrapFunc)
|
||||
, mIndex(0)
|
||||
{
|
||||
MOZ_ASSERT(mWrapFunc);
|
||||
}
|
||||
|
||||
void
|
||||
Next(JSContext* aCx, JS::MutableHandle<JSObject*> aResult, ErrorResult& aRv)
|
||||
{
|
||||
JS::Rooted<JS::Value> value(aCx, JS::UndefinedValue());
|
||||
if (mIndex >= this->mIterableObj->GetIterableLength()) {
|
||||
DictReturn(aCx, aResult, true, value, aRv);
|
||||
return;
|
||||
}
|
||||
|
||||
switch (mIteratorType) {
|
||||
case IterableIteratorType::Keys:
|
||||
case IterableIteratorType::Values:
|
||||
{
|
||||
if (!ToJSValue(aCx, this->mIterableObj->GetValueAtIndex(mIndex), &value)) {
|
||||
aRv.Throw(NS_ERROR_FAILURE);
|
||||
return;
|
||||
}
|
||||
DictReturn(aCx, aResult, false, value, aRv);
|
||||
break;
|
||||
}
|
||||
case IterableIteratorType::Entries:
|
||||
{
|
||||
if (!ToJSValue(aCx, this->mIterableObj->GetValueAtIndex(mIndex), &value)) {
|
||||
aRv.Throw(NS_ERROR_FAILURE);
|
||||
return;
|
||||
}
|
||||
KeyAndValueReturn(aCx, value, value, aResult, aRv);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
MOZ_CRASH("Invalid iterator type!");
|
||||
}
|
||||
++mIndex;
|
||||
}
|
||||
|
||||
bool
|
||||
WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto, JS::MutableHandle<JSObject*> aObj)
|
||||
{
|
||||
return (*mWrapFunc)(aCx, this, aGivenProto, aObj);
|
||||
}
|
||||
|
||||
protected:
|
||||
virtual ~OneTypeIterableIterator() {}
|
||||
|
||||
// Tells whether this is a key, value, or entries iterator.
|
||||
IterableIteratorType mIteratorType;
|
||||
// Function pointer to binding-type-specific Wrap() call for this iterator.
|
||||
WrapFunc mWrapFunc;
|
||||
// Current index of iteration.
|
||||
uint32_t mIndex;
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
class TwoTypeIterableIterator final : public IterableIterator<T>
|
||||
{
|
||||
public:
|
||||
typedef typename IterableIterator<T>::IterableIteratorType IterableIteratorType;
|
||||
using IterableIterator<T>::DictReturn;
|
||||
using IterableIterator<T>::KeyAndValueReturn;
|
||||
typedef bool (*WrapFunc)(JSContext* aCx,
|
||||
TwoTypeIterableIterator<T>* aObject,
|
||||
JS::Handle<JSObject*> aGivenProto,
|
||||
JS::MutableHandle<JSObject*> aReflector);
|
||||
|
||||
TwoTypeIterableIterator(T* aIterableObj, IterableIteratorType aIteratorType,
|
||||
WrapFunc aWrapFunc)
|
||||
: IterableIterator<T>(aIterableObj)
|
||||
, mIteratorType(aIteratorType)
|
||||
, mWrapFunc(aWrapFunc)
|
||||
, mIndex(0)
|
||||
{
|
||||
MOZ_ASSERT(mWrapFunc);
|
||||
}
|
||||
|
||||
void
|
||||
Next(JSContext* aCx, JS::MutableHandle<JSObject*> aResult, ErrorResult& aRv)
|
||||
{
|
||||
JS::Rooted<JS::Value> value(aCx, JS::UndefinedValue());
|
||||
if (mIndex >= this->mIterableObj->GetIterableLength()) {
|
||||
DictReturn(aCx, aResult, true, value, aRv);
|
||||
return;
|
||||
}
|
||||
switch (mIteratorType) {
|
||||
case IterableIteratorType::Keys:
|
||||
{
|
||||
if (!ToJSValue(aCx, this->mIterableObj->GetKeyAtIndex(mIndex), &value)) {
|
||||
aRv.Throw(NS_ERROR_FAILURE);
|
||||
return;
|
||||
}
|
||||
DictReturn(aCx, aResult, false, value, aRv);
|
||||
break;
|
||||
}
|
||||
case IterableIteratorType::Values:
|
||||
{
|
||||
if (!ToJSValue(aCx, this->mIterableObj->GetValueAtIndex(mIndex), &value)) {
|
||||
aRv.Throw(NS_ERROR_FAILURE);
|
||||
return;
|
||||
}
|
||||
DictReturn(aCx, aResult, false, value, aRv);
|
||||
break;
|
||||
}
|
||||
case IterableIteratorType::Entries:
|
||||
{
|
||||
JS::Rooted<JS::Value> key(aCx);
|
||||
if (!ToJSValue(aCx, this->mIterableObj->GetKeyAtIndex(mIndex), &key)) {
|
||||
aRv.Throw(NS_ERROR_FAILURE);
|
||||
return;
|
||||
}
|
||||
if (!ToJSValue(aCx, this->mIterableObj->GetValueAtIndex(mIndex), &value)) {
|
||||
aRv.Throw(NS_ERROR_FAILURE);
|
||||
return;
|
||||
}
|
||||
KeyAndValueReturn(aCx, key, value, aResult, aRv);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
MOZ_CRASH("Invalid iterator type!");
|
||||
}
|
||||
++mIndex;
|
||||
}
|
||||
|
||||
bool
|
||||
WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto, JS::MutableHandle<JSObject*> aObj)
|
||||
{
|
||||
return (*mWrapFunc)(aCx, this, aGivenProto, aObj);
|
||||
}
|
||||
|
||||
protected:
|
||||
virtual ~TwoTypeIterableIterator() {}
|
||||
|
||||
// Tells whether this is a key, value, or entries iterator.
|
||||
IterableIteratorType mIteratorType;
|
||||
// Function pointer to binding-type-specific Wrap() call for this iterator.
|
||||
|
|
|
@ -1098,7 +1098,7 @@ class IDLInterface(IDLObjectWithScope, IDLExposureMixins):
|
|||
|
||||
def validate(self):
|
||||
# We don't support consequential unforgeable interfaces. Need to check
|
||||
# this here, becaue in finish() an interface might not know yet that
|
||||
# this here, because in finish() an interface might not know yet that
|
||||
# it's consequential.
|
||||
if self.getExtendedAttribute("Unforgeable") and self.isConsequential():
|
||||
raise WebIDLError(
|
||||
|
@ -1117,6 +1117,8 @@ class IDLInterface(IDLObjectWithScope, IDLExposureMixins):
|
|||
self.identifier.name,
|
||||
locations)
|
||||
|
||||
indexedGetter = None
|
||||
hasLengthAttribute = False
|
||||
for member in self.members:
|
||||
member.validate()
|
||||
|
||||
|
@ -1127,8 +1129,13 @@ class IDLInterface(IDLObjectWithScope, IDLExposureMixins):
|
|||
[self.location, member.location])
|
||||
|
||||
# Check that PutForwards refers to another attribute and that no
|
||||
# cycles exist in forwarded assignments.
|
||||
# cycles exist in forwarded assignments. Also check for a
|
||||
# integer-typed "length" attribute.
|
||||
if member.isAttr():
|
||||
if (member.identifier.name == "length" and
|
||||
member.type.isInteger()):
|
||||
hasLengthAttribute = True
|
||||
|
||||
iface = self
|
||||
attr = member
|
||||
putForwards = attr.getExtendedAttribute("PutForwards")
|
||||
|
@ -1166,8 +1173,11 @@ class IDLInterface(IDLObjectWithScope, IDLExposureMixins):
|
|||
putForwards = attr.getExtendedAttribute("PutForwards")
|
||||
|
||||
# Check that the name of an [Alias] doesn't conflict with an
|
||||
# interface member.
|
||||
# interface member and whether we support indexed properties.
|
||||
if member.isMethod():
|
||||
if member.isGetter() and member.isIndexed():
|
||||
indexedGetter = member
|
||||
|
||||
for alias in member.aliases:
|
||||
if self.isOnGlobalProtoChain():
|
||||
raise WebIDLError("[Alias] must not be used on a "
|
||||
|
@ -1228,6 +1238,35 @@ class IDLInterface(IDLObjectWithScope, IDLExposureMixins):
|
|||
"exposed conditionally",
|
||||
[self.location])
|
||||
|
||||
# Value iterators are only allowed on interfaces with indexed getters,
|
||||
# and pair iterators are only allowed on interfaces without indexed
|
||||
# getters.
|
||||
if self.isIterable():
|
||||
iterableDecl = self.maplikeOrSetlikeOrIterable
|
||||
if iterableDecl.isValueIterator():
|
||||
if not indexedGetter:
|
||||
raise WebIDLError("Interface with value iterator does not "
|
||||
"support indexed properties",
|
||||
[self.location])
|
||||
|
||||
if iterableDecl.valueType != indexedGetter.signatures()[0][0]:
|
||||
raise WebIDLError("Iterable type does not match indexed "
|
||||
"getter type",
|
||||
[iterableDecl.location,
|
||||
indexedGetter.location])
|
||||
|
||||
if not hasLengthAttribute:
|
||||
raise WebIDLError('Interface with value iterator does not '
|
||||
'have an integer-typed "length" attribute',
|
||||
[self.location])
|
||||
else:
|
||||
assert iterableDecl.isPairIterator()
|
||||
if indexedGetter:
|
||||
raise WebIDLError("Interface with pair iterator supports "
|
||||
"indexed properties",
|
||||
[self.location, iterableDecl.location,
|
||||
indexedGetter.location])
|
||||
|
||||
def isInterface(self):
|
||||
return True
|
||||
|
||||
|
@ -3426,7 +3465,10 @@ class IDLMaplikeOrSetlikeOrIterableBase(IDLInterfaceMember):
|
|||
|
||||
def __init__(self, location, identifier, ifaceType, keyType, valueType, ifaceKind):
|
||||
IDLInterfaceMember.__init__(self, location, identifier, ifaceKind)
|
||||
assert isinstance(keyType, IDLType)
|
||||
if keyType is not None:
|
||||
assert isinstance(keyType, IDLType)
|
||||
else:
|
||||
assert valueType is not None
|
||||
assert ifaceType in ['maplike', 'setlike', 'iterable']
|
||||
if valueType is not None:
|
||||
assert isinstance(valueType, IDLType)
|
||||
|
@ -3445,6 +3487,9 @@ class IDLMaplikeOrSetlikeOrIterableBase(IDLInterfaceMember):
|
|||
def isIterable(self):
|
||||
return self.maplikeOrSetlikeOrIterableType == "iterable"
|
||||
|
||||
def hasKeyType(self):
|
||||
return self.keyType is not None
|
||||
|
||||
def hasValueType(self):
|
||||
return self.valueType is not None
|
||||
|
||||
|
@ -3469,7 +3514,8 @@ class IDLMaplikeOrSetlikeOrIterableBase(IDLInterfaceMember):
|
|||
[self.location, member.location])
|
||||
|
||||
def addMethod(self, name, members, allowExistingOperations, returnType, args=[],
|
||||
chromeOnly=False, isPure=False, affectsNothing=False, newObject=False):
|
||||
chromeOnly=False, isPure=False, affectsNothing=False, newObject=False,
|
||||
isIteratorAlias=False):
|
||||
"""
|
||||
Create an IDLMethod based on the parameters passed in.
|
||||
|
||||
|
@ -3529,16 +3575,20 @@ class IDLMaplikeOrSetlikeOrIterableBase(IDLInterfaceMember):
|
|||
if newObject:
|
||||
method.addExtendedAttributes(
|
||||
[IDLExtendedAttribute(self.location, ("NewObject",))])
|
||||
if isIteratorAlias:
|
||||
method.addExtendedAttributes(
|
||||
[IDLExtendedAttribute(self.location, ("Alias", "@@iterator"))])
|
||||
members.append(method)
|
||||
|
||||
def resolve(self, parentScope):
|
||||
self.keyType.resolveType(parentScope)
|
||||
if self.keyType:
|
||||
self.keyType.resolveType(parentScope)
|
||||
if self.valueType:
|
||||
self.valueType.resolveType(parentScope)
|
||||
|
||||
def finish(self, scope):
|
||||
IDLInterfaceMember.finish(self, scope)
|
||||
if not self.keyType.isComplete():
|
||||
if self.keyType and not self.keyType.isComplete():
|
||||
t = self.keyType.complete(scope)
|
||||
|
||||
assert not isinstance(t, IDLUnresolvedType)
|
||||
|
@ -3560,9 +3610,23 @@ class IDLMaplikeOrSetlikeOrIterableBase(IDLInterfaceMember):
|
|||
IDLInterfaceMember.handleExtendedAttribute(self, attr)
|
||||
|
||||
def _getDependentObjects(self):
|
||||
deps = set()
|
||||
if self.keyType:
|
||||
deps.add(self.keyType)
|
||||
if self.valueType:
|
||||
return set([self.keyType, self.valueType])
|
||||
return set([self.keyType])
|
||||
deps.add(self.valueType)
|
||||
return deps
|
||||
|
||||
def getForEachArguments(self):
|
||||
return [IDLArgument(self.location,
|
||||
IDLUnresolvedIdentifier(BuiltinLocation("<auto-generated-identifier>"),
|
||||
"callback"),
|
||||
BuiltinTypes[IDLBuiltinType.Types.object]),
|
||||
IDLArgument(self.location,
|
||||
IDLUnresolvedIdentifier(BuiltinLocation("<auto-generated-identifier>"),
|
||||
"thisArg"),
|
||||
BuiltinTypes[IDLBuiltinType.Types.any],
|
||||
optional=True)]
|
||||
|
||||
# Iterable adds ES6 iterator style functions and traits
|
||||
# (keys/values/entries/@@iterator) to an interface.
|
||||
|
@ -3583,9 +3647,15 @@ class IDLIterable(IDLMaplikeOrSetlikeOrIterableBase):
|
|||
we generate our functions as if they were part of the interface
|
||||
specification during parsing.
|
||||
"""
|
||||
# We only need to add entries/keys/values here if we're a pair iterator.
|
||||
# Value iterators just copy these from %ArrayPrototype% instead.
|
||||
if not self.isPairIterator():
|
||||
return
|
||||
|
||||
# object entries()
|
||||
self.addMethod("entries", members, False, self.iteratorType,
|
||||
affectsNothing=True, newObject=True)
|
||||
affectsNothing=True, newObject=True,
|
||||
isIteratorAlias=True)
|
||||
# object keys()
|
||||
self.addMethod("keys", members, False, self.iteratorType,
|
||||
affectsNothing=True, newObject=True)
|
||||
|
@ -3593,6 +3663,17 @@ class IDLIterable(IDLMaplikeOrSetlikeOrIterableBase):
|
|||
self.addMethod("values", members, False, self.iteratorType,
|
||||
affectsNothing=True, newObject=True)
|
||||
|
||||
# void forEach(callback(valueType, keyType), optional any thisArg)
|
||||
self.addMethod("forEach", members, False,
|
||||
BuiltinTypes[IDLBuiltinType.Types.void],
|
||||
self.getForEachArguments())
|
||||
|
||||
def isValueIterator(self):
|
||||
return not self.isPairIterator()
|
||||
|
||||
def isPairIterator(self):
|
||||
return self.hasKeyType()
|
||||
|
||||
# MaplikeOrSetlike adds ES6 map-or-set-like traits to an interface.
|
||||
class IDLMaplikeOrSetlike(IDLMaplikeOrSetlikeOrIterableBase):
|
||||
|
||||
|
@ -3629,26 +3710,17 @@ class IDLMaplikeOrSetlike(IDLMaplikeOrSetlikeOrIterableBase):
|
|||
|
||||
# object entries()
|
||||
self.addMethod("entries", members, False, BuiltinTypes[IDLBuiltinType.Types.object],
|
||||
affectsNothing=True)
|
||||
affectsNothing=True, isIteratorAlias=self.isMaplike())
|
||||
# object keys()
|
||||
self.addMethod("keys", members, False, BuiltinTypes[IDLBuiltinType.Types.object],
|
||||
affectsNothing=True)
|
||||
# object values()
|
||||
self.addMethod("values", members, False, BuiltinTypes[IDLBuiltinType.Types.object],
|
||||
affectsNothing=True)
|
||||
affectsNothing=True, isIteratorAlias=self.isSetlike())
|
||||
|
||||
# void forEach(callback(valueType, keyType), thisVal)
|
||||
foreachArguments = [IDLArgument(self.location,
|
||||
IDLUnresolvedIdentifier(BuiltinLocation("<auto-generated-identifier>"),
|
||||
"callback"),
|
||||
BuiltinTypes[IDLBuiltinType.Types.object]),
|
||||
IDLArgument(self.location,
|
||||
IDLUnresolvedIdentifier(BuiltinLocation("<auto-generated-identifier>"),
|
||||
"thisArg"),
|
||||
BuiltinTypes[IDLBuiltinType.Types.any],
|
||||
optional=True)]
|
||||
self.addMethod("forEach", members, False, BuiltinTypes[IDLBuiltinType.Types.void],
|
||||
foreachArguments)
|
||||
self.getForEachArguments())
|
||||
|
||||
def getKeyArg():
|
||||
return IDLArgument(self.location,
|
||||
|
@ -5438,10 +5510,13 @@ class Parser(Tokenizer):
|
|||
location = self.getLocation(p, 2)
|
||||
identifier = IDLUnresolvedIdentifier(location, "__iterable",
|
||||
allowDoubleUnderscore=True)
|
||||
keyType = p[3]
|
||||
valueType = None
|
||||
if (len(p) > 6):
|
||||
keyType = p[3]
|
||||
valueType = p[5]
|
||||
else:
|
||||
keyType = None
|
||||
valueType = p[3]
|
||||
|
||||
p[0] = IDLIterable(location, identifier, keyType, valueType, self.globalScope())
|
||||
|
||||
def p_Setlike(self, p):
|
||||
|
@ -6511,7 +6586,7 @@ class Parser(Tokenizer):
|
|||
if isinstance(m, IDLIterable):
|
||||
iterable = m
|
||||
break
|
||||
if iterable:
|
||||
if iterable and iterable.isPairIterator():
|
||||
def simpleExtendedAttr(str):
|
||||
return IDLExtendedAttribute(iface.location, (str, ))
|
||||
nextMethod = IDLMethod(
|
||||
|
|
|
@ -45,8 +45,8 @@ def WebIDLTest(parser, harness):
|
|||
prefix + " - Interface failed but not as a WebIDLError exception: %s" % e)
|
||||
|
||||
iterableMembers = [(x, WebIDL.IDLMethod) for x in ["entries", "keys",
|
||||
"values"]]
|
||||
setROMembers = ([(x, WebIDL.IDLMethod) for x in ["has", "forEach"]] +
|
||||
"values", "forEach"]]
|
||||
setROMembers = ([(x, WebIDL.IDLMethod) for x in ["has"]] +
|
||||
[("__setlike", WebIDL.IDLMaplikeOrSetlike)] +
|
||||
iterableMembers)
|
||||
setROMembers.extend([("size", WebIDL.IDLAttribute)])
|
||||
|
@ -62,7 +62,7 @@ def WebIDLTest(parser, harness):
|
|||
"__clear",
|
||||
"__delete"]] +
|
||||
setRWMembers)
|
||||
mapROMembers = ([(x, WebIDL.IDLMethod) for x in ["get", "has", "forEach"]] +
|
||||
mapROMembers = ([(x, WebIDL.IDLMethod) for x in ["get", "has"]] +
|
||||
[("__maplike", WebIDL.IDLMaplikeOrSetlike)] +
|
||||
iterableMembers)
|
||||
mapROMembers.extend([("size", WebIDL.IDLAttribute)])
|
||||
|
@ -78,6 +78,10 @@ def WebIDLTest(parser, harness):
|
|||
# __iterable to it for the iterable<> case.
|
||||
iterableMembers.append(("__iterable", WebIDL.IDLIterable))
|
||||
|
||||
valueIterableMembers = [("__iterable", WebIDL.IDLIterable)]
|
||||
valueIterableMembers.append(("__indexedgetter", WebIDL.IDLMethod))
|
||||
valueIterableMembers.append(("length", WebIDL.IDLAttribute))
|
||||
|
||||
disallowedIterableNames = ["keys", "entries", "values"]
|
||||
disallowedMemberNames = ["forEach", "has", "size"] + disallowedIterableNames
|
||||
mapDisallowedMemberNames = ["get"] + disallowedMemberNames
|
||||
|
@ -93,10 +97,10 @@ def WebIDLTest(parser, harness):
|
|||
"""
|
||||
interface Foo1 {
|
||||
iterable<long>;
|
||||
readonly attribute unsigned long length;
|
||||
getter long(unsigned long index);
|
||||
};
|
||||
""", iterableMembers,
|
||||
# numProductions == 2 because of the generated iterator iface,
|
||||
numProductions=2)
|
||||
""", valueIterableMembers)
|
||||
|
||||
shouldPass("Iterable (key and value)",
|
||||
"""
|
||||
|
|
|
@ -23,7 +23,7 @@ NS_INTERFACE_MAP_END
|
|||
TestInterfaceIterableSingle::TestInterfaceIterableSingle(nsPIDOMWindowInner* aParent)
|
||||
: mParent(aParent)
|
||||
{
|
||||
for(int i = 0; i < 3; ++i) {
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
mValues.AppendElement(i);
|
||||
}
|
||||
}
|
||||
|
@ -55,17 +55,22 @@ TestInterfaceIterableSingle::GetParentObject() const
|
|||
return mParent;
|
||||
}
|
||||
|
||||
size_t
|
||||
TestInterfaceIterableSingle::GetIterableLength() const
|
||||
uint32_t
|
||||
TestInterfaceIterableSingle::Length() const
|
||||
{
|
||||
return mValues.Length();
|
||||
}
|
||||
|
||||
uint32_t
|
||||
TestInterfaceIterableSingle::GetValueAtIndex(uint32_t index) const
|
||||
int32_t
|
||||
TestInterfaceIterableSingle::IndexedGetter(uint32_t aIndex, bool& aFound) const
|
||||
{
|
||||
MOZ_ASSERT(index < mValues.Length());
|
||||
return mValues.ElementAt(index);
|
||||
if (aIndex >= mValues.Length()) {
|
||||
aFound = false;
|
||||
return 0;
|
||||
}
|
||||
|
||||
aFound = true;
|
||||
return mValues[aIndex];
|
||||
}
|
||||
|
||||
} // namespace dom
|
||||
|
|
|
@ -36,12 +36,13 @@ public:
|
|||
static already_AddRefed<TestInterfaceIterableSingle>
|
||||
Constructor(const GlobalObject& aGlobal, ErrorResult& rv);
|
||||
|
||||
size_t GetIterableLength() const;
|
||||
uint32_t GetValueAtIndex(uint32_t aIndex) const;
|
||||
uint32_t Length() const;
|
||||
int32_t IndexedGetter(uint32_t aIndex, bool& aFound) const;
|
||||
|
||||
private:
|
||||
virtual ~TestInterfaceIterableSingle() {}
|
||||
nsCOMPtr<nsPIDOMWindowInner> mParent;
|
||||
nsTArray<uint32_t> mValues;
|
||||
nsTArray<int32_t> mValues;
|
||||
};
|
||||
|
||||
} // namespace dom
|
||||
|
|
|
@ -71,4 +71,5 @@ skip-if = debug == false
|
|||
[test_jsimplemented_eventhandler.html]
|
||||
skip-if = debug == false
|
||||
[test_iterable.html]
|
||||
skip-if = debug == false
|
||||
skip-if = debug == false
|
||||
[test_oom_reporting.html]
|
||||
|
|
|
@ -93,7 +93,8 @@
|
|||
is(e[1], 1, "SimpleMap: iterable second array element should be value");
|
||||
}
|
||||
is(m[Symbol.iterator].length, 0, "SimpleMap: @@iterator symbol is correct length");
|
||||
is(m[Symbol.iterator].name, "[Symbol.iterator]", "SimpleMap: @@iterator symbol has correct name");
|
||||
is(m[Symbol.iterator].name, "entries", "SimpleMap: @@iterator symbol has correct name");
|
||||
is(m[Symbol.iterator], m.entries, 'SimpleMap: @@iterator is an alias for "entries"');
|
||||
ok(iterable, "SimpleMap: @@iterator symbol resolved correctly");
|
||||
for (var k of m.keys()) {
|
||||
is(k, "test", "SimpleMap: first keys element should be 'test'");
|
||||
|
@ -139,7 +140,8 @@
|
|||
is(e, "test", "SimpleSet: iterable first array element should be key");
|
||||
}
|
||||
is(m[Symbol.iterator].length, 0, "SimpleSet: @@iterator symbol is correct length");
|
||||
is(m[Symbol.iterator].name, "[Symbol.iterator]", "SimpleSet: @@iterator symbol has correct name");
|
||||
is(m[Symbol.iterator].name, "values", "SimpleSet: @@iterator symbol has correct name");
|
||||
is(m[Symbol.iterator], m.values, 'SimpleSet: @@iterator is an alias for "values"');
|
||||
ok(iterable, "SimpleSet: @@iterator symbol resolved correctly");
|
||||
for (var k of m.keys()) {
|
||||
is(k, "test", "SimpleSet: first keys element should be 'test'");
|
||||
|
|
|
@ -14,7 +14,8 @@
|
|||
|
||||
base_properties = [["entries", "function", 0],
|
||||
["keys", "function", 0],
|
||||
["values", "function", 0]]
|
||||
["values", "function", 0],
|
||||
["forEach", "function", 1]]
|
||||
var testExistence = function testExistence(prefix, obj, properties) {
|
||||
for (var [name, type, args] of properties) {
|
||||
// Properties are somewhere up the proto chain, hasOwnProperty won't work
|
||||
|
@ -50,6 +51,16 @@
|
|||
info("IterableSingle: Testing simple iterable creation and functionality");
|
||||
itr = new TestInterfaceIterableSingle();
|
||||
testExistence("IterableSingle: ", itr, base_properties);
|
||||
is(itr[Symbol.iterator], Array.prototype[Symbol.iterator],
|
||||
"IterableSingle: Should be using %ArrayIterator% for @@iterator");
|
||||
is(itr.keys, Array.prototype.keys,
|
||||
"IterableSingle: Should be using %ArrayIterator% for 'keys'");
|
||||
is(itr.entries, Array.prototype.entries,
|
||||
"IterableSingle: Should be using %ArrayIterator% for 'entries'");
|
||||
is(itr.values, itr[Symbol.iterator],
|
||||
"IterableSingle: Should be using @@iterator for 'values'");
|
||||
is(itr.forEach, Array.prototype.forEach,
|
||||
"IterableSingle: Should be using %ArrayIterator% for 'forEach'");
|
||||
var keys = [...itr.keys()];
|
||||
var values = [...itr.values()];
|
||||
var entries = [...itr.entries()];
|
||||
|
@ -73,6 +84,23 @@
|
|||
is(entry.value[1], entries[i][1],
|
||||
"IterableSingle: Entry iterator value 1 should match destructuring " + i);
|
||||
}
|
||||
|
||||
var callsToForEachCallback = 0;
|
||||
var thisArg = {};
|
||||
itr.forEach(function(value, index, obj) {
|
||||
is(index, callsToForEachCallback,
|
||||
`IterableSingle: Should have the right index at ${callsToForEachCallback} calls to forEach callback`);
|
||||
is(value, values[index],
|
||||
`IterableSingle: Should have the right value at ${callsToForEachCallback} calls to forEach callback`);
|
||||
is(this, thisArg,
|
||||
"IterableSingle: Should have the right this value for forEach callback");
|
||||
is(obj, itr,
|
||||
"IterableSingle: Should have the right third arg for forEach callback");
|
||||
++callsToForEachCallback;
|
||||
}, thisArg);
|
||||
is(callsToForEachCallback, 3,
|
||||
"IterableSingle: Should have right total number of calls to forEach callback");
|
||||
|
||||
var key = key_itr.next();
|
||||
var value = value_itr.next();
|
||||
var entry = entries_itr.next();
|
||||
|
@ -83,13 +111,15 @@
|
|||
is(entry.value, undefined, "IterableDouble: Entry iterator value should be undefined");
|
||||
is(entry.done, true, "IterableSingle: Entry iterator done should be true");
|
||||
is(Object.prototype.toString.call(Object.getPrototypeOf(key_itr)),
|
||||
"[object TestInterfaceIterableSingleIteratorPrototype]",
|
||||
"[object Array Iterator]",
|
||||
"iterator prototype should have the right brand");
|
||||
|
||||
// Simple dual type iterable creation and functionality test
|
||||
info("IterableDouble: Testing simple iterable creation and functionality");
|
||||
itr = new TestInterfaceIterableDouble();
|
||||
testExistence("IterableDouble: ", itr, base_properties);
|
||||
is(itr.entries, itr[Symbol.iterator],
|
||||
"IterableDouble: Should be using @@iterator for 'entries'");
|
||||
var elements = [["a", "b"], ["c", "d"], ["e", "f"]]
|
||||
var keys = [...itr.keys()];
|
||||
var values = [...itr.values()];
|
||||
|
@ -114,6 +144,23 @@
|
|||
is(entry.value[1], entries[i][1],
|
||||
"IterableDouble: Entry iterator value 1 should match destructuring " + i);
|
||||
}
|
||||
|
||||
callsToForEachCallback = 0;
|
||||
thisArg = {};
|
||||
itr.forEach(function(value, key, obj) {
|
||||
is(key, keys[callsToForEachCallback],
|
||||
`IterableDouble: Should have the right key at ${callsToForEachCallback} calls to forEach callback`);
|
||||
is(value, values[callsToForEachCallback],
|
||||
`IterableDouble: Should have the right value at ${callsToForEachCallback} calls to forEach callback`);
|
||||
is(this, thisArg,
|
||||
"IterableDouble: Should have the right this value for forEach callback");
|
||||
is(obj, itr,
|
||||
"IterableSingle: Should have the right third arg for forEach callback");
|
||||
++callsToForEachCallback;
|
||||
}, thisArg);
|
||||
is(callsToForEachCallback, 3,
|
||||
"IterableDouble: Should have right total number of calls to forEach callback");
|
||||
|
||||
var key = key_itr.next();
|
||||
var value = value_itr.next();
|
||||
var entry = entries_itr.next()
|
||||
|
|
|
@ -0,0 +1,42 @@
|
|||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<!--
|
||||
https://bugzilla.mozilla.org/show_bug.cgi?id=
|
||||
-->
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Test for Bug </title>
|
||||
<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
|
||||
<script type="application/javascript">
|
||||
|
||||
/** Test for Bug **/
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
|
||||
window.length = 0xffffffff;
|
||||
|
||||
SimpleTest.expectUncaughtException();
|
||||
setTimeout(Array.prototype.splice, 0, undefined);
|
||||
|
||||
addEventListener("error", function(e) {
|
||||
is(e.type, "error", "Should have an error event");
|
||||
is(e.message, "uncaught exception: out of memory",
|
||||
"Should have the right error message");
|
||||
// Make sure we finish async, in case the expectUncaughtException assertion
|
||||
// about having seen the exception runs after our listener
|
||||
SimpleTest.executeSoon(SimpleTest.finish);
|
||||
});
|
||||
|
||||
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=">Mozilla Bug </a>
|
||||
<p id="display"></p>
|
||||
<div id="content" style="display: none">
|
||||
|
||||
</div>
|
||||
<pre id="test">
|
||||
</pre>
|
||||
</body>
|
||||
</html>
|
|
@ -27,6 +27,13 @@ CryptoBuffer::Assign(const uint8_t* aData, uint32_t aLength)
|
|||
return ReplaceElementsAt(0, Length(), aData, aLength, fallible);
|
||||
}
|
||||
|
||||
uint8_t*
|
||||
CryptoBuffer::Assign(const nsACString& aString)
|
||||
{
|
||||
return Assign(reinterpret_cast<uint8_t const*>(aString.BeginReading()),
|
||||
aString.Length());
|
||||
}
|
||||
|
||||
uint8_t*
|
||||
CryptoBuffer::Assign(const SECItem* aItem)
|
||||
{
|
||||
|
|
|
@ -22,6 +22,7 @@ class CryptoBuffer : public FallibleTArray<uint8_t>
|
|||
public:
|
||||
uint8_t* Assign(const CryptoBuffer& aData);
|
||||
uint8_t* Assign(const uint8_t* aData, uint32_t aLength);
|
||||
uint8_t* Assign(const nsACString& aString);
|
||||
uint8_t* Assign(const SECItem* aItem);
|
||||
uint8_t* Assign(const ArrayBuffer& aData);
|
||||
uint8_t* Assign(const ArrayBufferView& aData);
|
||||
|
|
|
@ -882,13 +882,19 @@ WindowsGamepadService::Cleanup()
|
|||
mXInputPollTimer->Cancel();
|
||||
}
|
||||
mGamepads.Clear();
|
||||
if (mObserver) {
|
||||
mObserver->Stop();
|
||||
mObserver = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
WindowsGamepadService::DevicesChanged(DeviceChangeType type)
|
||||
{
|
||||
if (type == DeviceChangeNotification) {
|
||||
mObserver->SetDeviceChangeTimer();
|
||||
if (mObserver) {
|
||||
mObserver->SetDeviceChangeTimer();
|
||||
}
|
||||
} else if (type == DeviceChangeStable) {
|
||||
ScanForDevices();
|
||||
}
|
||||
|
|
|
@ -162,7 +162,7 @@ function testSend(doneCb) {
|
|||
}
|
||||
|
||||
is(response[0].headers['Content-Disposition'],
|
||||
'form-data; name="empty"; filename=""');
|
||||
'form-data; name="empty"; filename="blob"');
|
||||
|
||||
is(response[1].headers['Content-Disposition'],
|
||||
'form-data; name="explicit"; filename="explicit-file-name"');
|
||||
|
|
|
@ -590,7 +590,7 @@ var expectedAugment = [
|
|||
//{ name: "aNameUndef", value: "undefined" },
|
||||
];
|
||||
|
||||
function checkMPSubmission(sub, expected, test) {
|
||||
function checkMPSubmission(sub, expected, test, isFormData = false) {
|
||||
function getPropCount(o) {
|
||||
var x, l = 0;
|
||||
for (x in o) ++l;
|
||||
|
@ -625,7 +625,7 @@ function checkMPSubmission(sub, expected, test) {
|
|||
else {
|
||||
is(sub[i].headers["Content-Disposition"],
|
||||
"form-data; name=\"" + mpquote(expected[i].name) + "\"; filename=\"" +
|
||||
mpquote(expected[i].fileName) + "\"",
|
||||
mpquote(expected[i].fileName != "" || !isFormData ? expected[i].fileName : "blob") + "\"",
|
||||
"Correct name in " + test);
|
||||
is(sub[i].headers["Content-Type"],
|
||||
expected[i].contentType,
|
||||
|
@ -782,14 +782,14 @@ function runTest() {
|
|||
xhr.open("POST", "form_submit_server.sjs");
|
||||
xhr.send(new FormData(form));
|
||||
yield undefined; // Wait for XHR load
|
||||
checkMPSubmission(JSON.parse(xhr.responseText), expectedSub, "send form using XHR and FormData");
|
||||
checkMPSubmission(JSON.parse(xhr.responseText), expectedSub, "send form using XHR and FormData", true);
|
||||
|
||||
// Send disabled form using XHR and FormData
|
||||
setDisabled(document.querySelectorAll("input, select, textarea"), true);
|
||||
xhr.open("POST", "form_submit_server.sjs");
|
||||
xhr.send(new FormData(form));
|
||||
yield undefined;
|
||||
checkMPSubmission(JSON.parse(xhr.responseText), [], "send disabled form using XHR and FormData");
|
||||
checkMPSubmission(JSON.parse(xhr.responseText), [], "send disabled form using XHR and FormData", true);
|
||||
setDisabled(document.querySelectorAll("input, select, textarea"), false);
|
||||
|
||||
// Send FormData
|
||||
|
@ -804,7 +804,7 @@ function runTest() {
|
|||
xhr.open("POST", "form_submit_server.sjs");
|
||||
xhr.send(fd);
|
||||
yield undefined;
|
||||
checkMPSubmission(JSON.parse(xhr.responseText), expectedAugment, "send FormData");
|
||||
checkMPSubmission(JSON.parse(xhr.responseText), expectedAugment, "send FormData", true);
|
||||
|
||||
// Augment <form> using FormData
|
||||
fd = new FormData(form);
|
||||
|
@ -813,7 +813,7 @@ function runTest() {
|
|||
xhr.send(fd);
|
||||
yield undefined;
|
||||
checkMPSubmission(JSON.parse(xhr.responseText),
|
||||
expectedSub.concat(expectedAugment), "send augmented FormData");
|
||||
expectedSub.concat(expectedAugment), "send augmented FormData", true);
|
||||
|
||||
SimpleTest.finish();
|
||||
yield undefined;
|
||||
|
|
|
@ -44,6 +44,7 @@
|
|||
#include "mozilla/ipc/GeckoChildProcessHost.h"
|
||||
#include "mozilla/ipc/TestShellChild.h"
|
||||
#include "mozilla/jsipc/CrossProcessObjectWrappers.h"
|
||||
#include "mozilla/layers/APZChild.h"
|
||||
#include "mozilla/layers/CompositorChild.h"
|
||||
#include "mozilla/layers/ImageBridgeChild.h"
|
||||
#include "mozilla/layers/PCompositorChild.h"
|
||||
|
@ -1260,6 +1261,19 @@ ContentChild::AllocPGMPServiceChild(mozilla::ipc::Transport* aTransport,
|
|||
return GMPServiceChild::Create(aTransport, aOtherProcess);
|
||||
}
|
||||
|
||||
PAPZChild*
|
||||
ContentChild::AllocPAPZChild(const TabId& aTabId)
|
||||
{
|
||||
return APZChild::Create(aTabId);
|
||||
}
|
||||
|
||||
bool
|
||||
ContentChild::DeallocPAPZChild(PAPZChild* aActor)
|
||||
{
|
||||
delete aActor;
|
||||
return true;
|
||||
}
|
||||
|
||||
PCompositorChild*
|
||||
ContentChild::AllocPCompositorChild(mozilla::ipc::Transport* aTransport,
|
||||
base::ProcessId aOtherProcess)
|
||||
|
|
|
@ -144,6 +144,11 @@ public:
|
|||
AllocPGMPServiceChild(mozilla::ipc::Transport* transport,
|
||||
base::ProcessId otherProcess) override;
|
||||
|
||||
PAPZChild*
|
||||
AllocPAPZChild(const TabId& aTabId) override;
|
||||
bool
|
||||
DeallocPAPZChild(PAPZChild* aActor) override;
|
||||
|
||||
PCompositorChild*
|
||||
AllocPCompositorChild(mozilla::ipc::Transport* aTransport,
|
||||
base::ProcessId aOtherProcess) override;
|
||||
|
|
|
@ -83,6 +83,7 @@
|
|||
#include "mozilla/ipc/TestShellParent.h"
|
||||
#include "mozilla/ipc/InputStreamUtils.h"
|
||||
#include "mozilla/jsipc/CrossProcessObjectWrappers.h"
|
||||
#include "mozilla/layers/PAPZParent.h"
|
||||
#include "mozilla/layers/CompositorParent.h"
|
||||
#include "mozilla/layers/ImageBridgeParent.h"
|
||||
#include "mozilla/layers/SharedBufferManagerParent.h"
|
||||
|
@ -2028,11 +2029,61 @@ NestedBrowserLayerIds()
|
|||
}
|
||||
} // namespace
|
||||
|
||||
/* static */
|
||||
bool
|
||||
ContentParent::RecvAllocateLayerTreeId(uint64_t* aId)
|
||||
ContentParent::AllocateLayerTreeId(TabParent* aTabParent, uint64_t* aId)
|
||||
{
|
||||
return AllocateLayerTreeId(aTabParent->Manager()->AsContentParent(),
|
||||
aTabParent, aTabParent->GetTabId(), aId);
|
||||
}
|
||||
|
||||
/* static */
|
||||
bool
|
||||
ContentParent::AllocateLayerTreeId(ContentParent* aContent,
|
||||
TabParent* aTopLevel, const TabId& aTabId,
|
||||
uint64_t* aId)
|
||||
{
|
||||
*aId = CompositorParent::AllocateLayerTreeId();
|
||||
|
||||
if (!gfxPlatform::AsyncPanZoomEnabled()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!aContent || !aTopLevel) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return CompositorParent::UpdateRemoteContentController(*aId, aContent,
|
||||
aTabId, aTopLevel);
|
||||
}
|
||||
|
||||
bool
|
||||
ContentParent::RecvAllocateLayerTreeId(const ContentParentId& aCpId,
|
||||
const TabId& aTabId, uint64_t* aId)
|
||||
{
|
||||
// Protect against spoofing by a compromised child. aCpId must either
|
||||
// correspond to the process that this ContentParent represents or be a
|
||||
// child of it.
|
||||
ContentProcessManager* cpm = ContentProcessManager::GetSingleton();
|
||||
if (ChildID() != aCpId) {
|
||||
ContentParentId parent;
|
||||
if (!cpm->GetParentProcessId(aCpId, &parent) ||
|
||||
ChildID() != parent) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// GetTopLevelTabParentByProcessAndTabId will make sure that aTabId
|
||||
// lives in the process for aCpId.
|
||||
RefPtr<ContentParent> contentParent = cpm->GetContentProcessById(aCpId);
|
||||
RefPtr<TabParent> browserParent =
|
||||
cpm->GetTopLevelTabParentByProcessAndTabId(aCpId, aTabId);
|
||||
MOZ_ASSERT(contentParent && browserParent);
|
||||
|
||||
if (!AllocateLayerTreeId(contentParent, browserParent, aTabId, aId)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto iter = NestedBrowserLayerIds().find(this);
|
||||
if (iter == NestedBrowserLayerIds().end()) {
|
||||
std::set<uint64_t> ids;
|
||||
|
@ -3398,6 +3449,21 @@ ContentParent::AllocPGMPServiceParent(mozilla::ipc::Transport* aTransport,
|
|||
return GMPServiceParent::Create(aTransport, aOtherProcess);
|
||||
}
|
||||
|
||||
PAPZParent*
|
||||
ContentParent::AllocPAPZParent(const TabId& aTabId)
|
||||
{
|
||||
// The PAPZParent should just be created in the main process and then an IPDL
|
||||
// constructor message sent to hook it up.
|
||||
MOZ_CRASH("This shouldn't be called");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool
|
||||
ContentParent::DeallocPAPZParent(PAPZParent* aActor)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
PCompositorParent*
|
||||
ContentParent::AllocPCompositorParent(mozilla::ipc::Transport* aTransport,
|
||||
base::ProcessId aOtherProcess)
|
||||
|
@ -5697,6 +5763,18 @@ ContentParent::RecvGetGraphicsDeviceInitData(DeviceInitData* aOut)
|
|||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
ContentParent::RecvGraphicsError(const nsCString& aError)
|
||||
{
|
||||
gfx::LogForwarder* lf = gfx::Factory::GetLogForwarder();
|
||||
if (lf) {
|
||||
std::stringstream message;
|
||||
message << "CP+" << aError.get();
|
||||
lf->UpdateStringsVector(message.str());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
ContentParent::RecvBeginDriverCrashGuard(const uint32_t& aGuardType, bool* aOutCrashed)
|
||||
{
|
||||
|
|
|
@ -506,6 +506,8 @@ public:
|
|||
InfallibleTArray<FrameScriptInfo>* aFrameScripts,
|
||||
nsCString* aURLToLoad) override;
|
||||
|
||||
static bool AllocateLayerTreeId(TabParent* aTabParent, uint64_t* aId);
|
||||
|
||||
protected:
|
||||
void OnChannelConnected(int32_t pid) override;
|
||||
|
||||
|
@ -650,10 +652,19 @@ private:
|
|||
|
||||
static void ForceKillTimerCallback(nsITimer* aTimer, void* aClosure);
|
||||
|
||||
static bool AllocateLayerTreeId(ContentParent* aContent,
|
||||
TabParent* aTopLevel, const TabId& aTabId,
|
||||
uint64_t* aId);
|
||||
|
||||
PGMPServiceParent*
|
||||
AllocPGMPServiceParent(mozilla::ipc::Transport* aTransport,
|
||||
base::ProcessId aOtherProcess) override;
|
||||
|
||||
PAPZParent*
|
||||
AllocPAPZParent(const TabId& aTabId) override;
|
||||
bool
|
||||
DeallocPAPZParent(PAPZParent* aActor) override;
|
||||
|
||||
PCompositorParent*
|
||||
AllocPCompositorParent(mozilla::ipc::Transport* aTransport,
|
||||
base::ProcessId aOtherProcess) override;
|
||||
|
@ -1004,7 +1015,9 @@ private:
|
|||
|
||||
virtual void ProcessingError(Result aCode, const char* aMsgName) override;
|
||||
|
||||
virtual bool RecvAllocateLayerTreeId(uint64_t* aId) override;
|
||||
virtual bool RecvAllocateLayerTreeId(const ContentParentId& aCpId,
|
||||
const TabId& aTabId,
|
||||
uint64_t* aId) override;
|
||||
|
||||
virtual bool RecvDeallocateLayerTreeId(const uint64_t& aId) override;
|
||||
|
||||
|
@ -1012,6 +1025,8 @@ private:
|
|||
int32_t* aStatus,
|
||||
bool* aSuccess) override;
|
||||
|
||||
virtual bool RecvGraphicsError(const nsCString& aError) override;
|
||||
|
||||
virtual bool
|
||||
RecvBeginDriverCrashGuard(const uint32_t& aGuardType,
|
||||
bool* aOutCrashed) override;
|
||||
|
|
|
@ -56,13 +56,9 @@ using class mozilla::WidgetPluginEvent from "ipc/nsGUIEventIPC.h";
|
|||
using struct mozilla::dom::RemoteDOMEvent from "mozilla/dom/TabMessageUtils.h";
|
||||
using mozilla::dom::ScreenOrientationInternal from "mozilla/dom/ScreenOrientation.h";
|
||||
using struct mozilla::layers::TextureFactoryIdentifier from "mozilla/layers/CompositorTypes.h";
|
||||
using mozilla::CSSPoint from "Units.h";
|
||||
using mozilla::CSSToScreenScale from "Units.h";
|
||||
using mozilla::CommandInt from "mozilla/EventForwards.h";
|
||||
using mozilla::Modifiers from "mozilla/EventForwards.h";
|
||||
using mozilla::layers::GeckoContentController::APZStateChange from "mozilla/layers/GeckoContentController.h";
|
||||
using mozilla::WritingMode from "mozilla/WritingModes.h";
|
||||
using mozilla::layers::AsyncDragMetrics from "mozilla/layers/AsyncDragMetrics.h";
|
||||
using mozilla::layers::TouchBehaviorFlags from "mozilla/layers/APZUtils.h";
|
||||
using nsIWidget::TouchPointerState from "nsIWidget.h";
|
||||
using struct LookAndFeelInt from "mozilla/widget/WidgetMessageUtils.h";
|
||||
|
@ -417,43 +413,6 @@ parent:
|
|||
nsString aName, nsString aFeatures)
|
||||
returns (bool windowOpened);
|
||||
|
||||
/**
|
||||
* Instructs the TabParent to forward a request to zoom to a rect given in
|
||||
* CSS pixels. This rect is relative to the document.
|
||||
*/
|
||||
async ZoomToRect(uint32_t aPresShellId, ViewID aViewId, CSSRect aRect, uint32_t aFlags);
|
||||
|
||||
/**
|
||||
* We know for sure that content has either preventDefaulted or not
|
||||
* preventDefaulted. This applies to an entire batch of touch events. It is
|
||||
* expected that, if there are any DOM touch listeners, touch events will be
|
||||
* batched and only processed for panning and zooming if content does not
|
||||
* preventDefault.
|
||||
*/
|
||||
async ContentReceivedInputBlock(ScrollableLayerGuid aGuid, uint64_t aInputBlockId, bool aPreventDefault);
|
||||
|
||||
/**
|
||||
* Notifies the APZ code of the results of the gecko hit-test for a
|
||||
* particular input block. Each target corresponds to one touch point in the
|
||||
* touch event.
|
||||
*/
|
||||
async SetTargetAPZC(uint64_t aInputBlockId, ScrollableLayerGuid[] aTargets);
|
||||
|
||||
/**
|
||||
* Notifies the APZ code of the allowed touch-behaviours for a particular
|
||||
* input block. Each item in the aFlags array corresponds to one touch point
|
||||
* in the touch event.
|
||||
*/
|
||||
async SetAllowedTouchBehavior(uint64_t aInputBlockId, TouchBehaviorFlags[] aFlags);
|
||||
|
||||
/**
|
||||
* Updates the zoom constraints for a scrollable frame in this tab.
|
||||
* The zoom controller code lives on the parent side and so this allows it to
|
||||
* have up-to-date zoom constraints.
|
||||
*/
|
||||
async UpdateZoomConstraints(uint32_t aPresShellId, ViewID aViewId,
|
||||
MaybeZoomConstraints aConstraints);
|
||||
|
||||
/**
|
||||
* Tells the containing widget whether the given input block results in a
|
||||
* swipe. Should be called in response to a WidgetWheelEvent that has
|
||||
|
@ -538,9 +497,6 @@ parent:
|
|||
prio(high) sync DispatchMouseEvent(WidgetMouseEvent event);
|
||||
prio(high) sync DispatchKeyboardEvent(WidgetKeyboardEvent event);
|
||||
|
||||
// Start an APZ drag on a scrollbar
|
||||
async StartScrollbarDrag(AsyncDragMetrics aDragMetrics);
|
||||
|
||||
async InvokeDragSession(IPCDataTransfer[] transfers, uint32_t action,
|
||||
nsCString visualData, uint32_t width, uint32_t height,
|
||||
uint32_t stride, uint8_t format,
|
||||
|
@ -576,20 +532,6 @@ child:
|
|||
ScreenOrientationInternal orientation,
|
||||
LayoutDeviceIntPoint chromeDisp) compressall;
|
||||
|
||||
async UpdateFrame(FrameMetrics frame);
|
||||
|
||||
// The following methods correspond to functions on the GeckoContentController
|
||||
// interface in gfx/layers/apz/public/GeckoContentController.h. Refer to documentation
|
||||
// in that file for these functions.
|
||||
async RequestFlingSnap(ViewID aScrollID, CSSPoint aDestination);
|
||||
async AcknowledgeScrollUpdate(ViewID aScrollId, uint32_t aScrollGeneration);
|
||||
async HandleDoubleTap(CSSPoint aPoint, Modifiers aModifiers, ScrollableLayerGuid aGuid);
|
||||
async HandleSingleTap(CSSPoint aPoint, Modifiers aModifiers, ScrollableLayerGuid aGuid);
|
||||
async HandleLongTap(CSSPoint point, Modifiers aModifiers, ScrollableLayerGuid aGuid, uint64_t aInputBlockId);
|
||||
async NotifyAPZStateChange(ViewID aViewId, APZStateChange aChange, int aArg);
|
||||
async NotifyFlushComplete();
|
||||
|
||||
|
||||
/**
|
||||
* Sending an activate message moves focus to the child.
|
||||
*/
|
||||
|
@ -661,7 +603,7 @@ child:
|
|||
/**
|
||||
* APZ notification for mouse scroll testing events.
|
||||
*/
|
||||
async MouseScrollTestEvent(ViewID aScrollId, nsString aEvent);
|
||||
async MouseScrollTestEvent(uint64_t aLayersId, ViewID aScrollId, nsString aEvent);
|
||||
|
||||
async CompositionEvent(WidgetCompositionEvent event);
|
||||
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
* 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 protocol PAPZ;
|
||||
include protocol PBackground;
|
||||
include protocol PBlob;
|
||||
include protocol PBluetooth;
|
||||
|
@ -465,6 +466,7 @@ prio(normal upto urgent) sync protocol PContent
|
|||
parent opens PVRManager;
|
||||
child opens PBackground;
|
||||
|
||||
manages PAPZ;
|
||||
manages PBlob;
|
||||
manages PBluetooth;
|
||||
manages PBrowser;
|
||||
|
@ -575,6 +577,8 @@ child:
|
|||
|
||||
async PTestShell();
|
||||
|
||||
async PAPZ(TabId tabId);
|
||||
|
||||
async RegisterChrome(ChromePackage[] packages, SubstitutionMapping[] substitutions,
|
||||
OverrideMapping[] overrides, nsCString locale, bool reset);
|
||||
async RegisterChromeItem(ChromeRegistryItem item);
|
||||
|
@ -993,7 +997,7 @@ parent:
|
|||
async CopyFavicon(URIParams oldURI, URIParams newURI, Principal aLoadingPrincipal, bool isPrivate);
|
||||
|
||||
// Tell the compositor to allocate a layer tree id for nested remote mozbrowsers.
|
||||
sync AllocateLayerTreeId()
|
||||
sync AllocateLayerTreeId(ContentParentId cpId, TabId tabId)
|
||||
returns (uint64_t id);
|
||||
async DeallocateLayerTreeId(uint64_t id);
|
||||
|
||||
|
@ -1016,6 +1020,9 @@ parent:
|
|||
|
||||
sync GetGraphicsFeatureStatus(int32_t aFeature) returns (int32_t aStatus, bool aSuccess);
|
||||
|
||||
// Graphics errors
|
||||
async GraphicsError(nsCString aError);
|
||||
|
||||
// Driver crash guards. aGuardType must be a member of CrashGuardType.
|
||||
sync BeginDriverCrashGuard(uint32_t aGuardType) returns (bool crashDetected);
|
||||
sync EndDriverCrashGuard(uint32_t aGuardType);
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
#include "ContentChild.h"
|
||||
#include "TabParent.h"
|
||||
#include "mozilla/Preferences.h"
|
||||
#include "mozilla/BrowserElementParent.h"
|
||||
#include "mozilla/ClearOnShutdown.h"
|
||||
#include "mozilla/EventListenerManager.h"
|
||||
#include "mozilla/dom/workers/ServiceWorkerManager.h"
|
||||
|
@ -28,6 +29,7 @@
|
|||
#include "ipc/Nuwa.h"
|
||||
#endif
|
||||
#include "mozilla/ipc/FileDescriptorUtils.h"
|
||||
#include "mozilla/layers/APZChild.h"
|
||||
#include "mozilla/layers/APZCCallbackHelper.h"
|
||||
#include "mozilla/layers/APZCTreeManager.h"
|
||||
#include "mozilla/layers/APZEventState.h"
|
||||
|
@ -37,6 +39,7 @@
|
|||
#include "mozilla/layers/InputAPZContext.h"
|
||||
#include "mozilla/layers/ShadowLayers.h"
|
||||
#include "mozilla/layout/RenderFrameChild.h"
|
||||
#include "mozilla/layout/RenderFrameParent.h"
|
||||
#include "mozilla/LookAndFeel.h"
|
||||
#include "mozilla/MouseEvents.h"
|
||||
#include "mozilla/Move.h"
|
||||
|
@ -589,6 +592,7 @@ TabChild::TabChild(nsIContentChild* aManager,
|
|||
, mIPCOpen(true)
|
||||
, mParentIsActive(false)
|
||||
, mDidSetRealShowInfo(false)
|
||||
, mAPZChild(nullptr)
|
||||
{
|
||||
// In the general case having the TabParent tell us if APZ is enabled or not
|
||||
// doesn't really work because the TabParent itself may not have a reference
|
||||
|
@ -602,7 +606,7 @@ TabChild::TabChild(nsIContentChild* aManager,
|
|||
const nsTArray<TouchBehaviorFlags>& aFlags)
|
||||
{
|
||||
if (nsCOMPtr<nsITabChild> tabChild = do_QueryReferent(weakPtrThis)) {
|
||||
static_cast<TabChild*>(tabChild.get())->SendSetAllowedTouchBehavior(aInputBlockId, aFlags);
|
||||
static_cast<TabChild*>(tabChild.get())->SetAllowedTouchBehavior(aInputBlockId, aFlags);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -710,6 +714,35 @@ TabChild::Observe(nsISupports *aSubject,
|
|||
return NS_OK;
|
||||
}
|
||||
|
||||
void
|
||||
TabChild::ContentReceivedInputBlock(const ScrollableLayerGuid& aGuid,
|
||||
uint64_t aInputBlockId,
|
||||
bool aPreventDefault) const
|
||||
{
|
||||
if (mAPZChild) {
|
||||
mAPZChild->SendContentReceivedInputBlock(aGuid, aInputBlockId,
|
||||
aPreventDefault);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
TabChild::SetTargetAPZC(uint64_t aInputBlockId,
|
||||
const nsTArray<ScrollableLayerGuid>& aTargets) const
|
||||
{
|
||||
if (mAPZChild) {
|
||||
mAPZChild->SendSetTargetAPZC(aInputBlockId, aTargets);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
TabChild::SetAllowedTouchBehavior(uint64_t aInputBlockId,
|
||||
const nsTArray<TouchBehaviorFlags>& aTargets) const
|
||||
{
|
||||
if (mAPZChild) {
|
||||
mAPZChild->SendSetAllowedTouchBehavior(aInputBlockId, aTargets);
|
||||
}
|
||||
}
|
||||
|
||||
bool
|
||||
TabChild::DoUpdateZoomConstraints(const uint32_t& aPresShellId,
|
||||
const ViewID& aViewId,
|
||||
|
@ -722,9 +755,12 @@ TabChild::DoUpdateZoomConstraints(const uint32_t& aPresShellId,
|
|||
return true;
|
||||
}
|
||||
|
||||
return SendUpdateZoomConstraints(aPresShellId,
|
||||
aViewId,
|
||||
aConstraints);
|
||||
if (!mAPZChild) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return mAPZChild->SendUpdateZoomConstraints(aPresShellId, aViewId,
|
||||
aConstraints);
|
||||
}
|
||||
|
||||
nsresult
|
||||
|
@ -811,7 +847,7 @@ TabChild::Init()
|
|||
bool aPreventDefault)
|
||||
{
|
||||
if (nsCOMPtr<nsITabChild> tabChild = do_QueryReferent(weakPtrThis)) {
|
||||
static_cast<TabChild*>(tabChild.get())->SendContentReceivedInputBlock(aGuid, aInputBlockId, aPreventDefault);
|
||||
static_cast<TabChild*>(tabChild.get())->ContentReceivedInputBlock(aGuid, aInputBlockId, aPreventDefault);
|
||||
}
|
||||
});
|
||||
mAPZEventState = new APZEventState(mPuppetWidget, Move(callback));
|
||||
|
@ -1633,7 +1669,7 @@ TabChild::RecvUpdateDimensions(const CSSRect& rect, const CSSSize& size,
|
|||
}
|
||||
|
||||
bool
|
||||
TabChild::RecvUpdateFrame(const FrameMetrics& aFrameMetrics)
|
||||
TabChild::UpdateFrame(const FrameMetrics& aFrameMetrics)
|
||||
{
|
||||
return TabChildBase::UpdateFrameHandler(aFrameMetrics);
|
||||
}
|
||||
|
@ -1652,77 +1688,68 @@ TabChild::RecvSuppressDisplayport(const bool& aEnabled)
|
|||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
TabChild::RecvRequestFlingSnap(const FrameMetrics::ViewID& aScrollId,
|
||||
const mozilla::CSSPoint& aDestination)
|
||||
void
|
||||
TabChild::HandleDoubleTap(const CSSPoint& aPoint, const Modifiers& aModifiers,
|
||||
const ScrollableLayerGuid& aGuid)
|
||||
{
|
||||
APZCCallbackHelper::RequestFlingSnap(aScrollId, aDestination);
|
||||
return true;
|
||||
TABC_LOG("Handling double tap at %s with %p %p\n",
|
||||
Stringify(aPoint).c_str(), mGlobal.get(), mTabChildGlobal.get());
|
||||
|
||||
if (!mGlobal || !mTabChildGlobal) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Note: there is nothing to do with the modifiers here, as we are not
|
||||
// synthesizing any sort of mouse event.
|
||||
CSSPoint point = APZCCallbackHelper::ApplyCallbackTransform(aPoint, aGuid);
|
||||
nsCOMPtr<nsIDocument> document = GetDocument();
|
||||
CSSRect zoomToRect = CalculateRectToZoomTo(document, point);
|
||||
// The double-tap can be dispatched by any scroll frame (so |aGuid| could be
|
||||
// the guid of any scroll frame), but the zoom-to-rect operation must be
|
||||
// performed by the root content scroll frame, so query its identifiers
|
||||
// for the SendZoomToRect() call rather than using the ones from |aGuid|.
|
||||
uint32_t presShellId;
|
||||
ViewID viewId;
|
||||
if (APZCCallbackHelper::GetOrCreateScrollIdentifiers(
|
||||
document->GetDocumentElement(), &presShellId, &viewId) &&
|
||||
mAPZChild) {
|
||||
mAPZChild->SendZoomToRect(presShellId, viewId, zoomToRect,
|
||||
DEFAULT_BEHAVIOR);
|
||||
}
|
||||
}
|
||||
|
||||
bool
|
||||
TabChild::RecvAcknowledgeScrollUpdate(const ViewID& aScrollId,
|
||||
const uint32_t& aScrollGeneration)
|
||||
{
|
||||
APZCCallbackHelper::AcknowledgeScrollUpdate(aScrollId, aScrollGeneration);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
TabChild::RecvHandleDoubleTap(const CSSPoint& aPoint, const Modifiers& aModifiers, const ScrollableLayerGuid& aGuid)
|
||||
{
|
||||
TABC_LOG("Handling double tap at %s with %p %p\n",
|
||||
Stringify(aPoint).c_str(), mGlobal.get(), mTabChildGlobal.get());
|
||||
|
||||
if (!mGlobal || !mTabChildGlobal) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Note: there is nothing to do with the modifiers here, as we are not
|
||||
// synthesizing any sort of mouse event.
|
||||
CSSPoint point = APZCCallbackHelper::ApplyCallbackTransform(aPoint, aGuid);
|
||||
nsCOMPtr<nsIDocument> document = GetDocument();
|
||||
CSSRect zoomToRect = CalculateRectToZoomTo(document, point);
|
||||
// The double-tap can be dispatched by any scroll frame (so |aGuid| could be
|
||||
// the guid of any scroll frame), but the zoom-to-rect operation must be
|
||||
// performed by the root content scroll frame, so query its identifiers
|
||||
// for the SendZoomToRect() call rather than using the ones from |aGuid|.
|
||||
uint32_t presShellId;
|
||||
ViewID viewId;
|
||||
if (APZCCallbackHelper::GetOrCreateScrollIdentifiers(
|
||||
document->GetDocumentElement(), &presShellId, &viewId)) {
|
||||
SendZoomToRect(presShellId, viewId, zoomToRect, DEFAULT_BEHAVIOR);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
TabChild::RecvHandleSingleTap(const CSSPoint& aPoint, const Modifiers& aModifiers, const ScrollableLayerGuid& aGuid)
|
||||
void
|
||||
TabChild::HandleSingleTap(const CSSPoint& aPoint,
|
||||
const Modifiers& aModifiers,
|
||||
const ScrollableLayerGuid& aGuid,
|
||||
bool aCallTakeFocusForClickFromTap)
|
||||
{
|
||||
if (aCallTakeFocusForClickFromTap && mRemoteFrame) {
|
||||
mRemoteFrame->SendTakeFocusForClickFromTap();
|
||||
}
|
||||
if (mGlobal && mTabChildGlobal) {
|
||||
mAPZEventState->ProcessSingleTap(aPoint, aModifiers, aGuid);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
TabChild::RecvHandleLongTap(const CSSPoint& aPoint, const Modifiers& aModifiers, const ScrollableLayerGuid& aGuid, const uint64_t& aInputBlockId)
|
||||
void
|
||||
TabChild::HandleLongTap(const CSSPoint& aPoint, const Modifiers& aModifiers,
|
||||
const ScrollableLayerGuid& aGuid,
|
||||
const uint64_t& aInputBlockId)
|
||||
{
|
||||
if (mGlobal && mTabChildGlobal) {
|
||||
mAPZEventState->ProcessLongTap(GetPresShell(), aPoint, aModifiers, aGuid,
|
||||
aInputBlockId);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
TabChild::RecvNotifyAPZStateChange(const ViewID& aViewId,
|
||||
const APZStateChange& aChange,
|
||||
const int& aArg)
|
||||
TabChild::NotifyAPZStateChange(const ViewID& aViewId,
|
||||
const layers::GeckoContentController::APZStateChange& aChange,
|
||||
const int& aArg)
|
||||
{
|
||||
mAPZEventState->ProcessAPZStateChange(GetDocument(), aViewId, aChange, aArg);
|
||||
if (aChange == APZStateChange::TransformEnd) {
|
||||
if (aChange == layers::GeckoContentController::APZStateChange::TransformEnd) {
|
||||
// This is used by tests to determine when the APZ is done doing whatever
|
||||
// it's doing. XXX generify this as needed when writing additional tests.
|
||||
DispatchMessageManagerMessage(
|
||||
|
@ -1732,11 +1759,23 @@ TabChild::RecvNotifyAPZStateChange(const ViewID& aViewId,
|
|||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
TabChild::RecvNotifyFlushComplete()
|
||||
void
|
||||
TabChild::StartScrollbarDrag(const layers::AsyncDragMetrics& aDragMetrics)
|
||||
{
|
||||
APZCCallbackHelper::NotifyFlushComplete();
|
||||
return true;
|
||||
if (mAPZChild) {
|
||||
mAPZChild->SendStartScrollbarDrag(aDragMetrics);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
TabChild::ZoomToRect(const uint32_t& aPresShellId,
|
||||
const FrameMetrics::ViewID& aViewId,
|
||||
const CSSRect& aRect,
|
||||
const uint32_t& aFlags)
|
||||
{
|
||||
if (mAPZChild) {
|
||||
mAPZChild->SendZoomToRect(aPresShellId, aViewId, aRect, aFlags);
|
||||
}
|
||||
}
|
||||
|
||||
bool
|
||||
|
@ -1854,8 +1893,21 @@ TabChild::RecvMouseWheelEvent(const WidgetWheelEvent& aEvent,
|
|||
}
|
||||
|
||||
bool
|
||||
TabChild::RecvMouseScrollTestEvent(const FrameMetrics::ViewID& aScrollId, const nsString& aEvent)
|
||||
TabChild::RecvMouseScrollTestEvent(const uint64_t& aLayersId,
|
||||
const FrameMetrics::ViewID& aScrollId, const nsString& aEvent)
|
||||
{
|
||||
if (aLayersId != mLayersId) {
|
||||
RefPtr<TabParent> browser = TabParent::GetTabParentFromLayersId(aLayersId);
|
||||
if (!browser) {
|
||||
return false;
|
||||
}
|
||||
NS_DispatchToMainThread(NS_NewRunnableFunction(
|
||||
[aLayersId, browser, aScrollId, aEvent] () -> void {
|
||||
Unused << browser->SendMouseScrollTestEvent(aLayersId, aScrollId, aEvent);
|
||||
}));
|
||||
return true;
|
||||
}
|
||||
|
||||
APZCCallbackHelper::NotifyMozMouseScrollEvent(aScrollId, aEvent);
|
||||
return true;
|
||||
}
|
||||
|
@ -2622,7 +2674,10 @@ TabChild::MakeHidden()
|
|||
void
|
||||
TabChild::UpdateHitRegion(const nsRegion& aRegion)
|
||||
{
|
||||
mRemoteFrame->SendUpdateHitRegion(aRegion);
|
||||
mRemoteFrame->SendUpdateHitRegion(aRegion);
|
||||
if (mAPZChild) {
|
||||
mAPZChild->SendUpdateHitRegion(aRegion);
|
||||
}
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
|
|
|
@ -37,6 +37,7 @@
|
|||
#include "mozilla/dom/ipc/IdType.h"
|
||||
#include "AudioChannelService.h"
|
||||
#include "PuppetWidget.h"
|
||||
#include "mozilla/layers/GeckoContentController.h"
|
||||
|
||||
class nsICachedFileDescriptorListener;
|
||||
class nsIDOMWindowUtils;
|
||||
|
@ -47,7 +48,9 @@ class RenderFrameChild;
|
|||
} // namespace layout
|
||||
|
||||
namespace layers {
|
||||
class APZChild;
|
||||
class APZEventState;
|
||||
class AsyncDragMetrics;
|
||||
class ImageCompositeNotification;
|
||||
} // namespace layers
|
||||
|
||||
|
@ -332,39 +335,6 @@ public:
|
|||
const ScreenOrientationInternal& aOrientation,
|
||||
const LayoutDeviceIntPoint& aChromeDisp) override;
|
||||
|
||||
virtual bool
|
||||
RecvUpdateFrame(const layers::FrameMetrics& aFrameMetrics) override;
|
||||
|
||||
virtual bool
|
||||
RecvRequestFlingSnap(const ViewID& aScrollId,
|
||||
const CSSPoint& aDestination) override;
|
||||
|
||||
virtual bool
|
||||
RecvAcknowledgeScrollUpdate(const ViewID& aScrollId,
|
||||
const uint32_t& aScrollGeneration) override;
|
||||
|
||||
virtual bool
|
||||
RecvHandleDoubleTap(const CSSPoint& aPoint,
|
||||
const Modifiers& aModifiers,
|
||||
const mozilla::layers::ScrollableLayerGuid& aGuid) override;
|
||||
|
||||
virtual bool
|
||||
RecvHandleSingleTap(const CSSPoint& aPoint,
|
||||
const Modifiers& aModifiers,
|
||||
const mozilla::layers::ScrollableLayerGuid& aGuid) override;
|
||||
|
||||
virtual bool
|
||||
RecvHandleLongTap(const CSSPoint& aPoint,
|
||||
const Modifiers& aModifiers,
|
||||
const mozilla::layers::ScrollableLayerGuid& aGuid,
|
||||
const uint64_t& aInputBlockId) override;
|
||||
|
||||
virtual bool RecvNotifyAPZStateChange(const ViewID& aViewId,
|
||||
const APZStateChange& aChange,
|
||||
const int& aArg) override;
|
||||
|
||||
virtual bool RecvNotifyFlushComplete() override;
|
||||
|
||||
virtual bool RecvActivate() override;
|
||||
|
||||
virtual bool RecvDeactivate() override;
|
||||
|
@ -418,7 +388,8 @@ public:
|
|||
const int32_t& aModifiers,
|
||||
const bool& aPreventDefault) override;
|
||||
|
||||
virtual bool RecvMouseScrollTestEvent(const FrameMetrics::ViewID& aScrollId,
|
||||
virtual bool RecvMouseScrollTestEvent(const uint64_t& aLayersId,
|
||||
const FrameMetrics::ViewID& aScrollId,
|
||||
const nsString& aEvent) override;
|
||||
|
||||
virtual bool RecvNativeSynthesisResponse(const uint64_t& aObserverId,
|
||||
|
@ -622,6 +593,40 @@ public:
|
|||
PRenderFrameChild* aRenderFrame,
|
||||
const ShowInfo& aShowInfo);
|
||||
|
||||
void ContentReceivedInputBlock(const ScrollableLayerGuid& aGuid,
|
||||
uint64_t aInputBlockId,
|
||||
bool aPreventDefault) const;
|
||||
void SetTargetAPZC(uint64_t aInputBlockId,
|
||||
const nsTArray<ScrollableLayerGuid>& aTargets) const;
|
||||
void HandleDoubleTap(const CSSPoint& aPoint,
|
||||
const Modifiers& aModifiers,
|
||||
const mozilla::layers::ScrollableLayerGuid& aGuid);
|
||||
void HandleSingleTap(const CSSPoint& aPoint,
|
||||
const Modifiers& aModifiers,
|
||||
const mozilla::layers::ScrollableLayerGuid& aGuid,
|
||||
bool aCallTakeFocusForClickFromTap);
|
||||
void HandleLongTap(const CSSPoint& aPoint,
|
||||
const Modifiers& aModifiers,
|
||||
const mozilla::layers::ScrollableLayerGuid& aGuid,
|
||||
const uint64_t& aInputBlockId);
|
||||
void SetAllowedTouchBehavior(uint64_t aInputBlockId,
|
||||
const nsTArray<TouchBehaviorFlags>& aFlags) const;
|
||||
|
||||
bool UpdateFrame(const FrameMetrics& aFrameMetrics);
|
||||
bool NotifyAPZStateChange(const ViewID& aViewId,
|
||||
const layers::GeckoContentController::APZStateChange& aChange,
|
||||
const int& aArg);
|
||||
void StartScrollbarDrag(const layers::AsyncDragMetrics& aDragMetrics);
|
||||
void ZoomToRect(const uint32_t& aPresShellId,
|
||||
const FrameMetrics::ViewID& aViewId,
|
||||
const CSSRect& aRect,
|
||||
const uint32_t& aFlags);
|
||||
|
||||
void SetAPZChild(layers::APZChild* aAPZChild)
|
||||
{
|
||||
mAPZChild = aAPZChild;
|
||||
}
|
||||
|
||||
protected:
|
||||
virtual ~TabChild();
|
||||
|
||||
|
@ -736,6 +741,10 @@ private:
|
|||
|
||||
AutoTArray<bool, NUMBER_OF_AUDIO_CHANNELS> mAudioChannelsActive;
|
||||
|
||||
// APZChild clears this pointer from its destructor, so it shouldn't be a
|
||||
// dangling pointer.
|
||||
layers::APZChild* mAPZChild;
|
||||
|
||||
DISALLOW_EVIL_CONSTRUCTORS(TabChild);
|
||||
};
|
||||
|
||||
|
|
|
@ -995,14 +995,6 @@ TabParent::UpdateDimensions(const nsIntRect& rect, const ScreenIntSize& size)
|
|||
}
|
||||
}
|
||||
|
||||
void
|
||||
TabParent::UpdateFrame(const FrameMetrics& aFrameMetrics)
|
||||
{
|
||||
if (!mIsDestroyed) {
|
||||
Unused << SendUpdateFrame(aFrameMetrics);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
TabParent::UIResolutionChanged()
|
||||
{
|
||||
|
@ -1042,76 +1034,6 @@ TabParent::HandleAccessKey(nsTArray<uint32_t>& aCharCodes,
|
|||
}
|
||||
}
|
||||
|
||||
void
|
||||
TabParent::RequestFlingSnap(const FrameMetrics::ViewID& aScrollId,
|
||||
const mozilla::CSSPoint& aDestination)
|
||||
{
|
||||
if (!mIsDestroyed) {
|
||||
Unused << SendRequestFlingSnap(aScrollId, aDestination);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
TabParent::AcknowledgeScrollUpdate(const ViewID& aScrollId, const uint32_t& aScrollGeneration)
|
||||
{
|
||||
if (!mIsDestroyed) {
|
||||
Unused << SendAcknowledgeScrollUpdate(aScrollId, aScrollGeneration);
|
||||
}
|
||||
}
|
||||
|
||||
void TabParent::HandleDoubleTap(const CSSPoint& aPoint,
|
||||
Modifiers aModifiers,
|
||||
const ScrollableLayerGuid &aGuid)
|
||||
{
|
||||
if (!mIsDestroyed) {
|
||||
Unused << SendHandleDoubleTap(aPoint, aModifiers, aGuid);
|
||||
}
|
||||
}
|
||||
|
||||
void TabParent::HandleSingleTap(const CSSPoint& aPoint,
|
||||
Modifiers aModifiers,
|
||||
const ScrollableLayerGuid &aGuid)
|
||||
{
|
||||
if (!mIsDestroyed) {
|
||||
Unused << SendHandleSingleTap(aPoint, aModifiers, aGuid);
|
||||
}
|
||||
}
|
||||
|
||||
void TabParent::HandleLongTap(const CSSPoint& aPoint,
|
||||
Modifiers aModifiers,
|
||||
const ScrollableLayerGuid &aGuid,
|
||||
uint64_t aInputBlockId)
|
||||
{
|
||||
if (!mIsDestroyed) {
|
||||
Unused << SendHandleLongTap(aPoint, aModifiers, aGuid, aInputBlockId);
|
||||
}
|
||||
}
|
||||
|
||||
void TabParent::NotifyAPZStateChange(ViewID aViewId,
|
||||
APZStateChange aChange,
|
||||
int aArg)
|
||||
{
|
||||
if (!mIsDestroyed) {
|
||||
Unused << SendNotifyAPZStateChange(aViewId, aChange, aArg);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
TabParent::NotifyMouseScrollTestEvent(const ViewID& aScrollId, const nsString& aEvent)
|
||||
{
|
||||
if (!mIsDestroyed) {
|
||||
Unused << SendMouseScrollTestEvent(aScrollId, aEvent);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
TabParent::NotifyFlushComplete()
|
||||
{
|
||||
if (!mIsDestroyed) {
|
||||
Unused << SendNotifyFlushComplete();
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
TabParent::Activate()
|
||||
{
|
||||
|
@ -1400,33 +1322,6 @@ CSSPoint TabParent::AdjustTapToChildWidget(const CSSPoint& aPoint)
|
|||
return aPoint + (LayoutDevicePoint(GetChildProcessOffset()) * GetLayoutDeviceToCSSScale());
|
||||
}
|
||||
|
||||
bool TabParent::SendHandleSingleTap(const CSSPoint& aPoint, const Modifiers& aModifiers, const ScrollableLayerGuid& aGuid)
|
||||
{
|
||||
if (mIsDestroyed) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return PBrowserParent::SendHandleSingleTap(AdjustTapToChildWidget(aPoint), aModifiers, aGuid);
|
||||
}
|
||||
|
||||
bool TabParent::SendHandleLongTap(const CSSPoint& aPoint, const Modifiers& aModifiers, const ScrollableLayerGuid& aGuid, const uint64_t& aInputBlockId)
|
||||
{
|
||||
if (mIsDestroyed) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return PBrowserParent::SendHandleLongTap(AdjustTapToChildWidget(aPoint), aModifiers, aGuid, aInputBlockId);
|
||||
}
|
||||
|
||||
bool TabParent::SendHandleDoubleTap(const CSSPoint& aPoint, const Modifiers& aModifiers, const ScrollableLayerGuid& aGuid)
|
||||
{
|
||||
if (mIsDestroyed) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return PBrowserParent::SendHandleDoubleTap(AdjustTapToChildWidget(aPoint), aModifiers, aGuid);
|
||||
}
|
||||
|
||||
bool TabParent::SendMouseWheelEvent(WidgetWheelEvent& event)
|
||||
{
|
||||
if (mIsDestroyed) {
|
||||
|
@ -2796,29 +2691,6 @@ TabParent::RecvBrowserFrameOpenWindow(PBrowserParent* aOpener,
|
|||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
TabParent::RecvZoomToRect(const uint32_t& aPresShellId,
|
||||
const ViewID& aViewId,
|
||||
const CSSRect& aRect,
|
||||
const uint32_t& aFlags)
|
||||
{
|
||||
if (RenderFrameParent* rfp = GetRenderFrame()) {
|
||||
rfp->ZoomToRect(aPresShellId, aViewId, aRect, aFlags);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
TabParent::RecvUpdateZoomConstraints(const uint32_t& aPresShellId,
|
||||
const ViewID& aViewId,
|
||||
const MaybeZoomConstraints& aConstraints)
|
||||
{
|
||||
if (RenderFrameParent* rfp = GetRenderFrame()) {
|
||||
rfp->UpdateZoomConstraints(aPresShellId, aViewId, aConstraints);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
TabParent::RecvRespondStartSwipeEvent(const uint64_t& aInputBlockId,
|
||||
const bool& aStartSwipe)
|
||||
|
@ -2829,46 +2701,6 @@ TabParent::RecvRespondStartSwipeEvent(const uint64_t& aInputBlockId,
|
|||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
TabParent::RecvContentReceivedInputBlock(const ScrollableLayerGuid& aGuid,
|
||||
const uint64_t& aInputBlockId,
|
||||
const bool& aPreventDefault)
|
||||
{
|
||||
if (RenderFrameParent* rfp = GetRenderFrame()) {
|
||||
rfp->ContentReceivedInputBlock(aGuid, aInputBlockId, aPreventDefault);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
TabParent::RecvSetTargetAPZC(const uint64_t& aInputBlockId,
|
||||
nsTArray<ScrollableLayerGuid>&& aTargets)
|
||||
{
|
||||
if (RenderFrameParent* rfp = GetRenderFrame()) {
|
||||
rfp->SetTargetAPZC(aInputBlockId, aTargets);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
TabParent::RecvStartScrollbarDrag(const AsyncDragMetrics& aDragMetrics)
|
||||
{
|
||||
if (RenderFrameParent* rfp = GetRenderFrame()) {
|
||||
rfp->StartScrollbarDrag(aDragMetrics);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
TabParent::RecvSetAllowedTouchBehavior(const uint64_t& aInputBlockId,
|
||||
nsTArray<TouchBehaviorFlags>&& aFlags)
|
||||
{
|
||||
if (RenderFrameParent* rfp = GetRenderFrame()) {
|
||||
rfp->SetAllowedTouchBehavior(aInputBlockId, aFlags);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
already_AddRefed<nsILoadContext>
|
||||
TabParent::GetLoadContext()
|
||||
{
|
||||
|
|
|
@ -50,8 +50,6 @@ class CpowHolder;
|
|||
} // namespace jsipc
|
||||
|
||||
namespace layers {
|
||||
class AsyncDragMetrics;
|
||||
struct FrameMetrics;
|
||||
struct TextureFactoryIdentifier;
|
||||
} // namespace layers
|
||||
|
||||
|
@ -90,7 +88,6 @@ class TabParent final : public PBrowserParent
|
|||
, public nsIWebBrowserPersistable
|
||||
{
|
||||
typedef mozilla::dom::ClonedMessageData ClonedMessageData;
|
||||
typedef mozilla::layers::AsyncDragMetrics AsyncDragMetrics;
|
||||
|
||||
virtual ~TabParent();
|
||||
|
||||
|
@ -297,31 +294,9 @@ public:
|
|||
|
||||
virtual bool RecvDispatchFocusToTopLevelWindow() override;
|
||||
|
||||
virtual bool RecvZoomToRect(const uint32_t& aPresShellId,
|
||||
const ViewID& aViewId,
|
||||
const CSSRect& aRect,
|
||||
const uint32_t& aFlags) override;
|
||||
|
||||
virtual bool
|
||||
RecvUpdateZoomConstraints(const uint32_t& aPresShellId,
|
||||
const ViewID& aViewId,
|
||||
const MaybeZoomConstraints& aConstraints) override;
|
||||
|
||||
virtual bool RecvRespondStartSwipeEvent(const uint64_t& aInputBlockId,
|
||||
const bool& aStartSwipe) override;
|
||||
|
||||
virtual bool
|
||||
RecvContentReceivedInputBlock(const ScrollableLayerGuid& aGuid,
|
||||
const uint64_t& aInputBlockId,
|
||||
const bool& aPreventDefault) override;
|
||||
|
||||
virtual bool RecvSetTargetAPZC(const uint64_t& aInputBlockId,
|
||||
nsTArray<ScrollableLayerGuid>&& aTargets) override;
|
||||
|
||||
virtual bool
|
||||
RecvSetAllowedTouchBehavior(const uint64_t& aInputBlockId,
|
||||
nsTArray<TouchBehaviorFlags>&& aTargets) override;
|
||||
|
||||
virtual bool
|
||||
RecvDispatchWheelEvent(const mozilla::WidgetWheelEvent& aEvent) override;
|
||||
|
||||
|
@ -331,9 +306,6 @@ public:
|
|||
virtual bool
|
||||
RecvDispatchKeyboardEvent(const mozilla::WidgetKeyboardEvent& aEvent) override;
|
||||
|
||||
virtual bool
|
||||
RecvStartScrollbarDrag(const AsyncDragMetrics& aDragMetrics) override;
|
||||
|
||||
virtual PColorPickerParent*
|
||||
AllocPColorPickerParent(const nsString& aTitle,
|
||||
const nsString& aInitialColor) override;
|
||||
|
@ -365,8 +337,6 @@ public:
|
|||
|
||||
void UpdateDimensions(const nsIntRect& aRect, const ScreenIntSize& aSize);
|
||||
|
||||
void UpdateFrame(const layers::FrameMetrics& aFrameMetrics);
|
||||
|
||||
void UIResolutionChanged();
|
||||
|
||||
void ThemeChanged();
|
||||
|
@ -375,34 +345,6 @@ public:
|
|||
const bool& aIsTrusted,
|
||||
const int32_t& aModifierMask);
|
||||
|
||||
void RequestFlingSnap(const FrameMetrics::ViewID& aScrollId,
|
||||
const mozilla::CSSPoint& aDestination);
|
||||
|
||||
void AcknowledgeScrollUpdate(const ViewID& aScrollId,
|
||||
const uint32_t& aScrollGeneration);
|
||||
|
||||
void HandleDoubleTap(const CSSPoint& aPoint,
|
||||
Modifiers aModifiers,
|
||||
const ScrollableLayerGuid& aGuid);
|
||||
|
||||
void HandleSingleTap(const CSSPoint& aPoint,
|
||||
Modifiers aModifiers,
|
||||
const ScrollableLayerGuid& aGuid);
|
||||
|
||||
void HandleLongTap(const CSSPoint& aPoint,
|
||||
Modifiers aModifiers,
|
||||
const ScrollableLayerGuid& aGuid,
|
||||
uint64_t aInputBlockId);
|
||||
|
||||
void NotifyAPZStateChange(ViewID aViewId,
|
||||
APZStateChange aChange,
|
||||
int aArg);
|
||||
|
||||
void NotifyMouseScrollTestEvent(const ViewID& aScrollId,
|
||||
const nsString& aEvent);
|
||||
|
||||
void NotifyFlushComplete();
|
||||
|
||||
void Activate();
|
||||
|
||||
void Deactivate();
|
||||
|
@ -482,19 +424,6 @@ public:
|
|||
|
||||
bool SendRealTouchEvent(WidgetTouchEvent& event);
|
||||
|
||||
bool SendHandleSingleTap(const CSSPoint& aPoint,
|
||||
const Modifiers& aModifiers,
|
||||
const ScrollableLayerGuid& aGuid);
|
||||
|
||||
bool SendHandleLongTap(const CSSPoint& aPoint,
|
||||
const Modifiers& aModifiers,
|
||||
const ScrollableLayerGuid& aGuid,
|
||||
const uint64_t& aInputBlockId);
|
||||
|
||||
bool SendHandleDoubleTap(const CSSPoint& aPoint,
|
||||
const Modifiers& aModifiers,
|
||||
const ScrollableLayerGuid& aGuid);
|
||||
|
||||
virtual PDocumentRendererParent*
|
||||
AllocPDocumentRendererParent(const nsRect& documentRect,
|
||||
const gfx::Matrix& transform,
|
||||
|
|
|
@ -52,7 +52,6 @@ GraphDriver::GraphDriver(MediaStreamGraphImpl* aGraphImpl)
|
|||
mIterationEnd(0),
|
||||
mGraphImpl(aGraphImpl),
|
||||
mWaitState(WAITSTATE_RUNNING),
|
||||
mAudioInput(nullptr),
|
||||
mCurrentTimeStamp(TimeStamp::Now()),
|
||||
mPreviousDriver(nullptr),
|
||||
mNextDriver(nullptr)
|
||||
|
@ -546,6 +545,7 @@ StreamAndPromiseForOperation::StreamAndPromiseForOperation(MediaStream* aStream,
|
|||
AudioCallbackDriver::AudioCallbackDriver(MediaStreamGraphImpl* aGraphImpl)
|
||||
: GraphDriver(aGraphImpl)
|
||||
, mSampleRate(0)
|
||||
, mInputChannels(1)
|
||||
, mIterationDurationMS(MEDIA_GRAPH_TARGET_PERIOD_MS)
|
||||
, mStarted(false)
|
||||
, mAudioInput(nullptr)
|
||||
|
@ -604,7 +604,7 @@ AudioCallbackDriver::Init()
|
|||
}
|
||||
|
||||
input = output;
|
||||
input.channels = 1; // change to support optional stereo capture
|
||||
input.channels = mInputChannels; // change to support optional stereo capture
|
||||
|
||||
cubeb_stream* stream;
|
||||
// XXX Only pass input input if we have an input listener. Always
|
||||
|
@ -922,14 +922,14 @@ AudioCallbackDriver::DataCallback(const AudioDataValue* aInputBuffer,
|
|||
// removed/added to this list and TSAN issues, but input and output will
|
||||
// use separate callback methods.
|
||||
mGraphImpl->NotifyOutputData(aOutputBuffer, static_cast<size_t>(aFrames),
|
||||
ChannelCount);
|
||||
mSampleRate, ChannelCount);
|
||||
|
||||
// Process mic data if any/needed -- after inserting far-end data for AEC!
|
||||
if (aInputBuffer) {
|
||||
if (mAudioInput) { // for this specific input-only or full-duplex stream
|
||||
mAudioInput->NotifyInputData(mGraphImpl, aInputBuffer,
|
||||
static_cast<size_t>(aFrames),
|
||||
ChannelCount);
|
||||
mSampleRate, mInputChannels);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -197,15 +197,6 @@ public:
|
|||
|
||||
virtual bool OnThread() = 0;
|
||||
|
||||
// These are invoked on the MSG thread (or MainThread in shutdown)
|
||||
virtual void SetInputListener(AudioDataListener *aListener) {
|
||||
mAudioInput = aListener;
|
||||
}
|
||||
// XXX do we need the param? probably no
|
||||
virtual void RemoveInputListener(AudioDataListener *aListener) {
|
||||
mAudioInput = nullptr;
|
||||
}
|
||||
|
||||
protected:
|
||||
GraphTime StateComputedTime() const;
|
||||
|
||||
|
@ -236,9 +227,6 @@ protected:
|
|||
// This must be access with the monitor.
|
||||
WaitState mWaitState;
|
||||
|
||||
// Callback for mic data, if any
|
||||
AudioDataListener *mAudioInput;
|
||||
|
||||
// This is used on the main thread (during initialization), and the graph
|
||||
// thread. No monitor needed because we know the graph thread does not run
|
||||
// during the initialization.
|
||||
|
@ -421,6 +409,17 @@ public:
|
|||
uint32_t aFrames,
|
||||
uint32_t aSampleRate) override;
|
||||
|
||||
// These are invoked on the MSG thread (we don't call this if not LIFECYCLE_RUNNING)
|
||||
virtual void SetInputListener(AudioDataListener *aListener) {
|
||||
MOZ_ASSERT(OnThread());
|
||||
mAudioInput = aListener;
|
||||
}
|
||||
// XXX do we need the param? probably no
|
||||
virtual void RemoveInputListener(AudioDataListener *aListener) {
|
||||
MOZ_ASSERT(OnThread());
|
||||
mAudioInput = nullptr;
|
||||
}
|
||||
|
||||
AudioCallbackDriver* AsAudioCallbackDriver() override {
|
||||
return this;
|
||||
}
|
||||
|
@ -486,6 +485,9 @@ private:
|
|||
/* The sample rate for the aforementionned cubeb stream. This is set on
|
||||
* initialization and can be read safely afterwards. */
|
||||
uint32_t mSampleRate;
|
||||
/* The number of input channels from cubeb. Should be set before opening cubeb
|
||||
* and then be static. */
|
||||
uint32_t mInputChannels;
|
||||
/* Approximation of the time between two callbacks. This is used to schedule
|
||||
* video frames. This is in milliseconds. Only even used (after
|
||||
* inizatialization) on the audio callback thread. */
|
||||
|
|
|
@ -634,13 +634,15 @@ AudioDevice::GetSource()
|
|||
}
|
||||
|
||||
nsresult VideoDevice::Allocate(const dom::MediaTrackConstraints &aConstraints,
|
||||
const MediaEnginePrefs &aPrefs) {
|
||||
return GetSource()->Allocate(aConstraints, aPrefs, mID);
|
||||
const MediaEnginePrefs &aPrefs,
|
||||
const nsACString& aOrigin) {
|
||||
return GetSource()->Allocate(aConstraints, aPrefs, mID, aOrigin);
|
||||
}
|
||||
|
||||
nsresult AudioDevice::Allocate(const dom::MediaTrackConstraints &aConstraints,
|
||||
const MediaEnginePrefs &aPrefs) {
|
||||
return GetSource()->Allocate(aConstraints, aPrefs, mID);
|
||||
const MediaEnginePrefs &aPrefs,
|
||||
const nsACString& aOrigin) {
|
||||
return GetSource()->Allocate(aConstraints, aPrefs, mID, aOrigin);
|
||||
}
|
||||
|
||||
nsresult VideoDevice::Restart(const dom::MediaTrackConstraints &aConstraints,
|
||||
|
@ -1203,7 +1205,8 @@ public:
|
|||
nsresult rv;
|
||||
|
||||
if (mAudioDevice) {
|
||||
rv = mAudioDevice->Allocate(GetInvariant(mConstraints.mAudio), mPrefs);
|
||||
rv = mAudioDevice->Allocate(GetInvariant(mConstraints.mAudio),
|
||||
mPrefs, mOrigin);
|
||||
if (NS_FAILED(rv)) {
|
||||
LOG(("Failed to allocate audiosource %d",rv));
|
||||
Fail(NS_LITERAL_STRING("SourceUnavailableError"),
|
||||
|
@ -1212,7 +1215,8 @@ public:
|
|||
}
|
||||
}
|
||||
if (mVideoDevice) {
|
||||
rv = mVideoDevice->Allocate(GetInvariant(mConstraints.mVideo), mPrefs);
|
||||
rv = mVideoDevice->Allocate(GetInvariant(mConstraints.mVideo),
|
||||
mPrefs, mOrigin);
|
||||
if (NS_FAILED(rv)) {
|
||||
LOG(("Failed to allocate videosource %d\n",rv));
|
||||
if (mAudioDevice) {
|
||||
|
|
|
@ -94,7 +94,8 @@ public:
|
|||
NS_IMETHOD GetType(nsAString& aType);
|
||||
Source* GetSource();
|
||||
nsresult Allocate(const dom::MediaTrackConstraints &aConstraints,
|
||||
const MediaEnginePrefs &aPrefs);
|
||||
const MediaEnginePrefs &aPrefs,
|
||||
const nsACString& aOrigin);
|
||||
nsresult Restart(const dom::MediaTrackConstraints &aConstraints,
|
||||
const MediaEnginePrefs &aPrefs);
|
||||
};
|
||||
|
@ -108,7 +109,8 @@ public:
|
|||
NS_IMETHOD GetType(nsAString& aType);
|
||||
Source* GetSource();
|
||||
nsresult Allocate(const dom::MediaTrackConstraints &aConstraints,
|
||||
const MediaEnginePrefs &aPrefs);
|
||||
const MediaEnginePrefs &aPrefs,
|
||||
const nsACString& aOrigin);
|
||||
nsresult Restart(const dom::MediaTrackConstraints &aConstraints,
|
||||
const MediaEnginePrefs &aPrefs);
|
||||
};
|
||||
|
|
|
@ -938,23 +938,37 @@ void
|
|||
MediaStreamGraphImpl::OpenAudioInputImpl(CubebUtils::AudioDeviceID aID,
|
||||
AudioDataListener *aListener)
|
||||
{
|
||||
// Bug 1238038 Need support for multiple mics at once
|
||||
MOZ_ASSERT(!mInputWanted);
|
||||
if (mInputWanted) {
|
||||
// Bug 1238038 Need support for multiple mics at once
|
||||
if (mInputDeviceUsers.Count() > 0 &&
|
||||
!mInputDeviceUsers.Get(aListener, nullptr)) {
|
||||
NS_ASSERTION(false, "Input from multiple mics not yet supported; bug 1238038");
|
||||
// Need to support separate input-only AudioCallback drivers; they'll
|
||||
// call us back on "other" threads. We will need to echo-cancel them, though.
|
||||
return;
|
||||
}
|
||||
mInputWanted = true;
|
||||
|
||||
// Add to count of users for this ID.
|
||||
// XXX Since we can't rely on IDs staying valid (ugh), use the listener as
|
||||
// a stand-in for the ID. Fix as part of support for multiple-captures
|
||||
// (Bug 1238038)
|
||||
uint32_t count = 0;
|
||||
mInputDeviceUsers.Get(aListener, &count); // ok if this fails
|
||||
count++;
|
||||
mInputDeviceUsers.Put(aListener, count); // creates a new entry in the hash if needed
|
||||
|
||||
// aID is a cubeb_devid, and we assume that opaque ptr is valid until
|
||||
// we close cubeb.
|
||||
mInputDeviceID = aID;
|
||||
mAudioInputs.AppendElement(aListener); // always monitor speaker data
|
||||
if (count == 1) { // first open for this listener
|
||||
mAudioInputs.AppendElement(aListener); // always monitor speaker data
|
||||
}
|
||||
|
||||
// Switch Drivers since we're adding input (to input-only or full-duplex)
|
||||
MonitorAutoLock mon(mMonitor);
|
||||
if (mLifecycleState == LIFECYCLE_RUNNING) {
|
||||
AudioCallbackDriver* driver = new AudioCallbackDriver(this);
|
||||
driver->SetInputListener(aListener);
|
||||
CurrentDriver()->SwitchAtNextIteration(driver);
|
||||
}
|
||||
}
|
||||
|
@ -985,6 +999,7 @@ MediaStreamGraphImpl::OpenAudioInput(CubebUtils::AudioDeviceID aID,
|
|||
CubebUtils::AudioDeviceID mID;
|
||||
RefPtr<AudioDataListener> mListener;
|
||||
};
|
||||
// XXX Check not destroyed!
|
||||
this->AppendMessage(MakeUnique<Message>(this, aID, aListener));
|
||||
return NS_OK;
|
||||
}
|
||||
|
@ -992,9 +1007,20 @@ MediaStreamGraphImpl::OpenAudioInput(CubebUtils::AudioDeviceID aID,
|
|||
void
|
||||
MediaStreamGraphImpl::CloseAudioInputImpl(AudioDataListener *aListener)
|
||||
{
|
||||
uint32_t count;
|
||||
DebugOnly<bool> result = mInputDeviceUsers.Get(aListener, &count);
|
||||
MOZ_ASSERT(result);
|
||||
if (--count > 0) {
|
||||
mInputDeviceUsers.Put(aListener, count);
|
||||
return; // still in use
|
||||
}
|
||||
mInputDeviceUsers.Remove(aListener);
|
||||
mInputDeviceID = nullptr;
|
||||
mInputWanted = false;
|
||||
CurrentDriver()->RemoveInputListener(aListener);
|
||||
AudioCallbackDriver *driver = CurrentDriver()->AsAudioCallbackDriver();
|
||||
if (driver) {
|
||||
driver->RemoveInputListener(aListener);
|
||||
}
|
||||
mAudioInputs.RemoveElement(aListener);
|
||||
|
||||
// Switch Drivers since we're adding or removing an input (to nothing/system or output only)
|
||||
|
@ -1059,10 +1085,10 @@ MediaStreamGraphImpl::CloseAudioInput(AudioDataListener *aListener)
|
|||
// All AudioInput listeners get the same speaker data (at least for now).
|
||||
void
|
||||
MediaStreamGraph::NotifyOutputData(AudioDataValue* aBuffer, size_t aFrames,
|
||||
uint32_t aChannels)
|
||||
TrackRate aRate, uint32_t aChannels)
|
||||
{
|
||||
for (auto& listener : mAudioInputs) {
|
||||
listener->NotifyOutputData(this, aBuffer, aFrames, aChannels);
|
||||
listener->NotifyOutputData(this, aBuffer, aFrames, aRate, aChannels);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2295,9 +2321,32 @@ MediaStream::AddMainThreadListener(MainThreadMediaStreamListener* aListener)
|
|||
NS_WARN_IF(NS_FAILED(NS_DispatchToMainThread(runnable.forget())));
|
||||
}
|
||||
|
||||
nsresult
|
||||
SourceMediaStream::OpenAudioInput(CubebUtils::AudioDeviceID aID,
|
||||
AudioDataListener *aListener)
|
||||
{
|
||||
if (GraphImpl()) {
|
||||
mInputListener = aListener;
|
||||
return GraphImpl()->OpenAudioInput(aID, aListener);
|
||||
}
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
void
|
||||
SourceMediaStream::CloseAudioInput()
|
||||
{
|
||||
// Destroy() may have run already and cleared this
|
||||
if (GraphImpl() && mInputListener) {
|
||||
GraphImpl()->CloseAudioInput(mInputListener);
|
||||
}
|
||||
mInputListener = nullptr;
|
||||
}
|
||||
|
||||
void
|
||||
SourceMediaStream::DestroyImpl()
|
||||
{
|
||||
CloseAudioInput();
|
||||
|
||||
// Hold mMutex while mGraph is reset so that other threads holding mMutex
|
||||
// can null-check know that the graph will not destroyed.
|
||||
MutexAutoLock lock(mMutex);
|
||||
|
|
|
@ -197,14 +197,14 @@ public:
|
|||
*/
|
||||
virtual void NotifyOutputData(MediaStreamGraph* aGraph,
|
||||
AudioDataValue* aBuffer, size_t aFrames,
|
||||
uint32_t aChannels) = 0;
|
||||
TrackRate aRate, uint32_t aChannels) = 0;
|
||||
/**
|
||||
* Input data from a microphone (or other audio source. This is not
|
||||
* guaranteed to be in any particular size chunks.
|
||||
*/
|
||||
virtual void NotifyInputData(MediaStreamGraph* aGraph,
|
||||
const AudioDataValue* aBuffer, size_t aFrames,
|
||||
uint32_t aChannels) = 0;
|
||||
TrackRate aRate, uint32_t aChannels) = 0;
|
||||
};
|
||||
|
||||
class AudioDataListener : public AudioDataListenerInterface {
|
||||
|
@ -734,6 +734,16 @@ public:
|
|||
SourceMediaStream* AsSourceStream() override { return this; }
|
||||
|
||||
// Media graph thread only
|
||||
|
||||
// Users of audio inputs go through the stream so it can track when the
|
||||
// last stream referencing an input goes away, so it can close the cubeb
|
||||
// input. Also note: callable on any thread (though it bounces through
|
||||
// MainThread to set the command if needed).
|
||||
nsresult OpenAudioInput(CubebUtils::AudioDeviceID aID,
|
||||
AudioDataListener *aListener);
|
||||
// Note: also implied when Destroy() happens
|
||||
void CloseAudioInput();
|
||||
|
||||
void DestroyImpl() override;
|
||||
|
||||
// Call these on any thread.
|
||||
|
@ -920,6 +930,12 @@ protected:
|
|||
void NotifyDirectConsumers(TrackData *aTrack,
|
||||
MediaSegment *aSegment);
|
||||
|
||||
// Only accessed on the MSG thread. Used so to ask the MSGImpl to usecount
|
||||
// users of a specific input.
|
||||
// XXX Should really be a CubebUtils::AudioDeviceID, but they aren't
|
||||
// copyable (opaque pointers)
|
||||
RefPtr<AudioDataListener> mInputListener;
|
||||
|
||||
// This must be acquired *before* MediaStreamGraphImpl's lock, if they are
|
||||
// held together.
|
||||
Mutex mMutex;
|
||||
|
@ -1298,7 +1314,7 @@ public:
|
|||
* to notify any listeners (for echo cancellation).
|
||||
*/
|
||||
void NotifyOutputData(AudioDataValue* aBuffer, size_t aFrames,
|
||||
uint32_t aChannels);
|
||||
TrackRate aRate, uint32_t aChannels);
|
||||
|
||||
protected:
|
||||
explicit MediaStreamGraph(TrackRate aSampleRate)
|
||||
|
|
|
@ -8,6 +8,8 @@
|
|||
|
||||
#include "MediaStreamGraph.h"
|
||||
|
||||
#include "nsDataHashtable.h"
|
||||
|
||||
#include "mozilla/Monitor.h"
|
||||
#include "mozilla/TimeStamp.h"
|
||||
#include "nsIMemoryReporter.h"
|
||||
|
@ -629,6 +631,9 @@ public:
|
|||
CubebUtils::AudioDeviceID mInputDeviceID;
|
||||
bool mOutputWanted;
|
||||
CubebUtils::AudioDeviceID mOutputDeviceID;
|
||||
// Maps AudioDataListeners to a usecount of streams using the listener
|
||||
// so we can know when it's no longer in use.
|
||||
nsDataHashtable<nsPtrHashKey<AudioDataListener>, uint32_t> mInputDeviceUsers;
|
||||
|
||||
// True if the graph needs another iteration after the current iteration.
|
||||
Atomic<bool> mNeedAnotherIteration;
|
||||
|
|
|
@ -169,7 +169,7 @@ void
|
|||
TrackBuffersManager::ResetParserState()
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
MOZ_RELEASE_ASSERT(!mAppendRunning, "Append is running, abort must have been called");
|
||||
MOZ_DIAGNOSTIC_ASSERT(!mAppendRunning, "Append is running, abort must have been called");
|
||||
MSE_DEBUG("");
|
||||
|
||||
// 1. If the append state equals PARSING_MEDIA_SEGMENT and the input buffer contains some complete coded frames, then run the coded frame processing algorithm until all of these complete coded frames have been processed.
|
||||
|
@ -187,7 +187,7 @@ RefPtr<TrackBuffersManager::RangeRemovalPromise>
|
|||
TrackBuffersManager::RangeRemoval(TimeUnit aStart, TimeUnit aEnd)
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
MOZ_RELEASE_ASSERT(!mAppendRunning, "Append is running");
|
||||
MOZ_DIAGNOSTIC_ASSERT(!mAppendRunning, "Append is running");
|
||||
MSE_DEBUG("From %.2f to %.2f", aStart.ToSeconds(), aEnd.ToSeconds());
|
||||
|
||||
mEnded = false;
|
||||
|
@ -301,7 +301,7 @@ void
|
|||
TrackBuffersManager::CompleteResetParserState()
|
||||
{
|
||||
MOZ_ASSERT(OnTaskQueue());
|
||||
MOZ_RELEASE_ASSERT(!mSegmentParserLoopRunning);
|
||||
MOZ_DIAGNOSTIC_ASSERT(!mSegmentParserLoopRunning);
|
||||
MSE_DEBUG("");
|
||||
|
||||
for (auto& track : GetTracksList()) {
|
||||
|
@ -539,7 +539,7 @@ RefPtr<TrackBuffersManager::AppendPromise>
|
|||
TrackBuffersManager::InitSegmentParserLoop()
|
||||
{
|
||||
MOZ_ASSERT(OnTaskQueue());
|
||||
MOZ_RELEASE_ASSERT(mAppendPromise.IsEmpty());
|
||||
MOZ_DIAGNOSTIC_ASSERT(mAppendPromise.IsEmpty());
|
||||
MSE_DEBUG("");
|
||||
|
||||
RefPtr<AppendPromise> p = mAppendPromise.Ensure(__func__);
|
||||
|
|
|
@ -157,7 +157,7 @@ public:
|
|||
NS_IMETHOD Run() {
|
||||
NS_ASSERTION(NS_IsMainThread(), "Must be on main thread.");
|
||||
if (mBackend == LayersBackend::LAYERS_D3D11 &&
|
||||
Preferences::GetBool("media.windows-media-foundation.allow-d3d11-dxva", false) &&
|
||||
Preferences::GetBool("media.windows-media-foundation.allow-d3d11-dxva", true) &&
|
||||
IsWin8OrLater()) {
|
||||
mDXVA2Manager = DXVA2Manager::CreateD3D11DXVA(mFailureReason);
|
||||
} else {
|
||||
|
|
|
@ -351,13 +351,15 @@ int
|
|||
CamerasChild::AllocateCaptureDevice(CaptureEngine aCapEngine,
|
||||
const char* unique_idUTF8,
|
||||
const unsigned int unique_idUTF8Length,
|
||||
int& capture_id)
|
||||
int& capture_id,
|
||||
const nsACString& aOrigin)
|
||||
{
|
||||
LOG((__PRETTY_FUNCTION__));
|
||||
nsCString unique_id(unique_idUTF8);
|
||||
nsCString origin(aOrigin);
|
||||
nsCOMPtr<nsIRunnable> runnable =
|
||||
media::NewRunnableFrom([this, aCapEngine, unique_id]() -> nsresult {
|
||||
if (this->SendAllocateCaptureDevice(aCapEngine, unique_id)) {
|
||||
media::NewRunnableFrom([this, aCapEngine, unique_id, origin]() -> nsresult {
|
||||
if (this->SendAllocateCaptureDevice(aCapEngine, unique_id, origin)) {
|
||||
return NS_OK;
|
||||
}
|
||||
return NS_ERROR_FAILURE;
|
||||
|
|
|
@ -181,7 +181,8 @@ public:
|
|||
int AllocateCaptureDevice(CaptureEngine aCapEngine,
|
||||
const char* unique_idUTF8,
|
||||
const unsigned int unique_idUTF8Length,
|
||||
int& capture_id);
|
||||
int& capture_id,
|
||||
const nsACString& aOrigin);
|
||||
int GetCaptureCapability(CaptureEngine aCapEngine,
|
||||
const char* unique_idUTF8,
|
||||
const unsigned int capability_number,
|
||||
|
|
|
@ -11,10 +11,14 @@
|
|||
|
||||
#include "mozilla/Assertions.h"
|
||||
#include "mozilla/unused.h"
|
||||
#include "mozilla/Services.h"
|
||||
#include "mozilla/Logging.h"
|
||||
#include "mozilla/ipc/BackgroundParent.h"
|
||||
#include "mozilla/Preferences.h"
|
||||
#include "nsIPermissionManager.h"
|
||||
#include "nsThreadUtils.h"
|
||||
#include "nsXPCOM.h"
|
||||
#include "nsNetUtil.h"
|
||||
|
||||
#include "webrtc/common_video/libyuv/include/webrtc_libyuv.h"
|
||||
|
||||
|
@ -641,39 +645,127 @@ CamerasParent::RecvGetCaptureDevice(const int& aCapEngine,
|
|||
return true;
|
||||
}
|
||||
|
||||
static nsresult
|
||||
GetPrincipalFromOrigin(const nsACString& aOrigin, nsIPrincipal** aPrincipal)
|
||||
{
|
||||
nsAutoCString originNoSuffix;
|
||||
mozilla::PrincipalOriginAttributes attrs;
|
||||
if (!attrs.PopulateFromOrigin(aOrigin, originNoSuffix)) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
nsCOMPtr<nsIURI> uri;
|
||||
nsresult rv = NS_NewURI(getter_AddRefs(uri), originNoSuffix);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
nsCOMPtr<nsIPrincipal> principal = mozilla::BasePrincipal::CreateCodebasePrincipal(uri, attrs);
|
||||
principal.forget(aPrincipal);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
// Find out whether the given origin has permission to use the
|
||||
// camera. If the permission is not persistent, we'll make it
|
||||
// a one-shot by removing the (session) permission.
|
||||
static bool
|
||||
HasCameraPermission(const nsCString& aOrigin)
|
||||
{
|
||||
// Name used with nsIPermissionManager
|
||||
static const char* cameraPermission = "camera";
|
||||
bool allowed = false;
|
||||
bool permanent = false;
|
||||
nsresult rv;
|
||||
nsCOMPtr<nsIPermissionManager> mgr =
|
||||
do_GetService(NS_PERMISSIONMANAGER_CONTRACTID, &rv);
|
||||
if (NS_SUCCEEDED(rv)) {
|
||||
nsCOMPtr<nsIIOService> ioServ(do_GetIOService());
|
||||
nsCOMPtr<nsIURI> uri;
|
||||
rv = ioServ->NewURI(aOrigin, nullptr, nullptr, getter_AddRefs(uri));
|
||||
if (NS_SUCCEEDED(rv)) {
|
||||
// Permanent permissions are only retrievable via principal, not uri
|
||||
nsCOMPtr<nsIPrincipal> principal;
|
||||
rv = GetPrincipalFromOrigin(aOrigin, getter_AddRefs(principal));
|
||||
if (NS_SUCCEEDED(rv)) {
|
||||
uint32_t video = nsIPermissionManager::UNKNOWN_ACTION;
|
||||
rv = mgr->TestExactPermissionFromPrincipal(principal,
|
||||
cameraPermission,
|
||||
&video);
|
||||
if (NS_SUCCEEDED(rv)) {
|
||||
allowed = (video == nsIPermissionManager::ALLOW_ACTION);
|
||||
// Was allowed, now see if this is a persistent permission
|
||||
// or a session one.
|
||||
if (allowed) {
|
||||
rv = mgr->TestExactPermanentPermission(principal,
|
||||
cameraPermission,
|
||||
&video);
|
||||
if (NS_SUCCEEDED(rv)) {
|
||||
permanent = (video == nsIPermissionManager::ALLOW_ACTION);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Session permissions are removed after one use.
|
||||
if (allowed && !permanent) {
|
||||
mgr->RemoveFromPrincipal(principal, cameraPermission);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return allowed;
|
||||
}
|
||||
|
||||
bool
|
||||
CamerasParent::RecvAllocateCaptureDevice(const int& aCapEngine,
|
||||
const nsCString& unique_id)
|
||||
const nsCString& unique_id,
|
||||
const nsCString& aOrigin)
|
||||
{
|
||||
LOG((__PRETTY_FUNCTION__));
|
||||
|
||||
LOG(("%s: Verifying permissions for %s", __PRETTY_FUNCTION__, aOrigin.get()));
|
||||
RefPtr<CamerasParent> self(this);
|
||||
RefPtr<nsRunnable> webrtc_runnable =
|
||||
media::NewRunnableFrom([self, aCapEngine, unique_id]() -> nsresult {
|
||||
int numdev = -1;
|
||||
int error = -1;
|
||||
if (self->EnsureInitialized(aCapEngine)) {
|
||||
error = self->mEngines[aCapEngine].mPtrViECapture->AllocateCaptureDevice(
|
||||
unique_id.get(), MediaEngineSource::kMaxUniqueIdLength, numdev);
|
||||
RefPtr<nsRunnable> mainthread_runnable =
|
||||
media::NewRunnableFrom([self, aCapEngine, unique_id, aOrigin]() -> nsresult {
|
||||
// Verify whether the claimed origin has received permission
|
||||
// to use the camera, either persistently or this session (one shot).
|
||||
bool allowed = HasCameraPermission(aOrigin);
|
||||
if (!allowed) {
|
||||
// Developer preference for turning off permission check.
|
||||
if (Preferences::GetBool("media.navigator.permission.disabled", false)
|
||||
|| Preferences::GetBool("media.navigator.permission.fake")) {
|
||||
allowed = true;
|
||||
LOG(("No permission but checks are disabled or fake sources active"));
|
||||
} else {
|
||||
LOG(("No camera permission for this origin"));
|
||||
}
|
||||
}
|
||||
RefPtr<nsIRunnable> ipc_runnable =
|
||||
media::NewRunnableFrom([self, numdev, error]() -> nsresult {
|
||||
if (self->IsShuttingDown()) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
if (error) {
|
||||
Unused << self->SendReplyFailure();
|
||||
return NS_ERROR_FAILURE;
|
||||
} else {
|
||||
LOG(("Allocated device nr %d", numdev));
|
||||
Unused << self->SendReplyAllocateCaptureDevice(numdev);
|
||||
return NS_OK;
|
||||
}
|
||||
// After retrieving the permission (or not) on the main thread,
|
||||
// bounce to the WebRTC thread to allocate the device (or not),
|
||||
// then bounce back to the IPC thread for the reply to content.
|
||||
RefPtr<nsRunnable> webrtc_runnable =
|
||||
media::NewRunnableFrom([self, allowed, aCapEngine, unique_id]() -> nsresult {
|
||||
int numdev = -1;
|
||||
int error = -1;
|
||||
if (allowed && self->EnsureInitialized(aCapEngine)) {
|
||||
error = self->mEngines[aCapEngine].mPtrViECapture->AllocateCaptureDevice(
|
||||
unique_id.get(), MediaEngineSource::kMaxUniqueIdLength, numdev);
|
||||
}
|
||||
RefPtr<nsIRunnable> ipc_runnable =
|
||||
media::NewRunnableFrom([self, numdev, error]() -> nsresult {
|
||||
if (self->IsShuttingDown()) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
if (error) {
|
||||
Unused << self->SendReplyFailure();
|
||||
return NS_ERROR_FAILURE;
|
||||
} else {
|
||||
LOG(("Allocated device nr %d", numdev));
|
||||
Unused << self->SendReplyAllocateCaptureDevice(numdev);
|
||||
return NS_OK;
|
||||
}
|
||||
});
|
||||
self->mPBackgroundThread->Dispatch(ipc_runnable, NS_DISPATCH_NORMAL);
|
||||
return NS_OK;
|
||||
});
|
||||
self->mPBackgroundThread->Dispatch(ipc_runnable, NS_DISPATCH_NORMAL);
|
||||
self->DispatchToVideoCaptureThread(webrtc_runnable);
|
||||
return NS_OK;
|
||||
});
|
||||
DispatchToVideoCaptureThread(webrtc_runnable);
|
||||
NS_DispatchToMainThread(mainthread_runnable);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
@ -86,7 +86,7 @@ public:
|
|||
static already_AddRefed<CamerasParent> Create();
|
||||
|
||||
// Messages received form the child. These run on the IPC/PBackground thread.
|
||||
virtual bool RecvAllocateCaptureDevice(const int&, const nsCString&) override;
|
||||
virtual bool RecvAllocateCaptureDevice(const int&, const nsCString&, const nsCString&) override;
|
||||
virtual bool RecvReleaseCaptureDevice(const int&, const int &) override;
|
||||
virtual bool RecvNumberOfCaptureDevices(const int&) override;
|
||||
virtual bool RecvNumberOfCapabilities(const int&, const nsCString&) override;
|
||||
|
|
|
@ -45,7 +45,7 @@ parent:
|
|||
async GetCaptureCapability(int engine, nsCString unique_idUTF8, int capability_number);
|
||||
async GetCaptureDevice(int engine, int num);
|
||||
|
||||
async AllocateCaptureDevice(int engine, nsCString unique_idUTF8);
|
||||
async AllocateCaptureDevice(int engine, nsCString unique_idUTF8, nsCString origin);
|
||||
async ReleaseCaptureDevice(int engine, int numdev);
|
||||
async StartCapture(int engine, int numdev, CaptureCapability capability);
|
||||
async StopCapture(int engine, int numdev);
|
||||
|
|
|
@ -67,6 +67,7 @@ for (var i=0; i<gSmallTests.length; ++i) {
|
|||
}
|
||||
// ensure metadata is loaded for default preload is none on b2g
|
||||
v.preload = "metadata";
|
||||
v.autoplay = "true";
|
||||
v._complete = false;
|
||||
v.addEventListener("error", loadError, false);
|
||||
v.addEventListener("loadedmetadata", loadedMetadata, false);
|
||||
|
|
|
@ -171,7 +171,8 @@ public:
|
|||
/* This call reserves but does not start the device. */
|
||||
virtual nsresult Allocate(const dom::MediaTrackConstraints &aConstraints,
|
||||
const MediaEnginePrefs &aPrefs,
|
||||
const nsString& aDeviceId) = 0;
|
||||
const nsString& aDeviceId,
|
||||
const nsACString& aOrigin) = 0;
|
||||
|
||||
virtual uint32_t GetBestFitnessDistance(
|
||||
const nsTArray<const dom::MediaTrackConstraintSet*>& aConstraintSets,
|
||||
|
|
|
@ -87,7 +87,8 @@ MediaEngineDefaultVideoSource::GetBestFitnessDistance(
|
|||
nsresult
|
||||
MediaEngineDefaultVideoSource::Allocate(const dom::MediaTrackConstraints &aConstraints,
|
||||
const MediaEnginePrefs &aPrefs,
|
||||
const nsString& aDeviceId)
|
||||
const nsString& aDeviceId,
|
||||
const nsACString& aOrigin)
|
||||
{
|
||||
if (mState != kReleased) {
|
||||
return NS_ERROR_FAILURE;
|
||||
|
@ -396,7 +397,8 @@ MediaEngineDefaultAudioSource::GetBestFitnessDistance(
|
|||
nsresult
|
||||
MediaEngineDefaultAudioSource::Allocate(const dom::MediaTrackConstraints &aConstraints,
|
||||
const MediaEnginePrefs &aPrefs,
|
||||
const nsString& aDeviceId)
|
||||
const nsString& aDeviceId,
|
||||
const nsACString& aOrigin)
|
||||
{
|
||||
if (mState != kReleased) {
|
||||
return NS_ERROR_FAILURE;
|
||||
|
|
|
@ -45,7 +45,8 @@ public:
|
|||
|
||||
nsresult Allocate(const dom::MediaTrackConstraints &aConstraints,
|
||||
const MediaEnginePrefs &aPrefs,
|
||||
const nsString& aDeviceId) override;
|
||||
const nsString& aDeviceId,
|
||||
const nsACString& aOrigin) override;
|
||||
nsresult Deallocate() override;
|
||||
nsresult Start(SourceMediaStream*, TrackID) override;
|
||||
nsresult Stop(SourceMediaStream*, TrackID) override;
|
||||
|
@ -114,7 +115,8 @@ public:
|
|||
|
||||
nsresult Allocate(const dom::MediaTrackConstraints &aConstraints,
|
||||
const MediaEnginePrefs &aPrefs,
|
||||
const nsString& aDeviceId) override;
|
||||
const nsString& aDeviceId,
|
||||
const nsACString& aOrigin) override;
|
||||
nsresult Deallocate() override;
|
||||
nsresult Start(SourceMediaStream*, TrackID) override;
|
||||
nsresult Stop(SourceMediaStream*, TrackID) override;
|
||||
|
@ -138,11 +140,11 @@ public:
|
|||
|
||||
void NotifyOutputData(MediaStreamGraph* aGraph,
|
||||
AudioDataValue* aBuffer, size_t aFrames,
|
||||
uint32_t aChannels) override
|
||||
TrackRate aRate, uint32_t aChannels) override
|
||||
{}
|
||||
void NotifyInputData(MediaStreamGraph* aGraph,
|
||||
const AudioDataValue* aBuffer, size_t aFrames,
|
||||
uint32_t aChannels) override
|
||||
TrackRate aRate, uint32_t aChannels) override
|
||||
{}
|
||||
bool IsFake() override {
|
||||
return true;
|
||||
|
|
|
@ -100,7 +100,8 @@ MediaEngineRemoteVideoSource::Shutdown()
|
|||
nsresult
|
||||
MediaEngineRemoteVideoSource::Allocate(const dom::MediaTrackConstraints& aConstraints,
|
||||
const MediaEnginePrefs& aPrefs,
|
||||
const nsString& aDeviceId)
|
||||
const nsString& aDeviceId,
|
||||
const nsACString& aOrigin)
|
||||
{
|
||||
LOG((__PRETTY_FUNCTION__));
|
||||
AssertIsOnOwningThread();
|
||||
|
@ -120,11 +121,12 @@ MediaEngineRemoteVideoSource::Allocate(const dom::MediaTrackConstraints& aConstr
|
|||
|
||||
if (mozilla::camera::GetChildAndCall(
|
||||
&mozilla::camera::CamerasChild::AllocateCaptureDevice,
|
||||
mCapEngine, GetUUID().get(), kMaxUniqueIdLength, mCaptureIndex)) {
|
||||
mCapEngine, GetUUID().get(), kMaxUniqueIdLength, mCaptureIndex, aOrigin)) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
mState = kAllocated;
|
||||
LOG(("Video device %d allocated", mCaptureIndex));
|
||||
LOG(("Video device %d allocated for %s", mCaptureIndex,
|
||||
PromiseFlatCString(aOrigin).get()));
|
||||
} else if (MOZ_LOG_TEST(GetMediaManagerLog(), mozilla::LogLevel::Debug)) {
|
||||
MonitorAutoLock lock(mMonitor);
|
||||
if (mSources.IsEmpty()) {
|
||||
|
|
|
@ -73,7 +73,8 @@ public:
|
|||
|
||||
nsresult Allocate(const dom::MediaTrackConstraints& aConstraints,
|
||||
const MediaEnginePrefs& aPrefs,
|
||||
const nsString& aDeviceId) override;
|
||||
const nsString& aDeviceId,
|
||||
const nsACString& aOrigin) override;
|
||||
nsresult Deallocate() override;;
|
||||
nsresult Start(SourceMediaStream*, TrackID) override;
|
||||
nsresult Stop(SourceMediaStream*, TrackID) override;
|
||||
|
|
|
@ -128,7 +128,8 @@ MediaEngineTabVideoSource::GetUUID(nsACString_internal& aUuid)
|
|||
nsresult
|
||||
MediaEngineTabVideoSource::Allocate(const dom::MediaTrackConstraints& aConstraints,
|
||||
const MediaEnginePrefs& aPrefs,
|
||||
const nsString& aDeviceId)
|
||||
const nsString& aDeviceId,
|
||||
const nsACString& aOrigin)
|
||||
{
|
||||
// windowId is not a proper constraint, so just read it.
|
||||
// It has no well-defined behavior in advanced, so ignore it there.
|
||||
|
|
|
@ -23,7 +23,8 @@ class MediaEngineTabVideoSource : public MediaEngineVideoSource, nsIDOMEventList
|
|||
void GetUUID(nsACString_internal&) override;
|
||||
nsresult Allocate(const dom::MediaTrackConstraints &,
|
||||
const mozilla::MediaEnginePrefs&,
|
||||
const nsString& aDeviceId) override;
|
||||
const nsString& aDeviceId,
|
||||
const nsACString& aOrigin) override;
|
||||
nsresult Deallocate() override;
|
||||
nsresult Start(mozilla::SourceMediaStream*, mozilla::TrackID) override;
|
||||
void SetDirectListeners(bool aHasDirectListeners) override {};
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
#include "mozilla/dom/File.h"
|
||||
#include "mozilla/Mutex.h"
|
||||
#include "mozilla/Monitor.h"
|
||||
#include "mozilla/UniquePtr.h"
|
||||
#include "nsCOMPtr.h"
|
||||
#include "nsThreadUtils.h"
|
||||
#include "DOMMediaStream.h"
|
||||
|
@ -72,7 +73,8 @@ public:
|
|||
void GetUUID(nsACString& aUUID) override;
|
||||
nsresult Allocate(const dom::MediaTrackConstraints& aConstraints,
|
||||
const MediaEnginePrefs& aPrefs,
|
||||
const nsString& aDeviceId) override
|
||||
const nsString& aDeviceId,
|
||||
const nsACString& aOrigin) override
|
||||
{
|
||||
// Nothing to do here, everything is managed in MediaManager.cpp
|
||||
return NS_OK;
|
||||
|
@ -95,11 +97,11 @@ public:
|
|||
{}
|
||||
void NotifyOutputData(MediaStreamGraph* aGraph,
|
||||
AudioDataValue* aBuffer, size_t aFrames,
|
||||
uint32_t aChannels) override
|
||||
TrackRate aRate, uint32_t aChannels) override
|
||||
{}
|
||||
void NotifyInputData(MediaStreamGraph* aGraph,
|
||||
const AudioDataValue* aBuffer, size_t aFrames,
|
||||
uint32_t aChannels) override
|
||||
TrackRate aRate, uint32_t aChannels) override
|
||||
{}
|
||||
void NotifyPull(MediaStreamGraph* aGraph, SourceMediaStream* aSource,
|
||||
TrackID aID, StreamTime aDesiredTime) override
|
||||
|
@ -138,8 +140,8 @@ public:
|
|||
virtual int GetRecordingDeviceName(int aIndex, char aStrNameUTF8[128],
|
||||
char aStrGuidUTF8[128]) = 0;
|
||||
virtual int GetRecordingDeviceStatus(bool& aIsAvailable) = 0;
|
||||
virtual void StartRecording(MediaStreamGraph *aGraph, AudioDataListener *aListener) = 0;
|
||||
virtual void StopRecording(MediaStreamGraph *aGraph, AudioDataListener *aListener) = 0;
|
||||
virtual void StartRecording(SourceMediaStream *aStream, AudioDataListener *aListener) = 0;
|
||||
virtual void StopRecording(SourceMediaStream *aStream) = 0;
|
||||
virtual int SetRecordingDevice(int aIndex) = 0;
|
||||
|
||||
protected:
|
||||
|
@ -153,7 +155,7 @@ class AudioInputCubeb final : public AudioInput
|
|||
{
|
||||
public:
|
||||
explicit AudioInputCubeb(webrtc::VoiceEngine* aVoiceEngine, int aIndex = 0) :
|
||||
AudioInput(aVoiceEngine), mSelectedDevice(aIndex), mInUse(false)
|
||||
AudioInput(aVoiceEngine), mSelectedDevice(aIndex), mInUseCount(0)
|
||||
{
|
||||
if (!mDeviceIndexes) {
|
||||
mDeviceIndexes = new nsTArray<int>;
|
||||
|
@ -214,25 +216,29 @@ public:
|
|||
return 0;
|
||||
}
|
||||
|
||||
void StartRecording(MediaStreamGraph *aGraph, AudioDataListener *aListener)
|
||||
void StartRecording(SourceMediaStream *aStream, AudioDataListener *aListener)
|
||||
{
|
||||
MOZ_ASSERT(mDevices);
|
||||
|
||||
ScopedCustomReleasePtr<webrtc::VoEExternalMedia> ptrVoERender;
|
||||
ptrVoERender = webrtc::VoEExternalMedia::GetInterface(mVoiceEngine);
|
||||
if (ptrVoERender) {
|
||||
ptrVoERender->SetExternalRecordingStatus(true);
|
||||
if (mInUseCount == 0) {
|
||||
ScopedCustomReleasePtr<webrtc::VoEExternalMedia> ptrVoERender;
|
||||
ptrVoERender = webrtc::VoEExternalMedia::GetInterface(mVoiceEngine);
|
||||
if (ptrVoERender) {
|
||||
ptrVoERender->SetExternalRecordingStatus(true);
|
||||
}
|
||||
mAnyInUse = true;
|
||||
}
|
||||
aGraph->OpenAudioInput(mDevices->device[mSelectedDevice]->devid, aListener);
|
||||
mInUse = true;
|
||||
mAnyInUse = true;
|
||||
mInUseCount++;
|
||||
// Always tell the stream we're using it for input
|
||||
aStream->OpenAudioInput(mDevices->device[mSelectedDevice]->devid, aListener);
|
||||
}
|
||||
|
||||
void StopRecording(MediaStreamGraph *aGraph, AudioDataListener *aListener)
|
||||
void StopRecording(SourceMediaStream *aStream)
|
||||
{
|
||||
aGraph->CloseAudioInput(aListener);
|
||||
mInUse = false;
|
||||
mAnyInUse = false;
|
||||
aStream->CloseAudioInput();
|
||||
if (--mInUseCount == 0) {
|
||||
mAnyInUse = false;
|
||||
}
|
||||
}
|
||||
|
||||
int SetRecordingDevice(int aIndex)
|
||||
|
@ -247,7 +253,7 @@ public:
|
|||
|
||||
protected:
|
||||
~AudioInputCubeb() {
|
||||
MOZ_RELEASE_ASSERT(!mInUse);
|
||||
MOZ_RELEASE_ASSERT(mInUseCount == 0);
|
||||
}
|
||||
|
||||
private:
|
||||
|
@ -270,10 +276,14 @@ private:
|
|||
// Calculate translation from existing mDevices to new devices. Note we
|
||||
// never end up with less devices than before, since people have
|
||||
// stashed indexes.
|
||||
// For some reason the "fake" device for automation is marked as DISABLED,
|
||||
// so white-list it.
|
||||
for (uint32_t i = 0; i < devices->count; i++) {
|
||||
if (devices->device[i]->type == CUBEB_DEVICE_TYPE_INPUT && // paranoia
|
||||
(devices->device[i]->state == CUBEB_DEVICE_STATE_ENABLED ||
|
||||
devices->device[i]->state == CUBEB_DEVICE_STATE_UNPLUGGED))
|
||||
devices->device[i]->state == CUBEB_DEVICE_STATE_UNPLUGGED ||
|
||||
(devices->device[i]->state == CUBEB_DEVICE_STATE_DISABLED &&
|
||||
strcmp(devices->device[i]->friendly_name, "Sine source at 440 Hz") == 0)))
|
||||
{
|
||||
auto j = mDeviceNames->IndexOf(devices->device[i]->device_id);
|
||||
if (j != nsTArray<nsCString>::NoIndex) {
|
||||
|
@ -300,7 +310,7 @@ private:
|
|||
// for this - and be careful of threading access. The mappings need to
|
||||
// updated on each re-enumeration.
|
||||
int mSelectedDevice;
|
||||
bool mInUse; // for assertions about listener lifetime
|
||||
uint32_t mInUseCount;
|
||||
|
||||
// pointers to avoid static constructors
|
||||
static nsTArray<int>* mDeviceIndexes;
|
||||
|
@ -347,8 +357,8 @@ public:
|
|||
return 0;
|
||||
}
|
||||
|
||||
void StartRecording(MediaStreamGraph *aGraph, AudioDataListener *aListener) {}
|
||||
void StopRecording(MediaStreamGraph *aGraph, AudioDataListener *aListener) {}
|
||||
void StartRecording(SourceMediaStream *aStream, AudioDataListener *aListener) {}
|
||||
void StopRecording(SourceMediaStream *aStream) {}
|
||||
|
||||
int SetRecordingDevice(int aIndex)
|
||||
{
|
||||
|
@ -379,15 +389,15 @@ public:
|
|||
// AudioDataListenerInterface methods
|
||||
virtual void NotifyOutputData(MediaStreamGraph* aGraph,
|
||||
AudioDataValue* aBuffer, size_t aFrames,
|
||||
uint32_t aChannels) override
|
||||
TrackRate aRate, uint32_t aChannels) override
|
||||
{
|
||||
mAudioSource->NotifyOutputData(aGraph, aBuffer, aFrames, aChannels);
|
||||
mAudioSource->NotifyOutputData(aGraph, aBuffer, aFrames, aRate, aChannels);
|
||||
}
|
||||
virtual void NotifyInputData(MediaStreamGraph* aGraph,
|
||||
const AudioDataValue* aBuffer, size_t aFrames,
|
||||
uint32_t aChannels) override
|
||||
TrackRate aRate, uint32_t aChannels) override
|
||||
{
|
||||
mAudioSource->NotifyInputData(aGraph, aBuffer, aFrames, aChannels);
|
||||
mAudioSource->NotifyInputData(aGraph, aBuffer, aFrames, aRate, aChannels);
|
||||
}
|
||||
|
||||
private:
|
||||
|
@ -421,7 +431,8 @@ public:
|
|||
, mAGC(webrtc::kAgcDefault)
|
||||
, mNoiseSuppress(webrtc::kNsDefault)
|
||||
, mPlayoutDelay(0)
|
||||
, mNullTransport(nullptr) {
|
||||
, mNullTransport(nullptr)
|
||||
, mInputBufferLen(0) {
|
||||
MOZ_ASSERT(aVoiceEnginePtr);
|
||||
MOZ_ASSERT(aAudioInput);
|
||||
mDeviceName.Assign(NS_ConvertUTF8toUTF16(name));
|
||||
|
@ -435,7 +446,8 @@ public:
|
|||
|
||||
nsresult Allocate(const dom::MediaTrackConstraints& aConstraints,
|
||||
const MediaEnginePrefs& aPrefs,
|
||||
const nsString& aDeviceId) override;
|
||||
const nsString& aDeviceId,
|
||||
const nsACString& aOrigin) override;
|
||||
nsresult Deallocate() override;
|
||||
nsresult Start(SourceMediaStream* aStream, TrackID aID) override;
|
||||
nsresult Stop(SourceMediaStream* aSource, TrackID aID) override;
|
||||
|
@ -452,10 +464,10 @@ public:
|
|||
// AudioDataListenerInterface methods
|
||||
void NotifyOutputData(MediaStreamGraph* aGraph,
|
||||
AudioDataValue* aBuffer, size_t aFrames,
|
||||
uint32_t aChannels) override;
|
||||
TrackRate aRate, uint32_t aChannels) override;
|
||||
void NotifyInputData(MediaStreamGraph* aGraph,
|
||||
const AudioDataValue* aBuffer, size_t aFrames,
|
||||
uint32_t aChannels) override;
|
||||
TrackRate aRate, uint32_t aChannels) override;
|
||||
|
||||
bool IsFake() override {
|
||||
return false;
|
||||
|
@ -524,6 +536,10 @@ private:
|
|||
int32_t mPlayoutDelay;
|
||||
|
||||
NullTransport *mNullTransport;
|
||||
|
||||
// For full_duplex packetizer output
|
||||
size_t mInputBufferLen;
|
||||
UniquePtr<int16_t[]> mInputBuffer;
|
||||
};
|
||||
|
||||
class MediaEngineWebRTC : public MediaEngine
|
||||
|
|
|
@ -220,7 +220,8 @@ uint32_t MediaEngineWebRTCMicrophoneSource::GetBestFitnessDistance(
|
|||
nsresult
|
||||
MediaEngineWebRTCMicrophoneSource::Allocate(const dom::MediaTrackConstraints &aConstraints,
|
||||
const MediaEnginePrefs &aPrefs,
|
||||
const nsString& aDeviceId)
|
||||
const nsString& aDeviceId,
|
||||
const nsACString& aOrigin)
|
||||
{
|
||||
AssertIsOnOwningThread();
|
||||
if (mState == kReleased) {
|
||||
|
@ -358,6 +359,8 @@ MediaEngineWebRTCMicrophoneSource::Start(SourceMediaStream *aStream,
|
|||
|
||||
if (mState == kStarted) {
|
||||
MOZ_ASSERT(aID == mTrackID);
|
||||
// Make sure we're associated with this stream
|
||||
mAudioInput->StartRecording(aStream, mListener);
|
||||
return NS_OK;
|
||||
}
|
||||
mState = kStarted;
|
||||
|
@ -374,6 +377,10 @@ MediaEngineWebRTCMicrophoneSource::Start(SourceMediaStream *aStream,
|
|||
if (mVoEBase->StartReceive(mChannel)) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
// Must be *before* StartSend() so it will notice we selected external input (full_duplex)
|
||||
mAudioInput->StartRecording(aStream, mListener);
|
||||
|
||||
if (mVoEBase->StartSend(mChannel)) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
@ -381,8 +388,6 @@ MediaEngineWebRTCMicrophoneSource::Start(SourceMediaStream *aStream,
|
|||
// Attach external media processor, so this::Process will be called.
|
||||
mVoERender->RegisterExternalMediaProcessing(mChannel, webrtc::kRecordingPerChannel, *this);
|
||||
|
||||
mAudioInput->StartRecording(aStream->Graph(), mListener);
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
|
@ -401,6 +406,7 @@ MediaEngineWebRTCMicrophoneSource::Stop(SourceMediaStream *aSource, TrackID aID)
|
|||
aSource->EndTrack(aID);
|
||||
|
||||
if (!mSources.IsEmpty()) {
|
||||
mAudioInput->StopRecording(aSource);
|
||||
return NS_OK;
|
||||
}
|
||||
if (mState != kStarted) {
|
||||
|
@ -413,7 +419,7 @@ MediaEngineWebRTCMicrophoneSource::Stop(SourceMediaStream *aSource, TrackID aID)
|
|||
mState = kStopped;
|
||||
}
|
||||
|
||||
mAudioInput->StopRecording(aSource->Graph(), mListener);
|
||||
mAudioInput->StopRecording(aSource);
|
||||
|
||||
mVoERender->DeRegisterExternalMediaProcessing(mChannel, webrtc::kRecordingPerChannel);
|
||||
|
||||
|
@ -440,6 +446,7 @@ void
|
|||
MediaEngineWebRTCMicrophoneSource::NotifyOutputData(MediaStreamGraph* aGraph,
|
||||
AudioDataValue* aBuffer,
|
||||
size_t aFrames,
|
||||
TrackRate aRate,
|
||||
uint32_t aChannels)
|
||||
{
|
||||
}
|
||||
|
@ -449,23 +456,29 @@ void
|
|||
MediaEngineWebRTCMicrophoneSource::NotifyInputData(MediaStreamGraph* aGraph,
|
||||
const AudioDataValue* aBuffer,
|
||||
size_t aFrames,
|
||||
TrackRate aRate,
|
||||
uint32_t aChannels)
|
||||
{
|
||||
// This will call Process() with data coming out of the AEC/NS/AGC/etc chain
|
||||
if (!mPacketizer ||
|
||||
mPacketizer->PacketSize() != mSampleFrequency/100 ||
|
||||
mPacketizer->PacketSize() != aRate/100u ||
|
||||
mPacketizer->Channels() != aChannels) {
|
||||
// It's ok to drop the audio still in the packetizer here.
|
||||
mPacketizer = new AudioPacketizer<AudioDataValue, int16_t>(mSampleFrequency/100, aChannels);
|
||||
}
|
||||
mPacketizer = new AudioPacketizer<AudioDataValue, int16_t>(aRate/100, aChannels);
|
||||
}
|
||||
|
||||
mPacketizer->Input(aBuffer, static_cast<uint32_t>(aFrames));
|
||||
|
||||
while (mPacketizer->PacketsAvailable()) {
|
||||
uint32_t samplesPerPacket = mPacketizer->PacketSize() *
|
||||
mPacketizer->Channels();
|
||||
int16_t* packet = mPacketizer->Output();
|
||||
mVoERender->ExternalRecordingInsertData(packet, samplesPerPacket, mSampleFrequency, 0);
|
||||
if (mInputBufferLen < samplesPerPacket) {
|
||||
mInputBuffer = MakeUnique<int16_t[]>(samplesPerPacket);
|
||||
}
|
||||
int16_t *packet = mInputBuffer.get();
|
||||
mPacketizer->Output(packet);
|
||||
|
||||
mVoERender->ExternalRecordingInsertData(packet, samplesPerPacket, aRate, 0);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -114,6 +114,7 @@ DIRS += [
|
|||
'manifest',
|
||||
'vr',
|
||||
'newapps',
|
||||
'u2f',
|
||||
]
|
||||
|
||||
if CONFIG['OS_ARCH'] == 'WINNT':
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
var Ci = Components.interfaces;
|
||||
var Cc = Components.classes;
|
||||
var am = Cc["@mozilla.org/network/http-auth-manager;1"].
|
||||
getService(Ci.nsIHttpAuthManager);
|
||||
am.setAuthIdentity("http", "mochi.test", 8888, "basic", "testrealm", "",
|
||||
"mochi.test", "user1", "password1");
|
|
@ -3,6 +3,7 @@ skip-if = ((buildapp == 'mulet' || buildapp == 'b2g') && toolkit != 'gonk') #b2g
|
|||
support-files =
|
||||
307-xo-redirect.sjs
|
||||
crashing_subpage.html
|
||||
file_authident.js
|
||||
file_bug738396.html
|
||||
file_bug771202.html
|
||||
file_bug863792.html
|
||||
|
@ -66,7 +67,6 @@ skip-if = !crashreporter || e10s
|
|||
[test_enumerate.html]
|
||||
[test_fullpage.html]
|
||||
[test_getauthenticationinfo.html]
|
||||
skip-if = e10s # Bug 1237834
|
||||
[test_hanging.html]
|
||||
skip-if = !crashreporter || e10s
|
||||
[test_instance_re-parent.html]
|
||||
|
|
|
@ -61,11 +61,17 @@ function iframeLoad() {
|
|||
SimpleTest.waitForExplicitFinish();
|
||||
setTestPluginEnabledState(SpecialPowers.Ci.nsIPluginTag.STATE_ENABLED);
|
||||
|
||||
// Authentication info is added twice here. In the non-e10s case, this does
|
||||
// nothing. In the e10s case, we need to add auth info in both the child process,
|
||||
// which the plugin checks for auth validity, and the parent process, which the
|
||||
// http network objects use.
|
||||
// TODO: Clean this up once HTTPAuthManager is made e10s compliant in bug 1249172
|
||||
var iframe = document.getElementById("iframe");
|
||||
var am = Cc["@mozilla.org/network/http-auth-manager;1"].
|
||||
getService(Ci.nsIHttpAuthManager);
|
||||
am.setAuthIdentity("http", "mochi.test", 8888, "basic", "testrealm", "",
|
||||
"mochi.test", "user1", "password1");
|
||||
SpecialPowers.loadChromeScript(SimpleTest.getTestFileURL("file_authident.js"));
|
||||
iframe.onload = iframeLoad;
|
||||
iframe.src = "http://mochi.test:8888/tests/toolkit/components/passwordmgr/" +
|
||||
"test/authenticate.sjs?user=user1&pass=password1&realm=testrealm&plugin=1";
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
#include "nsWrapperCache.h"
|
||||
#include "mozilla/ErrorResult.h"
|
||||
|
||||
class nsIDOMWindow;
|
||||
class nsPIDOMWindowInner;
|
||||
|
||||
namespace mozilla {
|
||||
namespace dom {
|
||||
|
|
|
@ -98,6 +98,8 @@ skip-if = buildapp == 'mulet' || buildapp == 'b2g' # b2g(Mouse selection not wor
|
|||
[test_text_update.html]
|
||||
[test_transform.xhtml]
|
||||
[test_transformParsing.html]
|
||||
[test_use_with_hsts.html]
|
||||
support-files = use-with-hsts-helper.html use-with-hsts-helper.html^headers^
|
||||
[test_valueAsString.xhtml]
|
||||
[test_valueLeaks.xhtml]
|
||||
[test_viewport.html]
|
||||
|
|
|
@ -0,0 +1,139 @@
|
|||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<!--
|
||||
https://bugzilla.mozilla.org/show_bug.cgi?id=1247733
|
||||
-->
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Test for Bug 1247733</title>
|
||||
<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<script type="application/javascript" src="/tests/SimpleTest/WindowSnapshot.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
|
||||
</head>
|
||||
<body>
|
||||
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1247733">Mozilla Bug 1247733</a>
|
||||
<p id="display">
|
||||
<iframe id="myIframe"></iframe>
|
||||
</p>
|
||||
<div id="content" style="display: none">
|
||||
|
||||
</div>
|
||||
<pre id="test"></pre>
|
||||
<script type="application/javascript">
|
||||
/** Test for Bug 1247733 **/
|
||||
|
||||
/**
|
||||
* This test ensures that we render the SVG 'use' element correctly, in
|
||||
* pages that have been upgraded from HTTP to HTTPS using strict transport
|
||||
* security (HSTS)
|
||||
*
|
||||
* Specifically:
|
||||
* (1) We load a file using HTTPS, in an iframe. The file gets sent
|
||||
* with a Strict-Transport-Security flag.
|
||||
* (2) We load the same file again, but now over HTTP (which should get
|
||||
* upgraded to HTTPS, since we received the Strict-Transport-Security
|
||||
* flag during the first load).
|
||||
* (3) After each of the above loads, we take a snapshot of the iframe
|
||||
* and ensure that it renders as fully lime (which the 'use' element
|
||||
* is responsible for). If the 'use' element fails to render, the iframe
|
||||
* will be fully red, and we'll fail an "assertSnapshots" check.
|
||||
*/
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
|
||||
const iframe = document.getElementById("myIframe");
|
||||
const iframeWin = iframe.contentWindow;
|
||||
|
||||
// URI for our testcase with 'use' element, via HTTP and HTTPS:
|
||||
const insecureURI = "http://example.com/tests/dom/svg/test/use-with-hsts-helper.html";
|
||||
const secureURI = "https://example.com/tests/dom/svg/test/use-with-hsts-helper.html";
|
||||
|
||||
// Snapshots that we'll populate below:
|
||||
var blankSnapshot; // Snapshot of blank iframe.
|
||||
var refSnapshot; // Snapshot of lime reference rendering in iframe.
|
||||
var secureSnapshot; // Snapshot of testcase using HTTPS.
|
||||
var upgradedSnapshot; // Snapshot of testcase using HTTP-upgraded-to-HTTPS.
|
||||
|
||||
// Bookkeeping to be sure receiveMessage is called as many times as we expect:
|
||||
var numPostMessageCalls = 0;
|
||||
const expectedNumPostMessageCalls = 2; // (We load the helper file twice.)
|
||||
|
||||
// Helper function, called via postMessage, to check iframe's actual location:
|
||||
function receiveMessage(event) {
|
||||
is(event.data, secureURI, "iframe should end up viewing secure URI");
|
||||
numPostMessageCalls++;
|
||||
}
|
||||
|
||||
// TEST CODE BEGINS HERE.
|
||||
// Execution basically proceeds top-to-bottom through the functions
|
||||
// from this point on, via a chain of iframe onload-callbacks.
|
||||
function runTest() {
|
||||
// Capture a snapshot with nothing in the iframe, so we can do a
|
||||
// sanity-check not-equal comparison against our reference case, to be
|
||||
// sure we're rendering anything at all:
|
||||
blankSnapshot = snapshotWindow(iframeWin);
|
||||
|
||||
// Point iframe at a reference case:
|
||||
iframe.onload = captureRefSnapshot;
|
||||
iframe.src = "data:text/html,<body style='background:lime'>";
|
||||
}
|
||||
|
||||
function captureRefSnapshot() {
|
||||
// Capture the reference screenshot:
|
||||
refSnapshot = snapshotWindow(iframeWin);
|
||||
|
||||
// Ensure reference-case looks different from blank snapshot:
|
||||
assertSnapshots(refSnapshot, blankSnapshot,
|
||||
false /*not equal*/, null /*no fuzz*/,
|
||||
"refSnapshot", "blankSnapshot");
|
||||
|
||||
// OK, assuming we've got a valid refSnapshot, we can now proceed to
|
||||
// capture test screenshots.
|
||||
|
||||
// Register a postMessage handler, so that iframe can report its location:
|
||||
window.addEventListener("message", receiveMessage, false);
|
||||
|
||||
// Point iframe at secure (HTTPS) version of testcase, & wait for callback:
|
||||
iframe.onload = captureSecureSnapshot;
|
||||
iframe.src = secureURI;
|
||||
}
|
||||
|
||||
function captureSecureSnapshot() {
|
||||
// Capture snapshot of iframe showing always-HTTPS version of testcase:
|
||||
secureSnapshot = snapshotWindow(iframeWin);
|
||||
assertSnapshots(secureSnapshot, refSnapshot,
|
||||
true /*equal*/, null /*no fuzz*/,
|
||||
"secureSnapshot", "refSnapshot");
|
||||
|
||||
// Point iframe at insecure (HTTP) version of testcase (which should get
|
||||
// automatically upgraded to secure (HTTPS) under the hood), & wait for
|
||||
// callback:
|
||||
iframe.onload = captureUpgradedSnapshot;
|
||||
iframe.src = insecureURI;
|
||||
}
|
||||
|
||||
function captureUpgradedSnapshot() {
|
||||
// Double-check that iframe is really pointed at insecure URI, to be sure
|
||||
// we're actually exercising HSTS. (Note that receiveMessage() will make
|
||||
// sure it's been upgraded to a secure HTTPS URI under the hood.)
|
||||
is(iframe.src, insecureURI,
|
||||
"test should've attempted to load insecure HTTP URI, to exercise HSTS");
|
||||
|
||||
// Capture snapshot of iframe showing upgraded-to-HTTPS version of testcase:
|
||||
upgradedSnapshot = snapshotWindow(iframeWin);
|
||||
assertSnapshots(upgradedSnapshot, refSnapshot,
|
||||
true /*equal*/, null /*no fuzz*/,
|
||||
"upgradedSnapshot", "refSnapshot");
|
||||
cleanupAndFinish();
|
||||
}
|
||||
|
||||
function cleanupAndFinish() {
|
||||
is(numPostMessageCalls, expectedNumPostMessageCalls,
|
||||
"didn't receive as many messages from child iframe as expected");
|
||||
SpecialPowers.cleanUpSTSData("http://example.com");
|
||||
SimpleTest.finish();
|
||||
}
|
||||
|
||||
runTest();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,30 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<script>
|
||||
// Notify parent of our final URI:
|
||||
window.parent.postMessage(window.location.href, "*");
|
||||
</script>
|
||||
<style>
|
||||
html, body {
|
||||
margin: 0;
|
||||
height: 100%;
|
||||
}
|
||||
svg {
|
||||
display: block;
|
||||
height: 100%;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<svg xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
version="1.1">
|
||||
<defs>
|
||||
<rect id="limeRect" width="100%" height="100%" fill="lime"/>
|
||||
</defs>
|
||||
<rect width="100%" height="100%" fill="red"/>
|
||||
<use xlink:href="#limeRect"/>
|
||||
</svg>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,2 @@
|
|||
Cache-Control: no-cache
|
||||
Strict-Transport-Security: max-age=60
|
|
@ -169,7 +169,7 @@ function testFormDataSend() {
|
|||
}
|
||||
|
||||
is(response[1].headers['Content-Disposition'],
|
||||
'form-data; name="empty"; filename=""');
|
||||
'form-data; name="empty"; filename="blob"');
|
||||
|
||||
is(response[2].headers['Content-Disposition'],
|
||||
'form-data; name="explicit"; filename="explicit-file-name"');
|
||||
|
|
|
@ -344,6 +344,7 @@ function testFormDataBodyCreation() {
|
|||
ok(fd.has("more"), "more should exist.");
|
||||
|
||||
var b = fd.get("blob");
|
||||
ok(b.name, "blob", "blob entry should be a Blob.");
|
||||
ok(b instanceof Blob, "blob entry should be a Blob.");
|
||||
|
||||
return readAsText(b).then(function(output) {
|
||||
|
@ -417,6 +418,7 @@ function testFormDataBodyExtraction() {
|
|||
ok(fd.has("blob"), "Has entry 'blob'.");
|
||||
var entries = fd.getAll("blob");
|
||||
is(entries.length, 1, "getAll returns all items.");
|
||||
is(entries[0].name, "blob", "Filename should be blob.");
|
||||
ok(entries[0] instanceof Blob, "getAll returns blobs.");
|
||||
});
|
||||
|
||||
|
@ -428,6 +430,7 @@ function testFormDataBodyExtraction() {
|
|||
ok(fd.has("blob"), "Has entry 'blob'.");
|
||||
var entries = fd.getAll("blob");
|
||||
is(entries.length, 1, "getAll returns all items.");
|
||||
is(entries[0].name, "blob", "Filename should be blob.");
|
||||
ok(entries[0] instanceof Blob, "getAll returns blobs.");
|
||||
|
||||
ok(fd.has("key"), "Has entry 'key'.");
|
||||
|
|
|
@ -1377,6 +1377,8 @@ var interfaceNamesInGlobalScope =
|
|||
{name: "TVSource", b2g: true, permission: ["tv"]},
|
||||
// IMPORTANT: Do not change this list without review from a DOM peer!
|
||||
{name: "TVTuner", b2g: true, permission: ["tv"]},
|
||||
// IMPORTANT: Do not change this list without review from a DOM peer!
|
||||
{name: "U2F", release: false},
|
||||
// IMPORTANT: Do not change this list without review from a DOM peer!
|
||||
{name: "UDPMessageEvent", b2g: true, permission: ["udp-socket"]},
|
||||
// IMPORTANT: Do not change this list without review from a DOM peer!
|
||||
|
|
|
@ -0,0 +1,172 @@
|
|||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim:set ts=2 sw=2 sts=2 et cindent: */
|
||||
/* 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 "NSSToken.h"
|
||||
|
||||
#include "nsNSSComponent.h"
|
||||
#include "pk11pub.h"
|
||||
|
||||
namespace mozilla {
|
||||
namespace dom {
|
||||
|
||||
const nsString NSSToken::mVersion = NS_LITERAL_STRING("U2F_V2");
|
||||
|
||||
const uint32_t kParamLen = 32;
|
||||
const uint32_t kPublicKeyLen = 65;
|
||||
const uint32_t kSignedDataLen = (2 * kParamLen) + 1 + 4;
|
||||
|
||||
NSSToken::NSSToken()
|
||||
: mInitialized(false)
|
||||
, mMutex("NSSToken::mMutex")
|
||||
{}
|
||||
|
||||
NSSToken::~NSSToken()
|
||||
{
|
||||
nsNSSShutDownPreventionLock locker;
|
||||
|
||||
if (isAlreadyShutDown()) {
|
||||
return;
|
||||
}
|
||||
|
||||
destructorSafeDestroyNSSReference();
|
||||
shutdown(calledFromObject);
|
||||
}
|
||||
|
||||
void
|
||||
NSSToken::virtualDestroyNSSReference()
|
||||
{
|
||||
destructorSafeDestroyNSSReference();
|
||||
}
|
||||
|
||||
void
|
||||
NSSToken::destructorSafeDestroyNSSReference()
|
||||
{
|
||||
mSlot = nullptr;
|
||||
}
|
||||
|
||||
nsresult
|
||||
NSSToken::Init()
|
||||
{
|
||||
MOZ_ASSERT(!mInitialized);
|
||||
if (mInitialized) {
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
nsNSSShutDownPreventionLock locker;
|
||||
if (isAlreadyShutDown()) {
|
||||
return NS_ERROR_NOT_AVAILABLE;
|
||||
}
|
||||
|
||||
MutexAutoLock lock(mMutex);
|
||||
|
||||
if (!EnsureNSSInitializedChromeOrContent()) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
mSlot = PK11_GetInternalSlot();
|
||||
if (!mSlot.get()) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
mInitialized = true;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
bool
|
||||
NSSToken::IsCompatibleVersion(const nsString& aVersionParam) const
|
||||
{
|
||||
MOZ_ASSERT(mInitialized);
|
||||
return mVersion == aVersionParam;
|
||||
}
|
||||
|
||||
/*
|
||||
* IsRegistered determines if the provided key handle is usable by this token.
|
||||
*/
|
||||
bool
|
||||
NSSToken::IsRegistered(const CryptoBuffer& aKeyHandle) const
|
||||
{
|
||||
MOZ_ASSERT(mInitialized);
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
* A U2F Register operation causes a new key pair to be generated by the token.
|
||||
* The token then returns the public key of the key pair, and a handle to the
|
||||
* private key. The input parameters are used only for attestation, which this
|
||||
* token does not provide. (We'll see how that works!)
|
||||
*
|
||||
* The format of the return registration data is as follows:
|
||||
*
|
||||
* Bytes Value
|
||||
* 1 0x05
|
||||
* 65 public key
|
||||
* 1 key handle length
|
||||
* * key handle
|
||||
* * attestation certificate (omitted for now)
|
||||
* * attestation signature (omitted for now)
|
||||
*
|
||||
*/
|
||||
nsresult
|
||||
NSSToken::Register(const CryptoBuffer& /* aChallengeParam */,
|
||||
const CryptoBuffer& /* aApplicationParam */,
|
||||
CryptoBuffer& aRegistrationData)
|
||||
{
|
||||
MOZ_ASSERT(mInitialized);
|
||||
nsNSSShutDownPreventionLock locker;
|
||||
if (isAlreadyShutDown()) {
|
||||
return NS_ERROR_NOT_AVAILABLE;
|
||||
}
|
||||
|
||||
MutexAutoLock lock(mMutex);
|
||||
|
||||
if (!mInitialized) {
|
||||
return NS_ERROR_NOT_INITIALIZED;
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
* A U2F Sign operation creates a signature over the "param" arguments (plus
|
||||
* some other stuff) using the private key indicated in the key handle argument.
|
||||
*
|
||||
* The format of the signed data is as follows:
|
||||
*
|
||||
* 32 Application parameter
|
||||
* 1 User presence (0x01)
|
||||
* 4 Counter
|
||||
* 32 Challenge parameter
|
||||
*
|
||||
* The format of the signature data is as follows:
|
||||
*
|
||||
* 1 User presence
|
||||
* 4 Counter
|
||||
* * Signature
|
||||
*
|
||||
*/
|
||||
nsresult
|
||||
NSSToken::Sign(const CryptoBuffer& aApplicationParam,
|
||||
const CryptoBuffer& aChallengeParam,
|
||||
const CryptoBuffer& aKeyHandle,
|
||||
CryptoBuffer& aSignatureData)
|
||||
{
|
||||
MOZ_ASSERT(mInitialized);
|
||||
nsNSSShutDownPreventionLock locker;
|
||||
if (isAlreadyShutDown()) {
|
||||
return NS_ERROR_NOT_AVAILABLE;
|
||||
}
|
||||
|
||||
MutexAutoLock lock(mMutex);
|
||||
|
||||
if (!mInitialized) {
|
||||
return NS_ERROR_NOT_INITIALIZED;
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
} // namespace dom
|
||||
} // namespace mozilla
|
|
@ -0,0 +1,57 @@
|
|||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim:set ts=2 sw=2 sts=2 et cindent: */
|
||||
/* 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/. */
|
||||
|
||||
#ifndef mozilla_dom_NSSToken_h
|
||||
#define mozilla_dom_NSSToken_h
|
||||
|
||||
#include "mozilla/dom/CryptoBuffer.h"
|
||||
#include "mozilla/Mutex.h"
|
||||
#include "nsNSSShutDown.h"
|
||||
#include "ScopedNSSTypes.h"
|
||||
|
||||
namespace mozilla {
|
||||
namespace dom {
|
||||
|
||||
// NSSToken will support FIDO U2F operations using NSS for the crypto layer.
|
||||
// This is a stub. It will be implemented in bug 1244960.
|
||||
class NSSToken final : public nsNSSShutDownObject
|
||||
{
|
||||
public:
|
||||
NSSToken();
|
||||
|
||||
~NSSToken();
|
||||
|
||||
nsresult Init();
|
||||
|
||||
bool IsCompatibleVersion(const nsString& aVersionParam) const;
|
||||
|
||||
bool IsRegistered(const CryptoBuffer& aKeyHandle) const;
|
||||
|
||||
nsresult Register(const CryptoBuffer& aApplicationParam,
|
||||
const CryptoBuffer& aChallengeParam,
|
||||
CryptoBuffer& aRegistrationData);
|
||||
|
||||
nsresult Sign(const CryptoBuffer& aApplicationParam,
|
||||
const CryptoBuffer& aChallengeParam,
|
||||
const CryptoBuffer& aKeyHandle,
|
||||
CryptoBuffer& aSignatureData);
|
||||
|
||||
// For nsNSSShutDownObject
|
||||
virtual void virtualDestroyNSSReference() override;
|
||||
void destructorSafeDestroyNSSReference();
|
||||
|
||||
private:
|
||||
bool mInitialized;
|
||||
ScopedPK11SlotInfo mSlot;
|
||||
mozilla::Mutex mMutex;
|
||||
|
||||
static const nsString mVersion;
|
||||
};
|
||||
|
||||
} // namespace dom
|
||||
} // namespace mozilla
|
||||
|
||||
#endif // mozilla_dom_NSSToken_h
|
|
@ -0,0 +1,583 @@
|
|||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim:set ts=2 sw=2 sts=2 et cindent: */
|
||||
/* 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 "mozilla/dom/CryptoBuffer.h"
|
||||
#include "mozilla/dom/U2F.h"
|
||||
#include "mozilla/dom/U2FBinding.h"
|
||||
#include "mozilla/Preferences.h"
|
||||
#include "nsContentUtils.h"
|
||||
#include "nsIEffectiveTLDService.h"
|
||||
#include "nsURLParsers.h"
|
||||
#include "nsNetCID.h"
|
||||
#include "pk11pub.h"
|
||||
|
||||
namespace mozilla {
|
||||
namespace dom {
|
||||
|
||||
// These enumerations are defined in the FIDO U2F Javascript API under the
|
||||
// interface "ErrorCode" as constant integers, and thus in the U2F.webidl file.
|
||||
// Any changes to these must occur in both locations.
|
||||
enum class ErrorCode {
|
||||
OK = 0,
|
||||
OTHER_ERROR = 1,
|
||||
BAD_REQUEST = 2,
|
||||
CONFIGURATION_UNSUPPORTED = 3,
|
||||
DEVICE_INELIGIBLE = 4,
|
||||
TIMEOUT = 5
|
||||
};
|
||||
|
||||
#define PREF_U2F_SOFTTOKEN_ENABLED "security.webauth.u2f.softtoken"
|
||||
#define PREF_U2F_USBTOKEN_ENABLED "security.webauth.u2f.usbtoken"
|
||||
|
||||
const nsString
|
||||
U2F::FinishEnrollment = NS_LITERAL_STRING("navigator.id.finishEnrollment");
|
||||
|
||||
const nsString
|
||||
U2F::GetAssertion = NS_LITERAL_STRING("navigator.id.getAssertion");
|
||||
|
||||
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(U2F)
|
||||
NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
|
||||
NS_INTERFACE_MAP_ENTRY(nsISupports)
|
||||
NS_INTERFACE_MAP_END
|
||||
|
||||
NS_IMPL_CYCLE_COLLECTING_ADDREF(U2F)
|
||||
NS_IMPL_CYCLE_COLLECTING_RELEASE(U2F)
|
||||
|
||||
NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(U2F, mParent)
|
||||
|
||||
U2F::U2F()
|
||||
{}
|
||||
|
||||
U2F::~U2F()
|
||||
{
|
||||
nsNSSShutDownPreventionLock locker;
|
||||
|
||||
if (isAlreadyShutDown()) {
|
||||
return;
|
||||
}
|
||||
shutdown(calledFromObject);
|
||||
}
|
||||
|
||||
/* virtual */ JSObject*
|
||||
U2F::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
|
||||
{
|
||||
return U2FBinding::Wrap(aCx, this, aGivenProto);
|
||||
}
|
||||
|
||||
void
|
||||
U2F::Init(nsPIDOMWindowInner* aParent, ErrorResult& aRv)
|
||||
{
|
||||
MOZ_ASSERT(!mParent);
|
||||
mParent = do_QueryInterface(aParent);
|
||||
MOZ_ASSERT(mParent);
|
||||
|
||||
nsCOMPtr<nsIDocument> doc = mParent->GetDoc();
|
||||
MOZ_ASSERT(doc);
|
||||
|
||||
nsIPrincipal* principal = doc->NodePrincipal();
|
||||
aRv = nsContentUtils::GetUTFOrigin(principal, mOrigin);
|
||||
if (NS_WARN_IF(aRv.Failed())) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (NS_WARN_IF(mOrigin.IsEmpty())) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!EnsureNSSInitializedChromeOrContent()) {
|
||||
return;
|
||||
}
|
||||
|
||||
aRv = mSoftToken.Init();
|
||||
if (NS_WARN_IF(aRv.Failed())) {
|
||||
return;
|
||||
}
|
||||
|
||||
aRv = mUSBToken.Init();
|
||||
if (NS_WARN_IF(aRv.Failed())) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
nsresult
|
||||
U2F::AssembleClientData(const nsAString& aTyp,
|
||||
const nsAString& aChallenge,
|
||||
CryptoBuffer& aClientData) const
|
||||
{
|
||||
ClientData clientDataObject;
|
||||
clientDataObject.mTyp.Construct(aTyp); // "Typ" from the U2F specification
|
||||
clientDataObject.mChallenge.Construct(aChallenge);
|
||||
clientDataObject.mOrigin.Construct(mOrigin);
|
||||
|
||||
nsAutoString json;
|
||||
if (NS_WARN_IF(!clientDataObject.ToJSON(json))) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
if (NS_WARN_IF(!aClientData.Assign(NS_ConvertUTF16toUTF8(json)))) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
bool
|
||||
U2F::ValidAppID(/* in/out */ nsString& aAppId) const
|
||||
{
|
||||
nsCOMPtr<nsIURLParser> urlParser =
|
||||
do_GetService(NS_STDURLPARSER_CONTRACTID);
|
||||
nsCOMPtr<nsIEffectiveTLDService> tldService =
|
||||
do_GetService(NS_EFFECTIVETLDSERVICE_CONTRACTID);
|
||||
|
||||
MOZ_ASSERT(urlParser);
|
||||
MOZ_ASSERT(tldService);
|
||||
|
||||
uint32_t facetSchemePos;
|
||||
int32_t facetSchemeLen;
|
||||
uint32_t facetAuthPos;
|
||||
int32_t facetAuthLen;
|
||||
// Facet is the specification's way of referring to the web origin.
|
||||
nsAutoCString facetUrl = NS_ConvertUTF16toUTF8(mOrigin);
|
||||
nsresult rv = urlParser->ParseURL(facetUrl.get(), mOrigin.Length(),
|
||||
&facetSchemePos, &facetSchemeLen,
|
||||
&facetAuthPos, &facetAuthLen,
|
||||
nullptr, nullptr); // ignore path
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
nsAutoCString facetScheme(Substring(facetUrl, facetSchemePos, facetSchemeLen));
|
||||
nsAutoCString facetAuth(Substring(facetUrl, facetAuthPos, facetAuthLen));
|
||||
|
||||
uint32_t appIdSchemePos;
|
||||
int32_t appIdSchemeLen;
|
||||
uint32_t appIdAuthPos;
|
||||
int32_t appIdAuthLen;
|
||||
nsAutoCString appIdUrl = NS_ConvertUTF16toUTF8(aAppId);
|
||||
rv = urlParser->ParseURL(appIdUrl.get(), aAppId.Length(),
|
||||
&appIdSchemePos, &appIdSchemeLen,
|
||||
&appIdAuthPos, &appIdAuthLen,
|
||||
nullptr, nullptr); // ignore path
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
nsAutoCString appIdScheme(Substring(appIdUrl, appIdSchemePos, appIdSchemeLen));
|
||||
nsAutoCString appIdAuth(Substring(appIdUrl, appIdAuthPos, appIdAuthLen));
|
||||
|
||||
// If the facetId (origin) is not HTTPS, reject
|
||||
if (!facetScheme.LowerCaseEqualsLiteral("https")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// If the appId is empty or null, overwrite it with the facetId and accept
|
||||
if (aAppId.IsEmpty() || aAppId.EqualsLiteral("null")) {
|
||||
aAppId.Assign(mOrigin);
|
||||
return true;
|
||||
}
|
||||
|
||||
// if the appId URL is not HTTPS, reject.
|
||||
if (!appIdScheme.LowerCaseEqualsLiteral("https")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// If the facetId and the appId auths match, accept
|
||||
if (facetAuth == appIdAuth) {
|
||||
return true;
|
||||
}
|
||||
|
||||
nsAutoCString appIdTld;
|
||||
nsAutoCString facetTld;
|
||||
|
||||
rv = tldService->GetBaseDomainFromHost(appIdAuth, 0, appIdTld);
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
return false;
|
||||
}
|
||||
rv = tldService->GetBaseDomainFromHost(facetAuth, 0, facetTld);
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// If this AppID's registered domain matches the Facet's, accept
|
||||
if (!facetTld.IsEmpty() && !appIdTld.IsEmpty() &&
|
||||
(facetTld == appIdTld)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// TODO(Bug 1244959) Implement the remaining algorithm.
|
||||
return false;
|
||||
}
|
||||
|
||||
template <class CB, class Rsp>
|
||||
void
|
||||
SendError(CB& aCallback, ErrorCode aErrorCode)
|
||||
{
|
||||
Rsp response;
|
||||
response.mErrorCode.Construct(static_cast<uint32_t>(aErrorCode));
|
||||
|
||||
ErrorResult rv;
|
||||
aCallback.Call(response, rv);
|
||||
NS_WARN_IF(rv.Failed());
|
||||
// Useful exceptions already got reported.
|
||||
rv.SuppressException();
|
||||
}
|
||||
|
||||
void
|
||||
U2F::Register(const nsAString& aAppId,
|
||||
const Sequence<RegisterRequest>& aRegisterRequests,
|
||||
const Sequence<RegisteredKey>& aRegisteredKeys,
|
||||
U2FRegisterCallback& aCallback,
|
||||
const Optional<Nullable<int32_t>>& opt_aTimeoutSeconds,
|
||||
ErrorResult& aRv)
|
||||
{
|
||||
nsNSSShutDownPreventionLock locker;
|
||||
if (isAlreadyShutDown()) {
|
||||
SendError<U2FRegisterCallback, RegisterResponse>(aCallback,
|
||||
ErrorCode::OTHER_ERROR);
|
||||
return;
|
||||
}
|
||||
|
||||
const bool softTokenEnabled =
|
||||
Preferences::GetBool(PREF_U2F_SOFTTOKEN_ENABLED);
|
||||
|
||||
const bool usbTokenEnabled =
|
||||
Preferences::GetBool(PREF_U2F_USBTOKEN_ENABLED);
|
||||
|
||||
nsAutoString appId(aAppId);
|
||||
|
||||
// Verify the global appId first.
|
||||
if (!ValidAppID(appId)) {
|
||||
SendError<U2FRegisterCallback, RegisterResponse>(aCallback,
|
||||
ErrorCode::BAD_REQUEST);
|
||||
return;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < aRegisteredKeys.Length(); ++i) {
|
||||
RegisteredKey request(aRegisteredKeys[i]);
|
||||
|
||||
// Check for equired attributes
|
||||
if (!(request.mKeyHandle.WasPassed() &&
|
||||
request.mVersion.WasPassed())) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Verify the appId for this Registered Key, if set
|
||||
if (request.mAppId.WasPassed() &&
|
||||
!ValidAppID(request.mAppId.Value())) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Decode the key handle
|
||||
CryptoBuffer keyHandle;
|
||||
nsresult rv = keyHandle.FromJwkBase64(request.mKeyHandle.Value());
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
SendError<U2FRegisterCallback, RegisterResponse>(aCallback,
|
||||
ErrorCode::BAD_REQUEST);
|
||||
return;
|
||||
}
|
||||
|
||||
// We ignore mTransports, as it is intended to be used for sorting the
|
||||
// available devices by preference, but is not an exclusion factor.
|
||||
|
||||
// Determine if the provided keyHandle is registered at any device. If so,
|
||||
// then we'll return DEVICE_INELIGIBLE to signify we're already registered.
|
||||
if (usbTokenEnabled &&
|
||||
mUSBToken.IsCompatibleVersion(request.mVersion.Value()) &&
|
||||
mUSBToken.IsRegistered(keyHandle)) {
|
||||
SendError<U2FRegisterCallback, RegisterResponse>(aCallback,
|
||||
ErrorCode::DEVICE_INELIGIBLE);
|
||||
return;
|
||||
}
|
||||
|
||||
if (softTokenEnabled &&
|
||||
mSoftToken.IsCompatibleVersion(request.mVersion.Value()) &&
|
||||
mSoftToken.IsRegistered(keyHandle)) {
|
||||
SendError<U2FRegisterCallback, RegisterResponse>(aCallback,
|
||||
ErrorCode::DEVICE_INELIGIBLE);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Search the requests in order for the first some token can fulfill
|
||||
for (size_t i = 0; i < aRegisterRequests.Length(); ++i) {
|
||||
RegisterRequest request(aRegisterRequests[i]);
|
||||
|
||||
// Check for equired attributes
|
||||
if (!(request.mVersion.WasPassed() &&
|
||||
request.mChallenge.WasPassed())) {
|
||||
continue;
|
||||
}
|
||||
|
||||
CryptoBuffer clientData;
|
||||
nsresult rv = AssembleClientData(FinishEnrollment,
|
||||
request.mChallenge.Value(),
|
||||
clientData);
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
SendError<U2FRegisterCallback, RegisterResponse>(aCallback,
|
||||
ErrorCode::OTHER_ERROR);
|
||||
return;
|
||||
}
|
||||
|
||||
// Hash the AppID and the ClientData into the AppParam and ChallengeParam
|
||||
SECStatus srv;
|
||||
nsCString cAppId = NS_ConvertUTF16toUTF8(appId);
|
||||
CryptoBuffer appParam;
|
||||
CryptoBuffer challengeParam;
|
||||
if (!appParam.SetLength(SHA256_LENGTH, fallible) ||
|
||||
!challengeParam.SetLength(SHA256_LENGTH, fallible)) {
|
||||
SendError<U2FRegisterCallback, RegisterResponse>(aCallback,
|
||||
ErrorCode::OTHER_ERROR);
|
||||
return;
|
||||
}
|
||||
|
||||
srv = PK11_HashBuf(SEC_OID_SHA256, appParam.Elements(),
|
||||
reinterpret_cast<const uint8_t*>(cAppId.BeginReading()),
|
||||
cAppId.Length());
|
||||
if (srv != SECSuccess) {
|
||||
SendError<U2FRegisterCallback, RegisterResponse>(aCallback,
|
||||
ErrorCode::OTHER_ERROR);
|
||||
return;
|
||||
}
|
||||
|
||||
srv = PK11_HashBuf(SEC_OID_SHA256, challengeParam.Elements(),
|
||||
clientData.Elements(), clientData.Length());
|
||||
if (srv != SECSuccess) {
|
||||
SendError<U2FRegisterCallback, RegisterResponse>(aCallback,
|
||||
ErrorCode::OTHER_ERROR);
|
||||
return;
|
||||
}
|
||||
|
||||
// Get the registration data from the token
|
||||
CryptoBuffer registrationData;
|
||||
bool registerSuccess = false;
|
||||
|
||||
if (usbTokenEnabled &&
|
||||
mUSBToken.IsCompatibleVersion(request.mVersion.Value())) {
|
||||
rv = mUSBToken.Register(opt_aTimeoutSeconds, challengeParam,
|
||||
appParam, registrationData);
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
SendError<U2FRegisterCallback, RegisterResponse>(aCallback,
|
||||
ErrorCode::OTHER_ERROR);
|
||||
return;
|
||||
}
|
||||
registerSuccess = true;
|
||||
}
|
||||
|
||||
if (!registerSuccess && softTokenEnabled &&
|
||||
mSoftToken.IsCompatibleVersion(request.mVersion.Value())) {
|
||||
rv = mSoftToken.Register(challengeParam, appParam, registrationData);
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
SendError<U2FRegisterCallback, RegisterResponse>(aCallback,
|
||||
ErrorCode::OTHER_ERROR);
|
||||
return;
|
||||
}
|
||||
registerSuccess = true;
|
||||
}
|
||||
|
||||
if (!registerSuccess) {
|
||||
// Try another request
|
||||
continue;
|
||||
}
|
||||
|
||||
// Assemble a response object to return
|
||||
nsString clientDataBase64, registrationDataBase64;
|
||||
nsresult rvClientData =
|
||||
clientData.ToJwkBase64(clientDataBase64);
|
||||
nsresult rvRegistrationData =
|
||||
registrationData.ToJwkBase64(registrationDataBase64);
|
||||
if (NS_WARN_IF(NS_FAILED(rvClientData)) ||
|
||||
NS_WARN_IF(NS_FAILED(rvRegistrationData))) {
|
||||
SendError<U2FRegisterCallback, RegisterResponse>(aCallback,
|
||||
ErrorCode::OTHER_ERROR);
|
||||
return;
|
||||
}
|
||||
|
||||
RegisterResponse response;
|
||||
response.mClientData.Construct(clientDataBase64);
|
||||
response.mRegistrationData.Construct(registrationDataBase64);
|
||||
response.mErrorCode.Construct(static_cast<uint32_t>(ErrorCode::OK));
|
||||
|
||||
ErrorResult result;
|
||||
aCallback.Call(response, result);
|
||||
NS_WARN_IF(result.Failed());
|
||||
// Useful exceptions already got reported.
|
||||
result.SuppressException();
|
||||
return;
|
||||
}
|
||||
|
||||
// Nothing could satisfy
|
||||
SendError<U2FRegisterCallback, RegisterResponse>(aCallback,
|
||||
ErrorCode::BAD_REQUEST);
|
||||
return;
|
||||
}
|
||||
|
||||
void
|
||||
U2F::Sign(const nsAString& aAppId,
|
||||
const nsAString& aChallenge,
|
||||
const Sequence<RegisteredKey>& aRegisteredKeys,
|
||||
U2FSignCallback& aCallback,
|
||||
const Optional<Nullable<int32_t>>& opt_aTimeoutSeconds,
|
||||
ErrorResult& aRv)
|
||||
{
|
||||
nsNSSShutDownPreventionLock locker;
|
||||
if (isAlreadyShutDown()) {
|
||||
SendError<U2FSignCallback, SignResponse>(aCallback,
|
||||
ErrorCode::OTHER_ERROR);
|
||||
return;
|
||||
}
|
||||
|
||||
const bool softTokenEnabled =
|
||||
Preferences::GetBool(PREF_U2F_SOFTTOKEN_ENABLED);
|
||||
|
||||
const bool usbTokenEnabled =
|
||||
Preferences::GetBool(PREF_U2F_USBTOKEN_ENABLED);
|
||||
|
||||
nsAutoString appId(aAppId);
|
||||
|
||||
// Verify the global appId first.
|
||||
if (!ValidAppID(appId)) {
|
||||
SendError<U2FSignCallback, SignResponse>(aCallback,
|
||||
ErrorCode::BAD_REQUEST);
|
||||
return;
|
||||
}
|
||||
|
||||
// Search the requests for one a token can fulfill
|
||||
for (size_t i = 0; i < aRegisteredKeys.Length(); i += 1) {
|
||||
RegisteredKey request(aRegisteredKeys[i]);
|
||||
|
||||
// Check for required attributes
|
||||
if (!(request.mVersion.WasPassed() &&
|
||||
request.mKeyHandle.WasPassed())) {
|
||||
SendError<U2FSignCallback, SignResponse>(aCallback,
|
||||
ErrorCode::OTHER_ERROR);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Allow an individual RegisteredKey to assert a different AppID
|
||||
nsAutoString regKeyAppId(appId);
|
||||
if (request.mAppId.WasPassed()) {
|
||||
regKeyAppId.Assign(request.mAppId.Value());
|
||||
if (!ValidAppID(regKeyAppId)) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Assemble a clientData object
|
||||
CryptoBuffer clientData;
|
||||
nsresult rv = AssembleClientData(GetAssertion, aChallenge, clientData);
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
SendError<U2FSignCallback, SignResponse>(aCallback,
|
||||
ErrorCode::OTHER_ERROR);
|
||||
return;
|
||||
}
|
||||
|
||||
// Hash the AppID and the ClientData into the AppParam and ChallengeParam
|
||||
SECStatus srv;
|
||||
nsCString cAppId = NS_ConvertUTF16toUTF8(regKeyAppId);
|
||||
CryptoBuffer appParam;
|
||||
CryptoBuffer challengeParam;
|
||||
if (!appParam.SetLength(SHA256_LENGTH, fallible) ||
|
||||
!challengeParam.SetLength(SHA256_LENGTH, fallible)) {
|
||||
SendError<U2FSignCallback, SignResponse>(aCallback,
|
||||
ErrorCode::OTHER_ERROR);
|
||||
return;
|
||||
}
|
||||
|
||||
srv = PK11_HashBuf(SEC_OID_SHA256, appParam.Elements(),
|
||||
reinterpret_cast<const uint8_t*>(cAppId.BeginReading()),
|
||||
cAppId.Length());
|
||||
if (srv != SECSuccess) {
|
||||
SendError<U2FSignCallback, SignResponse>(aCallback,
|
||||
ErrorCode::OTHER_ERROR);
|
||||
return;
|
||||
}
|
||||
|
||||
srv = PK11_HashBuf(SEC_OID_SHA256, challengeParam.Elements(),
|
||||
clientData.Elements(), clientData.Length());
|
||||
if (srv != SECSuccess) {
|
||||
SendError<U2FSignCallback, SignResponse>(aCallback,
|
||||
ErrorCode::OTHER_ERROR);
|
||||
return;
|
||||
}
|
||||
|
||||
// Decode the key handle
|
||||
CryptoBuffer keyHandle;
|
||||
rv = keyHandle.FromJwkBase64(request.mKeyHandle.Value());
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
SendError<U2FSignCallback, SignResponse>(aCallback,
|
||||
ErrorCode::OTHER_ERROR);
|
||||
return;
|
||||
}
|
||||
|
||||
// Get the signature from the token
|
||||
CryptoBuffer signatureData;
|
||||
bool signSuccess = false;
|
||||
|
||||
// We ignore mTransports, as it is intended to be used for sorting the
|
||||
// available devices by preference, but is not an exclusion factor.
|
||||
|
||||
if (usbTokenEnabled &&
|
||||
mUSBToken.IsCompatibleVersion(request.mVersion.Value())) {
|
||||
rv = mUSBToken.Sign(opt_aTimeoutSeconds, appParam, challengeParam,
|
||||
keyHandle, signatureData);
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
SendError<U2FSignCallback, SignResponse>(aCallback,
|
||||
ErrorCode::OTHER_ERROR);
|
||||
return;
|
||||
}
|
||||
signSuccess = true;
|
||||
}
|
||||
|
||||
if (!signSuccess && softTokenEnabled &&
|
||||
mSoftToken.IsCompatibleVersion(request.mVersion.Value())) {
|
||||
rv = mSoftToken.Sign(appParam, challengeParam, keyHandle, signatureData);
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
SendError<U2FSignCallback, SignResponse>(aCallback,
|
||||
ErrorCode::OTHER_ERROR);
|
||||
return;
|
||||
}
|
||||
signSuccess = true;
|
||||
}
|
||||
|
||||
if (!signSuccess) {
|
||||
// Try another request
|
||||
continue;
|
||||
}
|
||||
|
||||
// Assemble a response object to return
|
||||
nsString clientDataBase64, signatureDataBase64;
|
||||
nsresult rvClientData =
|
||||
clientData.ToJwkBase64(clientDataBase64);
|
||||
nsresult rvSignatureData =
|
||||
signatureData.ToJwkBase64(signatureDataBase64);
|
||||
if (NS_WARN_IF(NS_FAILED(rvClientData)) ||
|
||||
NS_WARN_IF(NS_FAILED(rvSignatureData))) {
|
||||
SendError<U2FSignCallback, SignResponse>(aCallback,
|
||||
ErrorCode::OTHER_ERROR);
|
||||
return;
|
||||
}
|
||||
SignResponse response;
|
||||
response.mKeyHandle.Construct(request.mKeyHandle.Value());
|
||||
response.mClientData.Construct(clientDataBase64);
|
||||
response.mSignatureData.Construct(signatureDataBase64);
|
||||
response.mErrorCode.Construct(static_cast<uint32_t>(ErrorCode::OK));
|
||||
|
||||
ErrorResult result;
|
||||
aCallback.Call(response, result);
|
||||
NS_WARN_IF(result.Failed());
|
||||
// Useful exceptions already got reported.
|
||||
result.SuppressException();
|
||||
return;
|
||||
}
|
||||
|
||||
// Nothing could satisfy
|
||||
SendError<U2FSignCallback, SignResponse>(aCallback,
|
||||
ErrorCode::DEVICE_INELIGIBLE);
|
||||
return;
|
||||
}
|
||||
|
||||
} // namespace dom
|
||||
} // namespace mozilla
|
|
@ -0,0 +1,106 @@
|
|||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim:set ts=2 sw=2 sts=2 et cindent: */
|
||||
/* 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/. */
|
||||
|
||||
#ifndef mozilla_dom_U2F_h
|
||||
#define mozilla_dom_U2F_h
|
||||
|
||||
#include "js/TypeDecls.h"
|
||||
#include "mozilla/Attributes.h"
|
||||
#include "mozilla/dom/BindingDeclarations.h"
|
||||
#include "mozilla/dom/Nullable.h"
|
||||
#include "mozilla/ErrorResult.h"
|
||||
#include "nsCycleCollectionParticipant.h"
|
||||
#include "nsPIDOMWindow.h"
|
||||
#include "nsWrapperCache.h"
|
||||
|
||||
#include "NSSToken.h"
|
||||
#include "USBToken.h"
|
||||
|
||||
namespace mozilla {
|
||||
namespace dom {
|
||||
|
||||
struct RegisterRequest;
|
||||
struct RegisteredKey;
|
||||
class U2FRegisterCallback;
|
||||
class U2FSignCallback;
|
||||
|
||||
} // namespace dom
|
||||
} // namespace mozilla
|
||||
|
||||
namespace mozilla {
|
||||
namespace dom {
|
||||
|
||||
class U2F final : public nsISupports,
|
||||
public nsWrapperCache,
|
||||
public nsNSSShutDownObject
|
||||
{
|
||||
public:
|
||||
NS_DECL_CYCLE_COLLECTING_ISUPPORTS
|
||||
NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(U2F)
|
||||
|
||||
U2F();
|
||||
|
||||
nsPIDOMWindowInner*
|
||||
GetParentObject() const
|
||||
{
|
||||
return mParent;
|
||||
}
|
||||
|
||||
void
|
||||
Init(nsPIDOMWindowInner* aParent, ErrorResult& aRv);
|
||||
|
||||
virtual JSObject*
|
||||
WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
|
||||
|
||||
void
|
||||
Register(const nsAString& aAppId,
|
||||
const Sequence<RegisterRequest>& aRegisterRequests,
|
||||
const Sequence<RegisteredKey>& aRegisteredKeys,
|
||||
U2FRegisterCallback& aCallback,
|
||||
const Optional<Nullable<int32_t>>& opt_aTimeoutSeconds,
|
||||
ErrorResult& aRv);
|
||||
|
||||
void
|
||||
Sign(const nsAString& aAppId,
|
||||
const nsAString& aChallenge,
|
||||
const Sequence<RegisteredKey>& aRegisteredKeys,
|
||||
U2FSignCallback& aCallback,
|
||||
const Optional<Nullable<int32_t>>& opt_aTimeoutSeconds,
|
||||
ErrorResult& aRv);
|
||||
|
||||
// No NSS resources to release.
|
||||
virtual
|
||||
void virtualDestroyNSSReference() override {};
|
||||
|
||||
private:
|
||||
nsCOMPtr<nsPIDOMWindowInner> mParent;
|
||||
nsString mOrigin;
|
||||
NSSToken mSoftToken;
|
||||
USBToken mUSBToken;
|
||||
|
||||
static const nsString FinishEnrollment;
|
||||
static const nsString GetAssertion;
|
||||
|
||||
~U2F();
|
||||
|
||||
nsresult
|
||||
AssembleClientData(const nsAString& aTyp,
|
||||
const nsAString& aChallenge,
|
||||
CryptoBuffer& aClientData) const;
|
||||
|
||||
// ValidAppID determines whether the supplied FIDO AppID is valid for
|
||||
// the current FacetID, e.g., the current origin. If the supplied
|
||||
// aAppId param is null or empty, it will be filled in per the algorithm.
|
||||
// See https://fidoalliance.org/specs/fido-u2f-v1.0-nfc-bt-amendment-20150514/fido-appid-and-facets.html
|
||||
// for a description of the algorithm.
|
||||
bool
|
||||
ValidAppID(/* in/out */ nsString& aAppId) const;
|
||||
};
|
||||
|
||||
} // namespace dom
|
||||
} // namespace mozilla
|
||||
|
||||
#endif // mozilla_dom_U2F_h
|
|
@ -0,0 +1,72 @@
|
|||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim:set ts=2 sw=2 sts=2 et cindent: */
|
||||
/* 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 "USBToken.h"
|
||||
|
||||
namespace mozilla {
|
||||
namespace dom {
|
||||
|
||||
USBToken::USBToken()
|
||||
: mInitialized(false)
|
||||
{}
|
||||
|
||||
USBToken::~USBToken()
|
||||
{}
|
||||
|
||||
nsresult
|
||||
USBToken::Init()
|
||||
{
|
||||
// This routine does nothing at present, but Bug 1245527 will
|
||||
// integrate the upcoming USB HID service here, which will likely
|
||||
// require an initialization upon load.
|
||||
MOZ_ASSERT(!mInitialized);
|
||||
if (mInitialized) {
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
mInitialized = true;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
const nsString USBToken::mVersion = NS_LITERAL_STRING("U2F_V2");
|
||||
|
||||
bool
|
||||
USBToken::IsCompatibleVersion(const nsString& aVersionParam) const
|
||||
{
|
||||
MOZ_ASSERT(mInitialized);
|
||||
return mVersion == aVersionParam;
|
||||
}
|
||||
|
||||
bool
|
||||
USBToken::IsRegistered(const CryptoBuffer& aKeyHandle) const
|
||||
{
|
||||
MOZ_ASSERT(mInitialized);
|
||||
return false;
|
||||
}
|
||||
|
||||
nsresult
|
||||
USBToken::Register(const Optional<Nullable<int32_t>>& opt_timeoutSeconds,
|
||||
const CryptoBuffer& /* aChallengeParam */,
|
||||
const CryptoBuffer& /* aApplicationParam */,
|
||||
CryptoBuffer& aRegistrationData) const
|
||||
{
|
||||
MOZ_ASSERT(mInitialized);
|
||||
return NS_ERROR_NOT_AVAILABLE;
|
||||
}
|
||||
|
||||
nsresult
|
||||
USBToken::Sign(const Optional<Nullable<int32_t>>& opt_timeoutSeconds,
|
||||
const CryptoBuffer& aApplicationParam,
|
||||
const CryptoBuffer& aChallengeParam,
|
||||
const CryptoBuffer& aKeyHandle,
|
||||
CryptoBuffer& aSignatureData) const
|
||||
{
|
||||
MOZ_ASSERT(mInitialized);
|
||||
return NS_ERROR_NOT_AVAILABLE;
|
||||
}
|
||||
|
||||
} // namespace dom
|
||||
} // namespace mozilla
|
|
@ -0,0 +1,49 @@
|
|||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim:set ts=2 sw=2 sts=2 et cindent: */
|
||||
/* 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/. */
|
||||
|
||||
#ifndef mozilla_dom_USBToken_h
|
||||
#define mozilla_dom_USBToken_h
|
||||
|
||||
#include "mozilla/dom/CryptoBuffer.h"
|
||||
|
||||
namespace mozilla {
|
||||
namespace dom {
|
||||
|
||||
// USBToken implements FIDO operations using a USB device.
|
||||
class USBToken final
|
||||
{
|
||||
public:
|
||||
USBToken();
|
||||
|
||||
~USBToken();
|
||||
|
||||
nsresult Init();
|
||||
|
||||
bool IsCompatibleVersion(const nsString& aVersionParam) const;
|
||||
|
||||
bool IsRegistered(const CryptoBuffer& aKeyHandle) const;
|
||||
|
||||
nsresult Register(const Optional<Nullable<int32_t>>& opt_timeoutSeconds,
|
||||
const CryptoBuffer& aApplicationParam,
|
||||
const CryptoBuffer& aChallengeParam,
|
||||
CryptoBuffer& aRegistrationData) const;
|
||||
|
||||
nsresult Sign(const Optional<Nullable<int32_t>>& opt_timeoutSeconds,
|
||||
const CryptoBuffer& aApplicationParam,
|
||||
const CryptoBuffer& aChallengeParam,
|
||||
const CryptoBuffer& aKeyHandle,
|
||||
CryptoBuffer& aSignatureData) const;
|
||||
|
||||
private:
|
||||
bool mInitialized;
|
||||
|
||||
static const nsString mVersion;
|
||||
};
|
||||
|
||||
} // namespace dom
|
||||
} // namespace mozilla
|
||||
|
||||
#endif // mozilla_dom_USBToken_h
|
|
@ -0,0 +1,28 @@
|
|||
# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
|
||||
# vim: set filetype=python:
|
||||
# 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/.
|
||||
|
||||
EXPORTS.mozilla.dom += [
|
||||
'NSSToken.h',
|
||||
'U2F.h',
|
||||
'USBToken.h',
|
||||
]
|
||||
|
||||
UNIFIED_SOURCES += [
|
||||
'NSSToken.cpp',
|
||||
'U2F.cpp',
|
||||
'USBToken.cpp',
|
||||
]
|
||||
|
||||
FINAL_LIBRARY = 'xul'
|
||||
|
||||
LOCAL_INCLUDES += [
|
||||
'/dom/base',
|
||||
'/dom/crypto',
|
||||
'/security/manager/ssl',
|
||||
'/security/pkix/include',
|
||||
]
|
||||
|
||||
MOCHITEST_MANIFESTS += ['tests/mochitest.ini']
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"trustedFacets" : [{
|
||||
"version": { "major": 1, "minor" : 0 },
|
||||
"ids": [
|
||||
"https://fido.example.com"
|
||||
]
|
||||
}]
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
Content-Type: application/fido.trusted-apps+json
|
|
@ -0,0 +1,6 @@
|
|||
# This file isn't actually JSON, so it shouldn't successfully parse.
|
||||
{
|
||||
"trustedFacets" : [{
|
||||
"version": { "major": 1, "minor" : 0 },
|
||||
},{}]
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
Content-Type: application/fido.trusted-apps+json
|
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"trustedFacets" : [{
|
||||
"version": { "major": 1, "minor" : 0 },
|
||||
"ids": [
|
||||
"https://example.net",
|
||||
"http://www.example.com"
|
||||
]
|
||||
}]
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
Content-Type: application/fido.trusted-apps+json
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"trustedFacets" : [{
|
||||
"version": { "major": 1, "minor" : 0 },
|
||||
"ids": [
|
||||
"https://fido.example.com"
|
||||
]
|
||||
}]
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
[DEFAULT]
|
||||
support-files =
|
||||
u2futil.js
|
||||
test_frame_appid_facet.html
|
||||
test_frame_register.html
|
||||
test_frame_appid_facet_remoteload.html
|
||||
test_frame_appid_facet_insecure.html
|
||||
test_frame_appid_facet_subdomain.html
|
||||
facet/facetList.txt
|
||||
facet/facetList-good
|
||||
facet/facetList-good^headers^
|
||||
facet/facetList-no_overlap
|
||||
facet/facetList-no_overlap^headers^
|
||||
facet/facetList-invalid_format
|
||||
facet/facetList-invalid_format^headers^
|
||||
|
||||
[test_util_methods.html]
|
||||
[test_no_token.html]
|
||||
[test_frame.html]
|
|
@ -0,0 +1,67 @@
|
|||
<!DOCTYPE html>
|
||||
<meta charset=utf-8>
|
||||
<head>
|
||||
<title>Test for AppID / FacetID behavior for FIDO Universal Second Factor</title>
|
||||
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<script src="u2futil.js"></script>
|
||||
|
||||
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
|
||||
</head>
|
||||
<body>
|
||||
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1231681">Mozilla Bug 1231681</a>
|
||||
<p id="display"></p>
|
||||
|
||||
<div id="content" style="display: none"></div>
|
||||
|
||||
<div id="framediv">
|
||||
<iframe id="testing_frame"></iframe>
|
||||
</div>
|
||||
|
||||
<pre id="log"></pre>
|
||||
|
||||
|
||||
<script class="testbody" type="text/javascript">
|
||||
SpecialPowers.setBoolPref("security.webauth.u2f", true);
|
||||
SpecialPowers.setBoolPref("security.webauth.u2f.softtoken", true);
|
||||
|
||||
var testList = [
|
||||
"https://example.com/tests/dom/u2f/tests/test_frame_register.html",
|
||||
"http://mochi.test:8888/tests/dom/u2f/tests/test_frame_appid_facet_insecure.html",
|
||||
"https://example.com/tests/dom/u2f/tests/test_frame_appid_facet.html",
|
||||
"https://example.com/tests/dom/u2f/tests/test_frame_appid_facet_remoteload.html",
|
||||
"https://test1.example.com/tests/dom/u2f/tests/test_frame_appid_facet_subdomain.html"
|
||||
];
|
||||
|
||||
function log(msg) {
|
||||
document.getElementById("log").textContent += "\n" + msg;
|
||||
}
|
||||
|
||||
function nextTest() {
|
||||
if (testList.length < 1) {
|
||||
SimpleTest.finish();
|
||||
return;
|
||||
}
|
||||
|
||||
document.getElementById('testing_frame').src = testList.shift();
|
||||
}
|
||||
|
||||
// listen for a messages from the mixed content test harness
|
||||
function receiveMessage(event) {
|
||||
if ("test" in event.data) {
|
||||
var summary = event.data.test + ": " + event.data.msg;
|
||||
log(event.data.status + ": " + summary);
|
||||
ok(event.data.status, summary);
|
||||
} else if ("done" in event.data) {
|
||||
nextTest();
|
||||
}
|
||||
}
|
||||
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
|
||||
window.addEventListener("message", receiveMessage, false);
|
||||
nextTest();
|
||||
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,69 @@
|
|||
<!DOCTYPE html>
|
||||
<meta charset=utf-8>
|
||||
<head>
|
||||
<script src="u2futil.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<p>Test for AppID / FacetID behavior for FIDO Universal Second Factor</p>
|
||||
<script class="testbody" type="text/javascript">
|
||||
"use strict";
|
||||
|
||||
SpecialPowers.setBoolPref("security.webauth.u2f", true);
|
||||
SpecialPowers.setBoolPref("security.webauth.u2f.softtoken", true);
|
||||
|
||||
local_is(window.location.origin, "https://example.com", "Is loaded correctly");
|
||||
|
||||
var version = "U2F_V2";
|
||||
var challenge = new Uint8Array(16);
|
||||
|
||||
u2f.register(null, [{
|
||||
version: version,
|
||||
challenge: bytesToBase64UrlSafe(challenge),
|
||||
}], [], function(res){
|
||||
local_is(res.errorCode, 0, "Null AppID should work.");
|
||||
});
|
||||
|
||||
u2f.register("", [{
|
||||
version: version,
|
||||
challenge: bytesToBase64UrlSafe(challenge),
|
||||
}], [], function(res){
|
||||
local_is(res.errorCode, 0, "Empty AppID should work.");
|
||||
});
|
||||
|
||||
// Test: Correct TLD, but incorrect scheme
|
||||
u2f.register("http://example.com/appId", [{
|
||||
version: version,
|
||||
challenge: bytesToBase64UrlSafe(challenge),
|
||||
}], [], function(res){
|
||||
local_isnot(res.errorCode, 0, "HTTP scheme is disallowed");
|
||||
});
|
||||
|
||||
// Test: Correct TLD, and also HTTPS
|
||||
u2f.register("https://example.com/appId", [{
|
||||
version: version,
|
||||
challenge: bytesToBase64UrlSafe(challenge),
|
||||
}], [], function(res){
|
||||
local_is(res.errorCode, 0, "HTTPS origin for example.com should work");
|
||||
});
|
||||
|
||||
// Test: Dynamic origin
|
||||
u2f.register(window.location.origin + "/otherAppId", [{
|
||||
version: version,
|
||||
challenge: bytesToBase64UrlSafe(challenge),
|
||||
}], [], function(res){
|
||||
local_is(res.errorCode, 0, "Direct window origin should work");
|
||||
});
|
||||
|
||||
// eTLD+1 check
|
||||
u2f.register("https://test1.example.com/appId", [{
|
||||
version: version,
|
||||
challenge: bytesToBase64UrlSafe(challenge),
|
||||
}], [], function(res){
|
||||
local_is(res.errorCode, 0, "Subdomain AppID should work");
|
||||
});
|
||||
|
||||
local_finished();
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,58 @@
|
|||
<!DOCTYPE html>
|
||||
<meta charset=utf-8>
|
||||
<head>
|
||||
<script src="u2futil.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<p>Test for AppID / FacetID behavior for FIDO Universal Second Factor</p>
|
||||
<script class="testbody" type="text/javascript">
|
||||
"use strict";
|
||||
|
||||
SpecialPowers.setBoolPref("security.webauth.u2f", true);
|
||||
SpecialPowers.setBoolPref("security.webauth.u2f.softtoken", true);
|
||||
|
||||
local_is(window.location.origin, "http://mochi.test:8888", "Is loaded correctly");
|
||||
|
||||
var version = "U2F_V2";
|
||||
var challenge = new Uint8Array(16);
|
||||
|
||||
u2f.register(null, [{
|
||||
version: version,
|
||||
challenge: bytesToBase64UrlSafe(challenge),
|
||||
}], [], function(res){
|
||||
local_isnot(res.errorCode, 0, "Insecure origin disallowed for null AppID");
|
||||
});
|
||||
|
||||
u2f.register("", [{
|
||||
version: version,
|
||||
challenge: bytesToBase64UrlSafe(challenge),
|
||||
}], [], function(res){
|
||||
local_isnot(res.errorCode, 0, "Insecure origin disallowed for empty AppID");
|
||||
});
|
||||
|
||||
u2f.register("http://example.com/appId", [{
|
||||
version: version,
|
||||
challenge: bytesToBase64UrlSafe(challenge),
|
||||
}], [], function(res){
|
||||
local_isnot(res.errorCode, 0, "Insecure origin disallowed for HTTP AppID");
|
||||
});
|
||||
|
||||
u2f.register("https://example.com/appId", [{
|
||||
version: version,
|
||||
challenge: bytesToBase64UrlSafe(challenge),
|
||||
}], [], function(res){
|
||||
local_isnot(res.errorCode, 0, "Insecure origin disallowed for HTTPS AppID from HTTP origin");
|
||||
});
|
||||
|
||||
u2f.register(window.location.origin + "/otherAppId", [{
|
||||
version: version,
|
||||
challenge: bytesToBase64UrlSafe(challenge),
|
||||
}], [], function(res){
|
||||
local_isnot(res.errorCode, 0, "Insecure origin disallowed for HTTP origin");
|
||||
});
|
||||
|
||||
local_finished();
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,60 @@
|
|||
<!DOCTYPE html>
|
||||
<meta charset=utf-8>
|
||||
<head>
|
||||
<script src="u2futil.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<p>Test for Remote AppId Load behavior for FIDO Universal Second Factor</p>
|
||||
<script class="testbody" type="text/javascript">
|
||||
"use strict";
|
||||
|
||||
SpecialPowers.setBoolPref("security.webauth.u2f", true);
|
||||
SpecialPowers.setBoolPref("security.webauth.u2f.softtoken", true);
|
||||
|
||||
var version = "U2F_V2";
|
||||
var challenge = new Uint8Array(16);
|
||||
|
||||
local_is(window.location.origin, "https://example.com", "Is loaded correctly");
|
||||
|
||||
// TODO: Must support remote loads of AppID manifests first.
|
||||
//
|
||||
// u2f.register("https://test1.example.com/dom/u2f/tests/facet/facetList.txt", [{
|
||||
// version: version,
|
||||
// challenge: bytesToBase64UrlSafe(challenge),
|
||||
// }], [], function(res){
|
||||
// local_is(res.errorCode, 2, "Should not permit this AppId contentType");
|
||||
// });
|
||||
|
||||
// u2f.register("https://test1.example.com/dom/u2f/tests/facet/facetListMissing", [{
|
||||
// version: version,
|
||||
// challenge: bytesToBase64UrlSafe(challenge),
|
||||
// }], [], function(res){
|
||||
// local_is(res.errorCode, 2, "Should not permit with a missing AppID list");
|
||||
// });
|
||||
|
||||
// u2f.register("https://test1.example.com/dom/u2f/tests/facet/facetList-good", [{
|
||||
// version: version,
|
||||
// challenge: bytesToBase64UrlSafe(challenge),
|
||||
// }], [], function(res){
|
||||
// local_is(res.errorCode, 0, "The AppId should permit example.com");
|
||||
// });
|
||||
|
||||
// u2f.register("https://test1.example.com/dom/u2f/tests/facet/facetList-no_overlap", [{
|
||||
// version: version,
|
||||
// challenge: bytesToBase64UrlSafe(challenge),
|
||||
// }], [], function(res){
|
||||
// local_is(res.errorCode, 2, "Should not permit with a missing AppID list");
|
||||
// });
|
||||
|
||||
// u2f.register("https://test1.example.com/dom/u2f/tests/facet/facetList-invalid_format", [{
|
||||
// version: version,
|
||||
// challenge: bytesToBase64UrlSafe(challenge),
|
||||
// }], [], function(res){
|
||||
// local_is(res.errorCode, 2, "Should not fail gracefully on invalid formatted facet lists");
|
||||
// });
|
||||
|
||||
local_finished();
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,38 @@
|
|||
<!DOCTYPE html>
|
||||
<meta charset=utf-8>
|
||||
<head>
|
||||
<script src="u2futil.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<p>Test for AppID / FacetID behavior for FIDO Universal Second Factor</p>
|
||||
<script class="testbody" type="text/javascript">
|
||||
"use strict";
|
||||
|
||||
SpecialPowers.setBoolPref("security.webauth.u2f", true);
|
||||
SpecialPowers.setBoolPref("security.webauth.u2f.softtoken", true);
|
||||
|
||||
var version = "U2F_V2";
|
||||
var challenge = new Uint8Array(16);
|
||||
|
||||
local_is(window.location.origin, "https://test1.example.com", "Is loaded correctly");
|
||||
|
||||
// eTLD+1 check
|
||||
u2f.register("https://example.com/appId", [{
|
||||
version: version,
|
||||
challenge: bytesToBase64UrlSafe(challenge),
|
||||
}], [], function(res){
|
||||
local_is(res.errorCode, 0, "AppID should work from a subdomain");
|
||||
});
|
||||
|
||||
u2f.register("https://example.net/appId", [{
|
||||
version: version,
|
||||
challenge: bytesToBase64UrlSafe(challenge),
|
||||
}], [], function(res){
|
||||
local_isnot(res.errorCode, 0, "AppID should not work from other domains");
|
||||
});
|
||||
|
||||
local_finished();
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,77 @@
|
|||
<!DOCTYPE html>
|
||||
<meta charset=utf-8>
|
||||
<head>
|
||||
<script src="u2futil.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<p>Test for Register behavior for FIDO Universal Second Factor</p>
|
||||
<script class="testbody" type="text/javascript">
|
||||
"use strict";
|
||||
|
||||
SpecialPowers.setBoolPref("security.webauth.u2f", true);
|
||||
SpecialPowers.setBoolPref("security.webauth.u2f.softtoken", true);
|
||||
|
||||
var version = "U2F_V2";
|
||||
var challenge = new Uint8Array(16);
|
||||
|
||||
local_is(window.location.origin, "https://example.com", "Is loaded correctly");
|
||||
|
||||
// eTLD+1 check
|
||||
u2f.register("https://example.com/appId", [{
|
||||
version: version,
|
||||
challenge: bytesToBase64UrlSafe(challenge),
|
||||
}], [], function(res){
|
||||
local_is(res.errorCode, 0, "AppID should work from a subdomain");
|
||||
});
|
||||
|
||||
u2f.register("https://example.net/appId", [{
|
||||
version: version,
|
||||
challenge: bytesToBase64UrlSafe(challenge),
|
||||
}], [], function(res){
|
||||
local_is(res.errorCode, 2, "AppID should not work from other domains");
|
||||
});
|
||||
|
||||
u2f.register("", [], [], function(res){
|
||||
local_is(res.errorCode, 2, "Empty register requests");
|
||||
});
|
||||
|
||||
local_doesThrow(function(){
|
||||
u2f.register("", null, [], null);
|
||||
}, "Non-array register requests");
|
||||
|
||||
local_doesThrow(function(){
|
||||
u2f.register("", [], null, null);
|
||||
}, "Non-array sign requests");
|
||||
|
||||
local_doesThrow(function(){
|
||||
u2f.register("", null, null, null);
|
||||
}, "Non-array for both arguments");
|
||||
|
||||
u2f.register("", [{}], [], function(res){
|
||||
local_is(res.errorCode, 2, "Empty request");
|
||||
});
|
||||
|
||||
u2f.register("https://example.net/appId", [{
|
||||
version: version,
|
||||
}], [], function(res){
|
||||
local_is(res.errorCode, 2, "Missing challenge");
|
||||
});
|
||||
|
||||
u2f.register("https://example.net/appId", [{
|
||||
challenge: bytesToBase64UrlSafe(challenge),
|
||||
}], [], function(res){
|
||||
local_is(res.errorCode, 2, "Missing version");
|
||||
});
|
||||
|
||||
u2f.register("https://example.net/appId", [{
|
||||
version: "a_version_00",
|
||||
challenge: bytesToBase64UrlSafe(challenge),
|
||||
}], [], function(res){
|
||||
local_is(res.errorCode, 2, "Invalid version");
|
||||
});
|
||||
|
||||
local_finished();
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,38 @@
|
|||
<!DOCTYPE html>
|
||||
<meta charset=utf-8>
|
||||
<head>
|
||||
<title>Test for FIDO Universal Second Factor No Token</title>
|
||||
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<script src="u2futil.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
|
||||
</head>
|
||||
<body>
|
||||
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1231681">Mozilla Bug 1231681</a>
|
||||
<p id="display"></p>
|
||||
<div id="content" style="display: none">
|
||||
</div>
|
||||
<pre id="test">
|
||||
<script class="testbody" type="text/javascript">
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
SpecialPowers.setBoolPref("security.webauth.u2f", true);
|
||||
SpecialPowers.setBoolPref("security.webauth.u2f.softtoken", false);
|
||||
SpecialPowers.setBoolPref("security.webauth.u2f.usbtoken", false);
|
||||
|
||||
var challenge = new Uint8Array(16);
|
||||
window.crypto.getRandomValues(challenge);
|
||||
|
||||
var regRequest = {
|
||||
version: "U2F_V2",
|
||||
challenge: bytesToBase64UrlSafe(challenge),
|
||||
};
|
||||
|
||||
u2f.register(window.location.origin, [regRequest], [], function (regResponse) {
|
||||
isnot(regResponse.errorCode, 0, "The registration should be rejected.");
|
||||
|
||||
SimpleTest.finish();
|
||||
});
|
||||
|
||||
</script>
|
||||
</pre>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,57 @@
|
|||
<!DOCTYPE html>
|
||||
<meta charset=utf-8>
|
||||
<head>
|
||||
<title>Test for Utility Methods for other FIDO Universal Second Factor tests</title>
|
||||
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<script type="text/javascript" src="/tests/dom/u2f/tests/u2futil.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
|
||||
</head>
|
||||
<body>
|
||||
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1231681">Mozilla Bug 1231681</a>
|
||||
<p id="display"></p>
|
||||
<div id="content" style="display: none">
|
||||
</div>
|
||||
<pre id="test">
|
||||
<script class="testbody" type="text/javascript">
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
SpecialPowers.setBoolPref("security.webauth.u2f", true);
|
||||
SpecialPowers.setBoolPref("security.webauth.u2f.softtoken", true);
|
||||
SpecialPowers.setBoolPref("security.webauth.u2f.usbtoken", false);
|
||||
|
||||
// Example from:
|
||||
// https://fidoalliance.org/specs/fido-u2f-v1.0-nfc-bt-amendment-20150514/fido-u2f-raw-message-formats.html
|
||||
//
|
||||
// Run this example from the console to check that the u2futil methods work
|
||||
var pubKey = hexDecode("04d368f1b665bade3c33a20f1e429c7750d5033660c019119d29aa4ba7abc04aa7c80a46bbe11ca8cb5674d74f31f8a903f6bad105fb6ab74aefef4db8b0025e1d");
|
||||
var appId = "https://gstatic.com/securitykey/a/example.com";
|
||||
var clientData = string2buffer('{"typ":"navigator.id.getAssertion","challenge":"opsXqUifDriAAmWclinfbS0e-USY0CgyJHe_Otd7z8o","cid_pubkey":{"kty":"EC","crv":"P-256","x":"HzQwlfXX7Q4S5MtCCnZUNBw3RMzPO9tOyWjBqRl4tJ8","y":"XVguGFLIZx1fXg3wNqfdbn75hi4-_7-BxhMljw42Ht4"},"origin":"http://example.com"}');
|
||||
var presenceAndCounter = hexDecode("0100000001");
|
||||
var signature = hexDecode("304402204b5f0cd17534cedd8c34ee09570ef542a353df4436030ce43d406de870b847780220267bb998fac9b7266eb60e7cb0b5eabdfd5ba9614f53c7b22272ec10047a923f");
|
||||
|
||||
// Import the key
|
||||
// Assemble the client data
|
||||
// Verify
|
||||
Promise.all([
|
||||
importPublicKey(pubKey),
|
||||
assembleSignedData(appId, presenceAndCounter, clientData)
|
||||
])
|
||||
.then(function(results) {
|
||||
var importedKey = results[0];
|
||||
var signedData = new Uint8Array(results[1]);
|
||||
return verifySignature(importedKey, signedData, signature);
|
||||
})
|
||||
.then(function(verified) {
|
||||
console.log("verified:", verified);
|
||||
ok(true, "Utility methods work")
|
||||
SimpleTest.finish();
|
||||
})
|
||||
.catch(function(err) {
|
||||
console.log("error:", err);
|
||||
ok(false, "Utility methods failed")
|
||||
SimpleTest.finish();
|
||||
});
|
||||
|
||||
</script>
|
||||
</pre>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,144 @@
|
|||
function local_is(value, expected, message) {
|
||||
if (value === expected) {
|
||||
local_ok(true, message);
|
||||
} else {
|
||||
local_ok(false, message + " unexpectedly: " + value + " !== " + expected);
|
||||
}
|
||||
}
|
||||
|
||||
function local_isnot(value, expected, message) {
|
||||
if (value !== expected) {
|
||||
local_ok(true, message);
|
||||
} else {
|
||||
local_ok(false, message + " unexpectedly: " + value + " === " + expected);
|
||||
}
|
||||
}
|
||||
|
||||
function local_ok(expression, message) {
|
||||
let body = {"test": this.location.pathname, "status":expression, "msg": message}
|
||||
parent.postMessage(body, "http://mochi.test:8888");
|
||||
}
|
||||
|
||||
function local_doesThrow(fn, name) {
|
||||
var gotException = false;
|
||||
try {
|
||||
fn();
|
||||
} catch (ex) { gotException = true; }
|
||||
local_ok(gotException, name);
|
||||
};
|
||||
|
||||
function local_finished() {
|
||||
parent.postMessage({"done":true}, "http://mochi.test:8888");
|
||||
}
|
||||
|
||||
function string2buffer(str) {
|
||||
return (new Uint8Array(str.length)).map((x, i) => str.charCodeAt(i));
|
||||
}
|
||||
|
||||
function buffer2string(buf) {
|
||||
var str = "";
|
||||
buf.map(x => str += String.fromCharCode(x));
|
||||
return str;
|
||||
}
|
||||
|
||||
function bytesToBase64(u8a){
|
||||
var CHUNK_SZ = 0x8000;
|
||||
var c = [];
|
||||
for (var i = 0; i < u8a.length; i += CHUNK_SZ) {
|
||||
c.push(String.fromCharCode.apply(null, u8a.subarray(i, i + CHUNK_SZ)));
|
||||
}
|
||||
return window.btoa(c.join(""));
|
||||
}
|
||||
|
||||
function base64ToBytes(b64encoded) {
|
||||
return new Uint8Array(window.atob(b64encoded).split("").map(function(c) {
|
||||
return c.charCodeAt(0);
|
||||
}));
|
||||
}
|
||||
|
||||
function bytesToBase64UrlSafe(buf) {
|
||||
return bytesToBase64(buf)
|
||||
.replace(/\+/g, "-")
|
||||
.replace(/\//g, "_")
|
||||
.replace(/=/g, "");
|
||||
}
|
||||
|
||||
function base64ToBytesUrlSafe(str) {
|
||||
if (str.length % 4 == 1) {
|
||||
throw "Improper b64 string";
|
||||
}
|
||||
|
||||
var b64 = str.replace(/\-/g, "+").replace(/\_/g, "/");
|
||||
while (b64.length % 4 != 0) {
|
||||
b64 += "=";
|
||||
}
|
||||
return base64ToBytes(b64);
|
||||
}
|
||||
|
||||
function hexEncode(buf) {
|
||||
return Array.from(buf)
|
||||
.map(x => ("0"+x.toString(16)).substr(-2))
|
||||
.join("");
|
||||
}
|
||||
|
||||
function hexDecode(str) {
|
||||
return new Uint8Array(str.match(/../g).map(x => parseInt(x, 16)));
|
||||
}
|
||||
|
||||
function importPublicKey(keyBytes) {
|
||||
if (keyBytes[0] != 0x04 || keyBytes.byteLength != 65) {
|
||||
throw "Bad public key octet string";
|
||||
}
|
||||
var jwk = {
|
||||
kty: "EC",
|
||||
crv: "P-256",
|
||||
x: bytesToBase64UrlSafe(keyBytes.slice(1, 33)),
|
||||
y: bytesToBase64UrlSafe(keyBytes.slice(33))
|
||||
};
|
||||
return crypto.subtle.importKey("jwk", jwk, {name: "ECDSA", namedCurve: "P-256"}, true, ["verify"])
|
||||
}
|
||||
|
||||
function assembleSignedData(appId, presenceAndCounter, clientData) {
|
||||
var appIdBuf = string2buffer(appId);
|
||||
return Promise.all([
|
||||
crypto.subtle.digest("SHA-256", appIdBuf),
|
||||
crypto.subtle.digest("SHA-256", clientData)
|
||||
])
|
||||
.then(function(digests) {
|
||||
var appParam = new Uint8Array(digests[0]);
|
||||
var clientParam = new Uint8Array(digests[1]);
|
||||
|
||||
var signedData = new Uint8Array(32 + 1 + 4 + 32);
|
||||
appParam.map((x, i) => signedData[0 + i] = x);
|
||||
presenceAndCounter.map((x, i) => signedData[32 + i] = x);
|
||||
clientParam.map((x, i) => signedData[37 + i] = x);
|
||||
return signedData;
|
||||
});
|
||||
}
|
||||
|
||||
function verifySignature(key, data, derSig) {
|
||||
if (derSig.byteLength < 70) {
|
||||
console.log("bad sig: " + hexEncode(derSig))
|
||||
throw "Invalid signature length: " + derSig.byteLength;
|
||||
}
|
||||
|
||||
// Poor man's ASN.1 decode
|
||||
// R and S are always 32 bytes. If ether has a DER
|
||||
// length > 32, it's just zeros we can chop off.
|
||||
var lenR = derSig[3];
|
||||
var lenS = derSig[3 + lenR + 2];
|
||||
var padR = lenR - 32;
|
||||
var padS = lenS - 32;
|
||||
var sig = new Uint8Array(64);
|
||||
derSig.slice(4 + padR, 4 + lenR).map((x, i) => sig[i] = x);
|
||||
derSig.slice(4 + lenR + 2 + padS, 4 + lenR + 2 + lenS).map(
|
||||
(x, i) => sig[32 + i] = x
|
||||
);
|
||||
|
||||
console.log("data: " + hexEncode(data));
|
||||
console.log("der: " + hexEncode(derSig));
|
||||
console.log("raw: " + hexEncode(sig));
|
||||
|
||||
var alg = {name: "ECDSA", hash: "SHA-256"};
|
||||
return crypto.subtle.verify(alg, key, sig, data);
|
||||
}
|
Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше
Загрузка…
Ссылка в новой задаче