Merge inbound to mozilla-central. a=merge
|
@ -121,6 +121,7 @@ skip-if = (os == 'linux' && debug && bits == 32) # Bug 1303439
|
|||
[browser_net_filter-flags.js]
|
||||
[browser_net_footer-summary.js]
|
||||
[browser_net_headers-alignment.js]
|
||||
[browser_net_headers_filter.js]
|
||||
[browser_net_headers_sorted.js]
|
||||
[browser_net_image-tooltip.js]
|
||||
[browser_net_json-b64.js]
|
||||
|
|
|
@ -0,0 +1,72 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* Tests if Request-Headers and Response-Headers are correctly filtered in Headers tab.
|
||||
*/
|
||||
add_task(async function () {
|
||||
let { tab, monitor } = await initNetMonitor(SIMPLE_SJS);
|
||||
info("Starting test... ");
|
||||
|
||||
let { document, store, windowRequire } = monitor.panelWin;
|
||||
let Actions = windowRequire("devtools/client/netmonitor/src/actions/index");
|
||||
let {
|
||||
getSortedRequests,
|
||||
} = windowRequire("devtools/client/netmonitor/src/selectors/index");
|
||||
|
||||
store.dispatch(Actions.batchEnable(false));
|
||||
|
||||
let wait = waitForNetworkEvents(monitor, 1);
|
||||
tab.linkedBrowser.reload();
|
||||
await wait;
|
||||
|
||||
wait = waitUntil(() => document.querySelector(".headers-overview"));
|
||||
EventUtils.sendMouseEvent({ type: "mousedown" },
|
||||
document.querySelectorAll(".request-list-item")[0]);
|
||||
await wait;
|
||||
|
||||
await waitUntil(() => {
|
||||
let request = getSortedRequests(store.getState()).get(0);
|
||||
return request.requestHeaders && request.responseHeaders;
|
||||
});
|
||||
|
||||
document.querySelectorAll(".devtools-filterinput")[1].focus();
|
||||
EventUtils.synthesizeKey("con", {});
|
||||
await waitUntil(() => document.querySelector(".treeRow.hidden"));
|
||||
|
||||
info("Check if Headers are filtered correctly");
|
||||
|
||||
let totalResponseHeaders = ["cache-control", "connection", "content-length",
|
||||
"content-type", "date", "expires", "foo-bar",
|
||||
"foo-bar", "foo-bar", "pragma", "server", "set-cookie",
|
||||
"set-cookie"];
|
||||
let expectedResponseHeaders = ["cache-control", "connection", "content-length",
|
||||
"content-type"];
|
||||
let expectedRequestHeaders = ["Cache-Control", "Connection"];
|
||||
|
||||
let labelCells = document.querySelectorAll(".treeLabelCell");
|
||||
let filteredResponseHeaders = [];
|
||||
let filteredRequestHeaders = [];
|
||||
|
||||
let responseHeadersLength = totalResponseHeaders.length;
|
||||
for (let i = 1; i < responseHeadersLength + 1; i++) {
|
||||
if (labelCells[i].offsetHeight > 0) {
|
||||
filteredResponseHeaders.push(labelCells[i].innerText);
|
||||
}
|
||||
}
|
||||
|
||||
for (let i = responseHeadersLength + 2; i < labelCells.length; i++) {
|
||||
if (labelCells[i].offsetHeight > 0) {
|
||||
filteredRequestHeaders.push(labelCells[i].innerText);
|
||||
}
|
||||
}
|
||||
|
||||
is(filteredResponseHeaders.toString(), expectedResponseHeaders.toString(),
|
||||
"Response Headers are filtered");
|
||||
is(filteredRequestHeaders.toString(), expectedRequestHeaders.toString(),
|
||||
"Request Headers are filtered");
|
||||
|
||||
await teardown(monitor);
|
||||
});
|
|
@ -82,7 +82,7 @@
|
|||
|
||||
/* Filtering */
|
||||
.treeTable .treeRow.hidden {
|
||||
display: none;
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.treeTable .treeValueCellDivider {
|
||||
|
|
|
@ -143,5 +143,23 @@ CredentialsContainer::Store(const Credential& aCredential, ErrorResult& aRv)
|
|||
return mManager->Store(aCredential);
|
||||
}
|
||||
|
||||
already_AddRefed<Promise>
|
||||
CredentialsContainer::PreventSilentAccess(ErrorResult& aRv)
|
||||
{
|
||||
nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(mParent);
|
||||
if (NS_WARN_IF(!global)) {
|
||||
aRv.Throw(NS_ERROR_FAILURE);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
RefPtr<Promise> promise = Promise::Create(global, aRv);
|
||||
if (NS_WARN_IF(aRv.Failed())) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
promise->MaybeResolveWithUndefined();
|
||||
return promise.forget();
|
||||
}
|
||||
|
||||
} // namespace dom
|
||||
} // namespace mozilla
|
||||
|
|
|
@ -41,6 +41,9 @@ public:
|
|||
already_AddRefed<Promise>
|
||||
Store(const Credential& aCredential, ErrorResult& aRv);
|
||||
|
||||
already_AddRefed<Promise>
|
||||
PreventSilentAccess(ErrorResult& aRv);
|
||||
|
||||
private:
|
||||
~CredentialsContainer();
|
||||
|
||||
|
|
|
@ -42,6 +42,15 @@ function testSameOrigin() {
|
|||
.catch(function sameOriginCatch(aResult) {
|
||||
local_ok(false, "Should not have failed " + aResult);
|
||||
})
|
||||
.then(function sameOriginPreventSilentAccess() {
|
||||
return navigator.credentials.preventSilentAccess();
|
||||
})
|
||||
.then(function sameOriginPreventSilentAccessThen(aResult) {
|
||||
local_ok(aResult == undefined, "PreventSilentAccess worked " + aResult);
|
||||
})
|
||||
.catch(function sameOriginPreventSilentAccessCatch(aResult) {
|
||||
local_ok(false, "Should not have failed " + aResult);
|
||||
})
|
||||
.then(function() {
|
||||
local_finished();
|
||||
});
|
||||
|
@ -58,6 +67,15 @@ function testCrossOrigin() {
|
|||
local_ok(aResult.toString().startsWith("NotAllowedError"),
|
||||
"Expecting a NotAllowedError, received " + aResult);
|
||||
})
|
||||
.then(function crossOriginPreventSilentAccess() {
|
||||
return navigator.credentials.preventSilentAccess();
|
||||
})
|
||||
.then(function crossOriginPreventSilentAccessThen(aResult) {
|
||||
local_ok(aResult == undefined, "PreventSilentAccess worked " + aResult);
|
||||
})
|
||||
.catch(function crossOriginPreventSilentAccessCatch(aResult) {
|
||||
local_ok(false, "Should not have failed " + aResult);
|
||||
})
|
||||
.then(function() {
|
||||
local_finished();
|
||||
});
|
||||
|
|
123
dom/u2f/U2F.cpp
|
@ -9,6 +9,7 @@
|
|||
#include "mozilla/ipc/PBackgroundChild.h"
|
||||
#include "mozilla/ipc/BackgroundChild.h"
|
||||
#include "mozilla/dom/WebAuthnTransactionChild.h"
|
||||
#include "mozilla/dom/WebAuthnUtil.h"
|
||||
#include "nsContentUtils.h"
|
||||
#include "nsICryptoHash.h"
|
||||
#include "nsIEffectiveTLDService.h"
|
||||
|
@ -128,110 +129,6 @@ RegisteredKeysToScopedCredentialList(const nsAString& aAppId,
|
|||
}
|
||||
}
|
||||
|
||||
enum class U2FOperation
|
||||
{
|
||||
Register,
|
||||
Sign
|
||||
};
|
||||
|
||||
static ErrorCode
|
||||
EvaluateAppID(nsPIDOMWindowInner* aParent, const nsString& aOrigin,
|
||||
const U2FOperation& aOp, /* in/out */ nsString& aAppId)
|
||||
{
|
||||
// Facet is the specification's way of referring to the web origin.
|
||||
nsAutoCString facetString = NS_ConvertUTF16toUTF8(aOrigin);
|
||||
nsCOMPtr<nsIURI> facetUri;
|
||||
if (NS_FAILED(NS_NewURI(getter_AddRefs(facetUri), facetString))) {
|
||||
return ErrorCode::BAD_REQUEST;
|
||||
}
|
||||
|
||||
// If the facetId (origin) is not HTTPS, reject
|
||||
bool facetIsHttps = false;
|
||||
if (NS_FAILED(facetUri->SchemeIs("https", &facetIsHttps)) || !facetIsHttps) {
|
||||
return ErrorCode::BAD_REQUEST;
|
||||
}
|
||||
|
||||
// If the appId is empty or null, overwrite it with the facetId and accept
|
||||
if (aAppId.IsEmpty() || aAppId.EqualsLiteral("null")) {
|
||||
aAppId.Assign(aOrigin);
|
||||
return ErrorCode::OK;
|
||||
}
|
||||
|
||||
// AppID is user-supplied. It's quite possible for this parse to fail.
|
||||
nsAutoCString appIdString = NS_ConvertUTF16toUTF8(aAppId);
|
||||
nsCOMPtr<nsIURI> appIdUri;
|
||||
if (NS_FAILED(NS_NewURI(getter_AddRefs(appIdUri), appIdString))) {
|
||||
return ErrorCode::BAD_REQUEST;
|
||||
}
|
||||
|
||||
// if the appId URL is not HTTPS, reject.
|
||||
bool appIdIsHttps = false;
|
||||
if (NS_FAILED(appIdUri->SchemeIs("https", &appIdIsHttps)) || !appIdIsHttps) {
|
||||
return ErrorCode::BAD_REQUEST;
|
||||
}
|
||||
|
||||
nsAutoCString appIdHost;
|
||||
if (NS_FAILED(appIdUri->GetAsciiHost(appIdHost))) {
|
||||
return ErrorCode::BAD_REQUEST;
|
||||
}
|
||||
|
||||
// Allow localhost.
|
||||
if (appIdHost.EqualsLiteral("localhost")) {
|
||||
nsAutoCString facetHost;
|
||||
if (NS_FAILED(facetUri->GetAsciiHost(facetHost))) {
|
||||
return ErrorCode::BAD_REQUEST;
|
||||
}
|
||||
|
||||
if (facetHost.EqualsLiteral("localhost")) {
|
||||
return ErrorCode::OK;
|
||||
}
|
||||
}
|
||||
|
||||
// Run the HTML5 algorithm to relax the same-origin policy, copied from W3C
|
||||
// Web Authentication. See Bug 1244959 comment #8 for context on why we are
|
||||
// doing this instead of implementing the external-fetch FacetID logic.
|
||||
nsCOMPtr<nsIDocument> document = aParent->GetDoc();
|
||||
if (!document || !document->IsHTMLDocument()) {
|
||||
return ErrorCode::BAD_REQUEST;
|
||||
}
|
||||
nsHTMLDocument* html = document->AsHTMLDocument();
|
||||
if (NS_WARN_IF(!html)) {
|
||||
return ErrorCode::BAD_REQUEST;
|
||||
}
|
||||
|
||||
// Use the base domain as the facet for evaluation. This lets this algorithm
|
||||
// relax the whole eTLD+1.
|
||||
nsCOMPtr<nsIEffectiveTLDService> tldService =
|
||||
do_GetService(NS_EFFECTIVETLDSERVICE_CONTRACTID);
|
||||
if (!tldService) {
|
||||
return ErrorCode::BAD_REQUEST;
|
||||
}
|
||||
|
||||
nsAutoCString lowestFacetHost;
|
||||
if (NS_FAILED(tldService->GetBaseDomain(facetUri, 0, lowestFacetHost))) {
|
||||
return ErrorCode::BAD_REQUEST;
|
||||
}
|
||||
|
||||
MOZ_LOG(gU2FLog, LogLevel::Debug,
|
||||
("AppId %s Facet %s", appIdHost.get(), lowestFacetHost.get()));
|
||||
|
||||
if (html->IsRegistrableDomainSuffixOfOrEqualTo(NS_ConvertUTF8toUTF16(lowestFacetHost),
|
||||
appIdHost)) {
|
||||
return ErrorCode::OK;
|
||||
}
|
||||
|
||||
// Bug #1436078 - Permit Google Accounts. Remove in Bug #1436085 in Jan 2023.
|
||||
if (aOp == U2FOperation::Sign && lowestFacetHost.EqualsLiteral("google.com") &&
|
||||
(aAppId.Equals(kGoogleAccountsAppId1) ||
|
||||
aAppId.Equals(kGoogleAccountsAppId2))) {
|
||||
MOZ_LOG(gU2FLog, LogLevel::Debug,
|
||||
("U2F permitted for Google Accounts via Bug #1436085"));
|
||||
return ErrorCode::OK;
|
||||
}
|
||||
|
||||
return ErrorCode::BAD_REQUEST;
|
||||
}
|
||||
|
||||
static nsresult
|
||||
BuildTransactionHashes(const nsCString& aRpId,
|
||||
const nsCString& aClientDataJSON,
|
||||
|
@ -375,13 +272,10 @@ U2F::Register(const nsAString& aAppId,
|
|||
}
|
||||
|
||||
// Evaluate the AppID
|
||||
nsString adjustedAppId;
|
||||
adjustedAppId.Assign(aAppId);
|
||||
ErrorCode appIdResult = EvaluateAppID(mParent, mOrigin, U2FOperation::Register,
|
||||
adjustedAppId);
|
||||
if (appIdResult != ErrorCode::OK) {
|
||||
nsString adjustedAppId(aAppId);
|
||||
if (!EvaluateAppID(mParent, mOrigin, U2FOperation::Register, adjustedAppId)) {
|
||||
RegisterResponse response;
|
||||
response.mErrorCode.Construct(static_cast<uint32_t>(appIdResult));
|
||||
response.mErrorCode.Construct(static_cast<uint32_t>(ErrorCode::BAD_REQUEST));
|
||||
ExecuteCallback(response, callback);
|
||||
return;
|
||||
}
|
||||
|
@ -538,13 +432,10 @@ U2F::Sign(const nsAString& aAppId,
|
|||
}
|
||||
|
||||
// Evaluate the AppID
|
||||
nsString adjustedAppId;
|
||||
adjustedAppId.Assign(aAppId);
|
||||
ErrorCode appIdResult = EvaluateAppID(mParent, mOrigin, U2FOperation::Sign,
|
||||
adjustedAppId);
|
||||
if (appIdResult != ErrorCode::OK) {
|
||||
nsString adjustedAppId(aAppId);
|
||||
if (!EvaluateAppID(mParent, mOrigin, U2FOperation::Sign, adjustedAppId)) {
|
||||
SignResponse response;
|
||||
response.mErrorCode.Construct(static_cast<uint32_t>(appIdResult));
|
||||
response.mErrorCode.Construct(static_cast<uint32_t>(ErrorCode::BAD_REQUEST));
|
||||
ExecuteCallback(response, callback);
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -30,8 +30,20 @@ struct WebAuthnScopedCredential {
|
|||
uint8_t transports;
|
||||
};
|
||||
|
||||
struct WebAuthnExtension {
|
||||
/* TODO Fill in with predefined extensions */
|
||||
struct WebAuthnExtensionAppId {
|
||||
uint8_t[] AppId;
|
||||
};
|
||||
|
||||
union WebAuthnExtension {
|
||||
WebAuthnExtensionAppId;
|
||||
};
|
||||
|
||||
struct WebAuthnExtensionResultAppId {
|
||||
bool AppId;
|
||||
};
|
||||
|
||||
union WebAuthnExtensionResult {
|
||||
WebAuthnExtensionResultAppId;
|
||||
};
|
||||
|
||||
struct WebAuthnMakeCredentialInfo {
|
||||
|
@ -57,8 +69,10 @@ struct WebAuthnGetAssertionInfo {
|
|||
};
|
||||
|
||||
struct WebAuthnGetAssertionResult {
|
||||
uint8_t[] RpIdHash;
|
||||
uint8_t[] CredentialID;
|
||||
uint8_t[] SigBuffer;
|
||||
WebAuthnExtensionResult[] Extensions;
|
||||
};
|
||||
|
||||
async protocol PWebAuthnTransaction {
|
||||
|
|
|
@ -115,6 +115,19 @@ PublicKeyCredential::IsUserVerifyingPlatformAuthenticatorAvailable(GlobalObject&
|
|||
return promise.forget();
|
||||
}
|
||||
|
||||
void
|
||||
PublicKeyCredential::GetClientExtensionResults(AuthenticationExtensionsClientOutputs& aResult)
|
||||
{
|
||||
aResult = mClientExtensionOutputs;
|
||||
}
|
||||
|
||||
void
|
||||
PublicKeyCredential::SetClientExtensionResultAppId(bool aResult)
|
||||
{
|
||||
mClientExtensionOutputs.mAppid.Construct();
|
||||
mClientExtensionOutputs.mAppid.Value() = aResult;
|
||||
}
|
||||
|
||||
|
||||
} // namespace dom
|
||||
} // namespace mozilla
|
||||
|
|
|
@ -49,12 +49,17 @@ public:
|
|||
static already_AddRefed<Promise>
|
||||
IsUserVerifyingPlatformAuthenticatorAvailable(GlobalObject& aGlobal);
|
||||
|
||||
void
|
||||
GetClientExtensionResults(AuthenticationExtensionsClientOutputs& aResult);
|
||||
|
||||
void
|
||||
SetClientExtensionResultAppId(bool aResult);
|
||||
|
||||
private:
|
||||
CryptoBuffer mRawId;
|
||||
JS::Heap<JSObject*> mRawIdCachedObj;
|
||||
RefPtr<AuthenticatorResponse> mResponse;
|
||||
// Extensions are not supported yet.
|
||||
// <some type> mClientExtensionResults;
|
||||
AuthenticationExtensionsClientOutputs mClientExtensionOutputs;
|
||||
};
|
||||
|
||||
} // namespace dom
|
||||
|
|
|
@ -127,6 +127,7 @@ U2FHIDTokenManager::Register(const nsTArray<WebAuthnScopedCredential>& aCredenti
|
|||
}
|
||||
|
||||
ClearPromises();
|
||||
mCurrentAppId = aApplication;
|
||||
mTransactionId = rust_u2f_mgr_register(mU2FManager,
|
||||
registerFlags,
|
||||
(uint64_t)aTimeoutMS,
|
||||
|
@ -164,6 +165,7 @@ RefPtr<U2FSignPromise>
|
|||
U2FHIDTokenManager::Sign(const nsTArray<WebAuthnScopedCredential>& aCredentials,
|
||||
const nsTArray<uint8_t>& aApplication,
|
||||
const nsTArray<uint8_t>& aChallenge,
|
||||
const nsTArray<WebAuthnExtension>& aExtensions,
|
||||
bool aRequireUserVerification,
|
||||
uint32_t aTimeoutMS)
|
||||
{
|
||||
|
@ -176,15 +178,25 @@ U2FHIDTokenManager::Sign(const nsTArray<WebAuthnScopedCredential>& aCredentials,
|
|||
signFlags |= U2F_FLAG_REQUIRE_USER_VERIFICATION;
|
||||
}
|
||||
|
||||
nsTArray<nsTArray<uint8_t>> appIds;
|
||||
appIds.AppendElement(aApplication);
|
||||
|
||||
// Process extensions.
|
||||
for (const WebAuthnExtension& ext: aExtensions) {
|
||||
if (ext.type() == WebAuthnExtension::TWebAuthnExtensionAppId) {
|
||||
appIds.AppendElement(ext.get_WebAuthnExtensionAppId().AppId());
|
||||
}
|
||||
}
|
||||
|
||||
ClearPromises();
|
||||
mCurrentAppId = aApplication;
|
||||
mTransactionId = rust_u2f_mgr_sign(mU2FManager,
|
||||
signFlags,
|
||||
(uint64_t)aTimeoutMS,
|
||||
u2f_sign_callback,
|
||||
aChallenge.Elements(),
|
||||
aChallenge.Length(),
|
||||
aApplication.Elements(),
|
||||
aApplication.Length(),
|
||||
U2FAppIds(appIds).Get(),
|
||||
U2FKeyHandles(aCredentials).Get());
|
||||
if (mTransactionId == 0) {
|
||||
return U2FSignPromise::CreateAndReject(NS_ERROR_DOM_UNKNOWN_ERR, __func__);
|
||||
|
@ -234,6 +246,12 @@ U2FHIDTokenManager::HandleSignResult(UniquePtr<U2FResult>&& aResult)
|
|||
|
||||
MOZ_ASSERT(!mSignPromise.IsEmpty());
|
||||
|
||||
nsTArray<uint8_t> appId;
|
||||
if (!aResult->CopyAppId(appId)) {
|
||||
mSignPromise.Reject(NS_ERROR_DOM_UNKNOWN_ERR, __func__);
|
||||
return;
|
||||
}
|
||||
|
||||
nsTArray<uint8_t> keyHandle;
|
||||
if (!aResult->CopyKeyHandle(keyHandle)) {
|
||||
mSignPromise.Reject(NS_ERROR_DOM_UNKNOWN_ERR, __func__);
|
||||
|
@ -246,7 +264,14 @@ U2FHIDTokenManager::HandleSignResult(UniquePtr<U2FResult>&& aResult)
|
|||
return;
|
||||
}
|
||||
|
||||
WebAuthnGetAssertionResult result(keyHandle, signature);
|
||||
nsTArray<WebAuthnExtensionResult> extensions;
|
||||
|
||||
if (appId != mCurrentAppId) {
|
||||
// Indicate to the RP that we used the FIDO appId.
|
||||
extensions.AppendElement(WebAuthnExtensionResultAppId(true));
|
||||
}
|
||||
|
||||
WebAuthnGetAssertionResult result(appId, keyHandle, signature, extensions);
|
||||
mSignPromise.Resolve(Move(result), __func__);
|
||||
}
|
||||
|
||||
|
|
|
@ -18,13 +18,32 @@
|
|||
namespace mozilla {
|
||||
namespace dom {
|
||||
|
||||
class U2FAppIds {
|
||||
public:
|
||||
explicit U2FAppIds(const nsTArray<nsTArray<uint8_t>>& aApplications)
|
||||
{
|
||||
mAppIds = rust_u2f_app_ids_new();
|
||||
|
||||
for (auto& app_id: aApplications) {
|
||||
rust_u2f_app_ids_add(mAppIds, app_id.Elements(), app_id.Length());
|
||||
}
|
||||
}
|
||||
|
||||
rust_u2f_app_ids* Get() { return mAppIds; }
|
||||
|
||||
~U2FAppIds() { rust_u2f_app_ids_free(mAppIds); }
|
||||
|
||||
private:
|
||||
rust_u2f_app_ids* mAppIds;
|
||||
};
|
||||
|
||||
class U2FKeyHandles {
|
||||
public:
|
||||
explicit U2FKeyHandles(const nsTArray<WebAuthnScopedCredential>& aCredentials)
|
||||
{
|
||||
mKeyHandles = rust_u2f_khs_new();
|
||||
|
||||
for (auto cred: aCredentials) {
|
||||
for (auto& cred: aCredentials) {
|
||||
rust_u2f_khs_add(mKeyHandles,
|
||||
cred.id().Elements(),
|
||||
cred.id().Length(),
|
||||
|
@ -66,6 +85,11 @@ public:
|
|||
return CopyBuffer(U2F_RESBUF_ID_SIGNATURE, aBuffer);
|
||||
}
|
||||
|
||||
bool CopyAppId(nsTArray<uint8_t>& aBuffer)
|
||||
{
|
||||
return CopyBuffer(U2F_RESBUF_ID_APPID, aBuffer);
|
||||
}
|
||||
|
||||
private:
|
||||
bool CopyBuffer(uint8_t aResBufID, nsTArray<uint8_t>& aBuffer) {
|
||||
if (!mResult) {
|
||||
|
@ -104,6 +128,7 @@ public:
|
|||
Sign(const nsTArray<WebAuthnScopedCredential>& aCredentials,
|
||||
const nsTArray<uint8_t>& aApplication,
|
||||
const nsTArray<uint8_t>& aChallenge,
|
||||
const nsTArray<WebAuthnExtension>& aExtensions,
|
||||
bool aRequireUserVerification,
|
||||
uint32_t aTimeoutMS) override;
|
||||
|
||||
|
@ -123,6 +148,7 @@ private:
|
|||
|
||||
rust_u2f_manager* mU2FManager;
|
||||
uint64_t mTransactionId;
|
||||
nsTArray<uint8_t> mCurrentAppId;
|
||||
MozPromiseHolder<U2FRegisterPromise> mRegisterPromise;
|
||||
MozPromiseHolder<U2FSignPromise> mSignPromise;
|
||||
};
|
||||
|
|
|
@ -693,6 +693,27 @@ U2FSoftTokenManager::Register(const nsTArray<WebAuthnScopedCredential>& aCredent
|
|||
return U2FRegisterPromise::CreateAndResolve(Move(result), __func__);
|
||||
}
|
||||
|
||||
bool
|
||||
U2FSoftTokenManager::FindRegisteredKeyHandle(const nsTArray<nsTArray<uint8_t>>& aAppIds,
|
||||
const nsTArray<WebAuthnScopedCredential>& aCredentials,
|
||||
/*out*/ nsTArray<uint8_t>& aKeyHandle,
|
||||
/*out*/ nsTArray<uint8_t>& aAppId)
|
||||
{
|
||||
for (const nsTArray<uint8_t>& app_id: aAppIds) {
|
||||
for (const WebAuthnScopedCredential& cred: aCredentials) {
|
||||
bool isRegistered = false;
|
||||
nsresult rv = IsRegistered(cred.id(), app_id, isRegistered);
|
||||
if (NS_SUCCEEDED(rv) && isRegistered) {
|
||||
aKeyHandle.Assign(cred.id());
|
||||
aAppId.Assign(app_id);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// 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.
|
||||
//
|
||||
|
@ -713,6 +734,7 @@ RefPtr<U2FSignPromise>
|
|||
U2FSoftTokenManager::Sign(const nsTArray<WebAuthnScopedCredential>& aCredentials,
|
||||
const nsTArray<uint8_t>& aApplication,
|
||||
const nsTArray<uint8_t>& aChallenge,
|
||||
const nsTArray<WebAuthnExtension>& aExtensions,
|
||||
bool aRequireUserVerification,
|
||||
uint32_t aTimeoutMS)
|
||||
{
|
||||
|
@ -728,18 +750,21 @@ U2FSoftTokenManager::Sign(const nsTArray<WebAuthnScopedCredential>& aCredentials
|
|||
return U2FSignPromise::CreateAndReject(NS_ERROR_DOM_NOT_ALLOWED_ERR, __func__);
|
||||
}
|
||||
|
||||
nsTArray<uint8_t> keyHandle;
|
||||
for (auto cred: aCredentials) {
|
||||
bool isRegistered = false;
|
||||
nsresult rv = IsRegistered(cred.id(), aApplication, isRegistered);
|
||||
if (NS_SUCCEEDED(rv) && isRegistered) {
|
||||
keyHandle.Assign(cred.id());
|
||||
break;
|
||||
nsTArray<nsTArray<uint8_t>> appIds;
|
||||
appIds.AppendElement(aApplication);
|
||||
|
||||
// Process extensions.
|
||||
for (const WebAuthnExtension& ext: aExtensions) {
|
||||
if (ext.type() == WebAuthnExtension::TWebAuthnExtensionAppId) {
|
||||
appIds.AppendElement(ext.get_WebAuthnExtensionAppId().AppId());
|
||||
}
|
||||
}
|
||||
|
||||
// Fail if we didn't recognize a key id.
|
||||
if (keyHandle.IsEmpty()) {
|
||||
nsTArray<uint8_t> chosenAppId(aApplication);
|
||||
nsTArray<uint8_t> keyHandle;
|
||||
|
||||
// Fail if we can't find a valid key handle.
|
||||
if (!FindRegisteredKeyHandle(appIds, aCredentials, keyHandle, chosenAppId)) {
|
||||
return U2FSignPromise::CreateAndReject(NS_ERROR_DOM_NOT_ALLOWED_ERR, __func__);
|
||||
}
|
||||
|
||||
|
@ -748,10 +773,10 @@ U2FSoftTokenManager::Sign(const nsTArray<WebAuthnScopedCredential>& aCredentials
|
|||
UniquePK11SlotInfo slot(PK11_GetInternalSlot());
|
||||
MOZ_ASSERT(slot.get());
|
||||
|
||||
if (NS_WARN_IF((aChallenge.Length() != kParamLen) || (aApplication.Length() != kParamLen))) {
|
||||
if (NS_WARN_IF((aChallenge.Length() != kParamLen) || (chosenAppId.Length() != kParamLen))) {
|
||||
MOZ_LOG(gNSSTokenLog, LogLevel::Warning,
|
||||
("Parameter lengths are wrong! challenge=%d app=%d expected=%d",
|
||||
(uint32_t)aChallenge.Length(), (uint32_t)aApplication.Length(), kParamLen));
|
||||
(uint32_t)aChallenge.Length(), (uint32_t)chosenAppId.Length(), kParamLen));
|
||||
|
||||
return U2FSignPromise::CreateAndReject(NS_ERROR_ILLEGAL_VALUE, __func__);
|
||||
}
|
||||
|
@ -760,8 +785,8 @@ U2FSoftTokenManager::Sign(const nsTArray<WebAuthnScopedCredential>& aCredentials
|
|||
UniqueSECKEYPrivateKey privKey = PrivateKeyFromKeyHandle(slot, mWrappingKey,
|
||||
const_cast<uint8_t*>(keyHandle.Elements()),
|
||||
keyHandle.Length(),
|
||||
const_cast<uint8_t*>(aApplication.Elements()),
|
||||
aApplication.Length());
|
||||
const_cast<uint8_t*>(chosenAppId.Elements()),
|
||||
chosenAppId.Length());
|
||||
if (NS_WARN_IF(!privKey.get())) {
|
||||
MOZ_LOG(gNSSTokenLog, LogLevel::Warning, ("Couldn't get the priv key!"));
|
||||
return U2FSignPromise::CreateAndReject(NS_ERROR_FAILURE, __func__);
|
||||
|
@ -790,7 +815,7 @@ U2FSoftTokenManager::Sign(const nsTArray<WebAuthnScopedCredential>& aCredentials
|
|||
|
||||
// It's OK to ignore the return values here because we're writing into
|
||||
// pre-allocated space
|
||||
signedDataBuf.AppendElements(aApplication.Elements(), aApplication.Length(),
|
||||
signedDataBuf.AppendElements(chosenAppId.Elements(), chosenAppId.Length(),
|
||||
mozilla::fallible);
|
||||
signedDataBuf.AppendElement(0x01, mozilla::fallible);
|
||||
signedDataBuf.AppendSECItem(counterItem);
|
||||
|
@ -832,7 +857,15 @@ U2FSoftTokenManager::Sign(const nsTArray<WebAuthnScopedCredential>& aCredentials
|
|||
signatureBuf.AppendSECItem(counterItem);
|
||||
signatureBuf.AppendSECItem(signatureItem);
|
||||
|
||||
WebAuthnGetAssertionResult result(keyHandle, nsTArray<uint8_t>(signatureBuf));
|
||||
nsTArray<uint8_t> signature(signatureBuf);
|
||||
nsTArray<WebAuthnExtensionResult> extensions;
|
||||
|
||||
if (chosenAppId != aApplication) {
|
||||
// Indicate to the RP that we used the FIDO appId.
|
||||
extensions.AppendElement(WebAuthnExtensionResultAppId(true));
|
||||
}
|
||||
|
||||
WebAuthnGetAssertionResult result(chosenAppId, keyHandle, signature, extensions);
|
||||
return U2FSignPromise::CreateAndResolve(Move(result), __func__);
|
||||
}
|
||||
|
||||
|
|
|
@ -34,6 +34,7 @@ public:
|
|||
Sign(const nsTArray<WebAuthnScopedCredential>& aCredentials,
|
||||
const nsTArray<uint8_t>& aApplication,
|
||||
const nsTArray<uint8_t>& aChallenge,
|
||||
const nsTArray<WebAuthnExtension>& aExtensions,
|
||||
bool aRequireUserVerification,
|
||||
uint32_t aTimeoutMS) override;
|
||||
|
||||
|
@ -47,6 +48,12 @@ private:
|
|||
const nsTArray<uint8_t>& aAppParam,
|
||||
bool& aResult);
|
||||
|
||||
bool
|
||||
FindRegisteredKeyHandle(const nsTArray<nsTArray<uint8_t>>& aAppIds,
|
||||
const nsTArray<WebAuthnScopedCredential>& aCredentials,
|
||||
/*out*/ nsTArray<uint8_t>& aKeyHandle,
|
||||
/*out*/ nsTArray<uint8_t>& aAppId);
|
||||
|
||||
bool mInitialized;
|
||||
mozilla::UniquePK11SymKey mWrappingKey;
|
||||
|
||||
|
|
|
@ -322,6 +322,7 @@ U2FTokenManager::Sign(PWebAuthnTransactionParent* aTransactionParent,
|
|||
mTokenManagerImpl->Sign(aTransactionInfo.AllowList(),
|
||||
aTransactionInfo.RpIdHash(),
|
||||
aTransactionInfo.ClientDataHash(),
|
||||
aTransactionInfo.Extensions(),
|
||||
aTransactionInfo.RequireUserVerification(),
|
||||
aTransactionInfo.TimeoutMS())
|
||||
->Then(GetCurrentThreadSerialEventTarget(), __func__,
|
||||
|
|
|
@ -38,6 +38,7 @@ public:
|
|||
Sign(const nsTArray<WebAuthnScopedCredential>& aCredentials,
|
||||
const nsTArray<uint8_t>& aApplication,
|
||||
const nsTArray<uint8_t>& aChallenge,
|
||||
const nsTArray<WebAuthnExtension>& aExtensions,
|
||||
bool aRequireUserVerification,
|
||||
uint32_t aTimeoutMS) = 0;
|
||||
|
||||
|
|
|
@ -49,8 +49,11 @@ NS_IMPL_ISUPPORTS(WebAuthnManager, nsIDOMEventListener);
|
|||
**********************************************************************/
|
||||
|
||||
static nsresult
|
||||
AssembleClientData(const nsAString& aOrigin, const CryptoBuffer& aChallenge,
|
||||
const nsAString& aType, /* out */ nsACString& aJsonOut)
|
||||
AssembleClientData(const nsAString& aOrigin,
|
||||
const CryptoBuffer& aChallenge,
|
||||
const nsAString& aType,
|
||||
const AuthenticationExtensionsClientInputs& aExtensions,
|
||||
/* out */ nsACString& aJsonOut)
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
|
||||
|
@ -65,6 +68,7 @@ AssembleClientData(const nsAString& aOrigin, const CryptoBuffer& aChallenge,
|
|||
clientDataObject.mChallenge.Assign(challengeBase64);
|
||||
clientDataObject.mOrigin.Assign(aOrigin);
|
||||
clientDataObject.mHashAlgorithm.AssignLiteral(u"SHA-256");
|
||||
clientDataObject.mClientExtensions = aExtensions;
|
||||
|
||||
nsAutoString temp;
|
||||
if (NS_WARN_IF(!clientDataObject.ToJSON(temp))) {
|
||||
|
@ -264,6 +268,12 @@ WebAuthnManager::MakeCredential(const PublicKeyCredentialCreationOptions& aOptio
|
|||
}
|
||||
}
|
||||
|
||||
// <https://w3c.github.io/webauthn/#sctn-appid-extension>
|
||||
if (aOptions.mExtensions.mAppid.WasPassed()) {
|
||||
promise->MaybeReject(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
|
||||
return promise.forget();
|
||||
}
|
||||
|
||||
CryptoBuffer rpIdHash;
|
||||
if (!rpIdHash.SetLength(SHA256_LENGTH, fallible)) {
|
||||
promise->MaybeReject(NS_ERROR_OUT_OF_MEMORY);
|
||||
|
@ -345,7 +355,8 @@ WebAuthnManager::MakeCredential(const PublicKeyCredentialCreationOptions& aOptio
|
|||
|
||||
nsAutoCString clientDataJSON;
|
||||
srv = AssembleClientData(origin, challenge,
|
||||
NS_LITERAL_STRING("webauthn.create"), clientDataJSON);
|
||||
NS_LITERAL_STRING("webauthn.create"),
|
||||
aOptions.mExtensions, clientDataJSON);
|
||||
if (NS_WARN_IF(NS_FAILED(srv))) {
|
||||
promise->MaybeReject(NS_ERROR_DOM_SECURITY_ERR);
|
||||
return promise.forget();
|
||||
|
@ -537,7 +548,7 @@ WebAuthnManager::GetAssertion(const PublicKeyCredentialRequestOptions& aOptions,
|
|||
|
||||
nsAutoCString clientDataJSON;
|
||||
srv = AssembleClientData(origin, challenge, NS_LITERAL_STRING("webauthn.get"),
|
||||
clientDataJSON);
|
||||
aOptions.mExtensions, clientDataJSON);
|
||||
if (NS_WARN_IF(NS_FAILED(srv))) {
|
||||
promise->MaybeReject(NS_ERROR_DOM_SECURITY_ERR);
|
||||
return promise.forget();
|
||||
|
@ -593,14 +604,40 @@ WebAuthnManager::GetAssertion(const PublicKeyCredentialRequestOptions& aOptions,
|
|||
bool requireUserVerification =
|
||||
aOptions.mUserVerification == UserVerificationRequirement::Required;
|
||||
|
||||
// TODO: Add extension list building
|
||||
// If extensions was specified, process any extensions supported by this
|
||||
// If extensions were specified, process any extensions supported by this
|
||||
// client platform, to produce the extension data that needs to be sent to the
|
||||
// authenticator. If an error is encountered while processing an extension,
|
||||
// skip that extension and do not produce any extension data for it. Call the
|
||||
// result of this processing clientExtensions.
|
||||
nsTArray<WebAuthnExtension> extensions;
|
||||
|
||||
// <https://w3c.github.io/webauthn/#sctn-appid-extension>
|
||||
if (aOptions.mExtensions.mAppid.WasPassed()) {
|
||||
nsString appId(aOptions.mExtensions.mAppid.Value());
|
||||
|
||||
// Check that the appId value is allowed.
|
||||
if (!EvaluateAppID(mParent, origin, U2FOperation::Sign, appId)) {
|
||||
promise->MaybeReject(NS_ERROR_DOM_SECURITY_ERR);
|
||||
return promise.forget();
|
||||
}
|
||||
|
||||
CryptoBuffer appIdHash;
|
||||
if (!appIdHash.SetLength(SHA256_LENGTH, fallible)) {
|
||||
promise->MaybeReject(NS_ERROR_OUT_OF_MEMORY);
|
||||
return promise.forget();
|
||||
}
|
||||
|
||||
// We need the SHA-256 hash of the appId.
|
||||
nsresult srv = HashCString(hashService, NS_ConvertUTF16toUTF8(appId), appIdHash);
|
||||
if (NS_WARN_IF(NS_FAILED(srv))) {
|
||||
promise->MaybeReject(NS_ERROR_DOM_SECURITY_ERR);
|
||||
return promise.forget();
|
||||
}
|
||||
|
||||
// Append the hash and send it to the backend.
|
||||
extensions.AppendElement(WebAuthnExtensionAppId(appIdHash));
|
||||
}
|
||||
|
||||
WebAuthnGetAssertionInfo info(rpIdHash,
|
||||
clientDataHash,
|
||||
adjustedTimeout,
|
||||
|
@ -807,7 +844,7 @@ WebAuthnManager::FinishGetAssertion(const uint64_t& aTransactionId,
|
|||
}
|
||||
|
||||
CryptoBuffer rpIdHashBuf;
|
||||
if (!rpIdHashBuf.Assign(mTransaction.ref().mRpIdHash)) {
|
||||
if (!rpIdHashBuf.Assign(aResult.RpIdHash())) {
|
||||
RejectTransaction(NS_ERROR_OUT_OF_MEMORY);
|
||||
return;
|
||||
}
|
||||
|
@ -862,6 +899,14 @@ WebAuthnManager::FinishGetAssertion(const uint64_t& aTransactionId,
|
|||
credential->SetRawId(credentialBuf);
|
||||
credential->SetResponse(assertion);
|
||||
|
||||
// Forward client extension results.
|
||||
for (auto& ext: aResult.Extensions()) {
|
||||
if (ext.type() == WebAuthnExtensionResult::TWebAuthnExtensionResultAppId) {
|
||||
bool appid = ext.get_WebAuthnExtensionResultAppId().AppId();
|
||||
credential->SetClientExtensionResultAppId(appid);
|
||||
}
|
||||
}
|
||||
|
||||
mTransaction.ref().mPromise->MaybeResolve(credential);
|
||||
ClearTransaction();
|
||||
}
|
||||
|
|
|
@ -5,11 +5,113 @@
|
|||
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "mozilla/dom/WebAuthnUtil.h"
|
||||
#include "nsIEffectiveTLDService.h"
|
||||
#include "nsNetUtil.h"
|
||||
#include "pkixutil.h"
|
||||
|
||||
namespace mozilla {
|
||||
namespace dom {
|
||||
|
||||
// Bug #1436078 - Permit Google Accounts. Remove in Bug #1436085 in Jan 2023.
|
||||
NS_NAMED_LITERAL_STRING(kGoogleAccountsAppId1,
|
||||
"https://www.gstatic.com/securitykey/origins.json");
|
||||
NS_NAMED_LITERAL_STRING(kGoogleAccountsAppId2,
|
||||
"https://www.gstatic.com/securitykey/a/google.com/origins.json");
|
||||
|
||||
bool
|
||||
EvaluateAppID(nsPIDOMWindowInner* aParent, const nsString& aOrigin,
|
||||
const U2FOperation& aOp, /* in/out */ nsString& aAppId)
|
||||
{
|
||||
// Facet is the specification's way of referring to the web origin.
|
||||
nsAutoCString facetString = NS_ConvertUTF16toUTF8(aOrigin);
|
||||
nsCOMPtr<nsIURI> facetUri;
|
||||
if (NS_FAILED(NS_NewURI(getter_AddRefs(facetUri), facetString))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// If the facetId (origin) is not HTTPS, reject
|
||||
bool facetIsHttps = false;
|
||||
if (NS_FAILED(facetUri->SchemeIs("https", &facetIsHttps)) || !facetIsHttps) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// If the appId is empty or null, overwrite it with the facetId and accept
|
||||
if (aAppId.IsEmpty() || aAppId.EqualsLiteral("null")) {
|
||||
aAppId.Assign(aOrigin);
|
||||
return true;
|
||||
}
|
||||
|
||||
// AppID is user-supplied. It's quite possible for this parse to fail.
|
||||
nsAutoCString appIdString = NS_ConvertUTF16toUTF8(aAppId);
|
||||
nsCOMPtr<nsIURI> appIdUri;
|
||||
if (NS_FAILED(NS_NewURI(getter_AddRefs(appIdUri), appIdString))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// if the appId URL is not HTTPS, reject.
|
||||
bool appIdIsHttps = false;
|
||||
if (NS_FAILED(appIdUri->SchemeIs("https", &appIdIsHttps)) || !appIdIsHttps) {
|
||||
return false;
|
||||
}
|
||||
|
||||
nsAutoCString appIdHost;
|
||||
if (NS_FAILED(appIdUri->GetAsciiHost(appIdHost))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Allow localhost.
|
||||
if (appIdHost.EqualsLiteral("localhost")) {
|
||||
nsAutoCString facetHost;
|
||||
if (NS_FAILED(facetUri->GetAsciiHost(facetHost))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (facetHost.EqualsLiteral("localhost")) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Run the HTML5 algorithm to relax the same-origin policy, copied from W3C
|
||||
// Web Authentication. See Bug 1244959 comment #8 for context on why we are
|
||||
// doing this instead of implementing the external-fetch FacetID logic.
|
||||
nsCOMPtr<nsIDocument> document = aParent->GetDoc();
|
||||
if (!document || !document->IsHTMLDocument()) {
|
||||
return false;
|
||||
}
|
||||
nsHTMLDocument* html = document->AsHTMLDocument();
|
||||
if (NS_WARN_IF(!html)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Use the base domain as the facet for evaluation. This lets this algorithm
|
||||
// relax the whole eTLD+1.
|
||||
nsCOMPtr<nsIEffectiveTLDService> tldService =
|
||||
do_GetService(NS_EFFECTIVETLDSERVICE_CONTRACTID);
|
||||
if (!tldService) {
|
||||
return false;
|
||||
}
|
||||
|
||||
nsAutoCString lowestFacetHost;
|
||||
if (NS_FAILED(tldService->GetBaseDomain(facetUri, 0, lowestFacetHost))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (html->IsRegistrableDomainSuffixOfOrEqualTo(NS_ConvertUTF8toUTF16(lowestFacetHost),
|
||||
appIdHost)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Bug #1436078 - Permit Google Accounts. Remove in Bug #1436085 in Jan 2023.
|
||||
if (aOp == U2FOperation::Sign && lowestFacetHost.EqualsLiteral("google.com") &&
|
||||
(aAppId.Equals(kGoogleAccountsAppId1) ||
|
||||
aAppId.Equals(kGoogleAccountsAppId2))) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
nsresult
|
||||
ReadToCryptoBuffer(pkix::Reader& aSrc, /* out */ CryptoBuffer& aDest,
|
||||
uint32_t aLen)
|
||||
|
|
|
@ -16,6 +16,17 @@
|
|||
|
||||
namespace mozilla {
|
||||
namespace dom {
|
||||
|
||||
enum class U2FOperation
|
||||
{
|
||||
Register,
|
||||
Sign
|
||||
};
|
||||
|
||||
bool
|
||||
EvaluateAppID(nsPIDOMWindowInner* aParent, const nsString& aOrigin,
|
||||
const U2FOperation& aOp, /* in/out */ nsString& aAppId);
|
||||
|
||||
nsresult
|
||||
AssembleAuthenticatorData(const CryptoBuffer& rpIdHashBuf,
|
||||
const uint8_t flags,
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
[DEFAULT]
|
||||
support-files =
|
||||
head.js
|
||||
tab_webauthn_result.html
|
||||
tab_webauthn_success.html
|
||||
../cbor/*
|
||||
|
@ -8,4 +9,5 @@ support-files =
|
|||
skip-if = !e10s
|
||||
|
||||
[browser_abort_visibility.js]
|
||||
[browser_fido_appid_extension.js]
|
||||
[browser_webauthn_telemetry.js]
|
||||
|
|
|
@ -0,0 +1,153 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
"use strict";
|
||||
|
||||
const TEST_URL = "https://example.com/";
|
||||
|
||||
function arrivingHereIsBad(aResult) {
|
||||
ok(false, "Bad result! Received a: " + aResult);
|
||||
}
|
||||
|
||||
function expectError(aType) {
|
||||
let expected = `${aType}Error`;
|
||||
return function (aResult) {
|
||||
is(aResult.slice(0, expected.length), expected, `Expecting a ${aType}Error`);
|
||||
};
|
||||
}
|
||||
|
||||
let expectNotSupportedError = expectError("NotSupported");
|
||||
let expectNotAllowedError = expectError("NotAllowed");
|
||||
let expectSecurityError = expectError("Security");
|
||||
|
||||
function promiseU2FRegister(tab, app_id) {
|
||||
let challenge = crypto.getRandomValues(new Uint8Array(16));
|
||||
challenge = bytesToBase64UrlSafe(challenge);
|
||||
|
||||
return ContentTask.spawn(tab.linkedBrowser, [app_id, challenge], function ([app_id, challenge]) {
|
||||
return new Promise(resolve => {
|
||||
content.u2f.register(app_id, [{version: "U2F_V2", challenge}], [], resolve);
|
||||
});
|
||||
}).then(res => {
|
||||
is(res.errorCode, 0, "u2f.register() succeeded");
|
||||
let data = base64ToBytesUrlSafe(res.registrationData);
|
||||
is(data[0], 0x05, "Reserved byte is correct");
|
||||
return data.slice(67, 67 + data[66]);
|
||||
});
|
||||
}
|
||||
|
||||
function promiseWebAuthnRegister(tab, appid) {
|
||||
return ContentTask.spawn(tab.linkedBrowser, [appid], ([appid]) => {
|
||||
const cose_alg_ECDSA_w_SHA256 = -7;
|
||||
|
||||
let challenge = content.crypto.getRandomValues(new Uint8Array(16));
|
||||
|
||||
let pubKeyCredParams = [{
|
||||
type: "public-key",
|
||||
alg: cose_alg_ECDSA_w_SHA256
|
||||
}];
|
||||
|
||||
let publicKey = {
|
||||
rp: {id: content.document.domain, name: "none", icon: "none"},
|
||||
user: {id: new Uint8Array(), name: "none", icon: "none", displayName: "none"},
|
||||
pubKeyCredParams,
|
||||
extensions: {appid},
|
||||
challenge
|
||||
};
|
||||
|
||||
return content.navigator.credentials.create({publicKey})
|
||||
.then(res => res.rawId);
|
||||
});
|
||||
}
|
||||
|
||||
function promiseWebAuthnSign(tab, key_handle, extensions = {}) {
|
||||
return ContentTask.spawn(tab.linkedBrowser, [key_handle, extensions],
|
||||
([key_handle, extensions]) => {
|
||||
let challenge = content.crypto.getRandomValues(new Uint8Array(16));
|
||||
|
||||
let credential = {
|
||||
id: key_handle,
|
||||
type: "public-key",
|
||||
transports: ["usb"]
|
||||
};
|
||||
|
||||
let publicKey = {
|
||||
challenge,
|
||||
extensions,
|
||||
rpId: content.document.domain,
|
||||
allowCredentials: [credential],
|
||||
};
|
||||
|
||||
return content.navigator.credentials.get({publicKey})
|
||||
.then(credential => {
|
||||
return {
|
||||
authenticatorData: credential.response.authenticatorData,
|
||||
clientDataJSON: credential.response.clientDataJSON,
|
||||
extensions: credential.getClientExtensionResults()
|
||||
};
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
add_task(function test_setup() {
|
||||
Services.prefs.setBoolPref("security.webauth.u2f", true);
|
||||
Services.prefs.setBoolPref("security.webauth.webauthn", true);
|
||||
Services.prefs.setBoolPref("security.webauth.webauthn_enable_softtoken", true);
|
||||
Services.prefs.setBoolPref("security.webauth.webauthn_enable_usbtoken", false);
|
||||
});
|
||||
|
||||
add_task(async function test_appid() {
|
||||
// Open a new tab.
|
||||
let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, TEST_URL);
|
||||
|
||||
// Get a keyHandle for a FIDO AppId.
|
||||
let appid = "https://example.com/appId";
|
||||
let keyHandle = await promiseU2FRegister(tab, appid);
|
||||
|
||||
// The FIDO AppId extension can't be used for MakeCredential.
|
||||
await promiseWebAuthnRegister(tab, appid)
|
||||
.then(arrivingHereIsBad)
|
||||
.catch(expectNotSupportedError);
|
||||
|
||||
// Using the keyHandle shouldn't work without the FIDO AppId extension.
|
||||
await promiseWebAuthnSign(tab, keyHandle)
|
||||
.then(arrivingHereIsBad)
|
||||
.catch(expectNotAllowedError);
|
||||
|
||||
// Invalid app IDs (for the current origin) must be rejected.
|
||||
await promiseWebAuthnSign(tab, keyHandle, {appid: "https://bogus.com/appId"})
|
||||
.then(arrivingHereIsBad)
|
||||
.catch(expectSecurityError);
|
||||
|
||||
// Non-matching app IDs must be rejected.
|
||||
await promiseWebAuthnSign(tab, keyHandle, {appid: appid + "2"})
|
||||
.then(arrivingHereIsBad)
|
||||
.catch(expectNotAllowedError);
|
||||
|
||||
let rpId = new TextEncoder("utf-8").encode(appid);
|
||||
let rpIdHash = await crypto.subtle.digest("SHA-256", rpId);
|
||||
|
||||
// Succeed with the right fallback rpId.
|
||||
await promiseWebAuthnSign(tab, keyHandle, {appid})
|
||||
.then(({authenticatorData, clientDataJSON, extensions}) => {
|
||||
is(extensions.appid, true, "appid extension was acted upon");
|
||||
|
||||
// Check that the correct rpIdHash is returned.
|
||||
let rpIdHashSign = authenticatorData.slice(0, 32);
|
||||
ok(memcmp(rpIdHash, rpIdHashSign), "rpIdHash is correct");
|
||||
|
||||
let clientData = JSON.parse(buffer2string(clientDataJSON));
|
||||
is(clientData.clientExtensions.appid, appid, "appid extension sent");
|
||||
});
|
||||
|
||||
// Close tab.
|
||||
await BrowserTestUtils.removeTab(tab);
|
||||
});
|
||||
|
||||
add_task(function test_cleanup() {
|
||||
Services.prefs.clearUserPref("security.webauth.u2f");
|
||||
Services.prefs.clearUserPref("security.webauth.webauthn");
|
||||
Services.prefs.clearUserPref("security.webauth.webauthn_enable_softtoken");
|
||||
Services.prefs.clearUserPref("security.webauth.webauthn_enable_usbtoken");
|
||||
});
|
|
@ -0,0 +1,65 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
"use strict";
|
||||
|
||||
function bytesToBase64(u8a){
|
||||
let CHUNK_SZ = 0x8000;
|
||||
let c = [];
|
||||
for (let 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 bytesToBase64UrlSafe(buf) {
|
||||
return bytesToBase64(buf)
|
||||
.replace(/\+/g, "-")
|
||||
.replace(/\//g, "_")
|
||||
.replace(/=/g, "");
|
||||
}
|
||||
|
||||
function base64ToBytes(b64encoded) {
|
||||
return new Uint8Array(window.atob(b64encoded).split("").map(function(c) {
|
||||
return c.charCodeAt(0);
|
||||
}));
|
||||
}
|
||||
|
||||
function base64ToBytesUrlSafe(str) {
|
||||
if (!str || 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 buffer2string(buf) {
|
||||
let str = "";
|
||||
if (!(buf.constructor === Uint8Array)) {
|
||||
buf = new Uint8Array(buf);
|
||||
}
|
||||
buf.map(function(x){ return str += String.fromCharCode(x) });
|
||||
return str;
|
||||
}
|
||||
|
||||
function memcmp(x, y) {
|
||||
let xb = new Uint8Array(x);
|
||||
let yb = new Uint8Array(y);
|
||||
|
||||
if (x.byteLength != y.byteLength) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (let i = 0; i < xb.byteLength; ++i) {
|
||||
if (xb[i] != yb[i]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
|
@ -68,6 +68,10 @@ function() {
|
|||
is(clientData.hashAlgorithm, "SHA-256", "Hash algorithm is correct");
|
||||
is(clientData.type, "webauthn.create", "Type is correct");
|
||||
|
||||
let extensions = aCredInfo.getClientExtensionResults();
|
||||
is(extensions.appid, undefined, "appid extension wasn't used");
|
||||
is(clientData.clientExtensions.appid, undefined, "appid extension wasn't sent");
|
||||
|
||||
return webAuthnDecodeCBORAttestation(aCredInfo.response.attestationObject)
|
||||
.then(function(aAttestationObj) {
|
||||
// Make sure the RP ID hash matches what we calculate.
|
||||
|
|
|
@ -82,7 +82,7 @@ fn main() {
|
|||
flags,
|
||||
15_000,
|
||||
chall_bytes,
|
||||
app_bytes,
|
||||
vec![app_bytes],
|
||||
vec![key_handle],
|
||||
move |rv| { tx.send(rv.unwrap()).unwrap(); },
|
||||
)
|
||||
|
|
|
@ -9,6 +9,7 @@ use std::{ptr, slice};
|
|||
|
||||
use U2FManager;
|
||||
|
||||
type U2FAppIds = Vec<::AppId>;
|
||||
type U2FKeyHandles = Vec<::KeyHandle>;
|
||||
type U2FResult = HashMap<u8, Vec<u8>>;
|
||||
type U2FCallback = extern "C" fn(u64, *mut U2FResult);
|
||||
|
@ -16,6 +17,7 @@ type U2FCallback = extern "C" fn(u64, *mut U2FResult);
|
|||
const RESBUF_ID_REGISTRATION: u8 = 0;
|
||||
const RESBUF_ID_KEYHANDLE: u8 = 1;
|
||||
const RESBUF_ID_SIGNATURE: u8 = 2;
|
||||
const RESBUF_ID_APPID: u8 = 3;
|
||||
|
||||
// Generates a new 64-bit transaction id with collision probability 2^-32.
|
||||
fn new_tid() -> u64 {
|
||||
|
@ -42,6 +44,27 @@ pub unsafe extern "C" fn rust_u2f_mgr_free(mgr: *mut U2FManager) {
|
|||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn rust_u2f_app_ids_new() -> *mut U2FAppIds {
|
||||
Box::into_raw(Box::new(vec![]))
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn rust_u2f_app_ids_add(
|
||||
ids: *mut U2FAppIds,
|
||||
id_ptr: *const u8,
|
||||
id_len: usize
|
||||
) {
|
||||
(*ids).push(from_raw(id_ptr, id_len));
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn rust_u2f_app_ids_free(ids: *mut U2FAppIds) {
|
||||
if !ids.is_null() {
|
||||
Box::from_raw(ids);
|
||||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn rust_u2f_khs_new() -> *mut U2FKeyHandles {
|
||||
Box::into_raw(Box::new(vec![]))
|
||||
|
@ -165,8 +188,7 @@ pub unsafe extern "C" fn rust_u2f_mgr_sign(
|
|||
callback: U2FCallback,
|
||||
challenge_ptr: *const u8,
|
||||
challenge_len: usize,
|
||||
application_ptr: *const u8,
|
||||
application_len: usize,
|
||||
app_ids: *const U2FAppIds,
|
||||
khs: *const U2FKeyHandles,
|
||||
) -> u64 {
|
||||
if mgr.is_null() || khs.is_null() {
|
||||
|
@ -174,13 +196,18 @@ pub unsafe extern "C" fn rust_u2f_mgr_sign(
|
|||
}
|
||||
|
||||
// Check buffers.
|
||||
if challenge_ptr.is_null() || application_ptr.is_null() {
|
||||
if challenge_ptr.is_null() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Need at least one app_id.
|
||||
if (*app_ids).len() < 1 {
|
||||
return 0;
|
||||
}
|
||||
|
||||
let flags = ::SignFlags::from_bits_truncate(flags);
|
||||
let challenge = from_raw(challenge_ptr, challenge_len);
|
||||
let application = from_raw(application_ptr, application_len);
|
||||
let app_ids = (*app_ids).clone();
|
||||
let key_handles = (*khs).clone();
|
||||
|
||||
let tid = new_tid();
|
||||
|
@ -188,13 +215,14 @@ pub unsafe extern "C" fn rust_u2f_mgr_sign(
|
|||
flags,
|
||||
timeout,
|
||||
challenge,
|
||||
application,
|
||||
app_ids,
|
||||
key_handles,
|
||||
move |rv| {
|
||||
if let Ok((key_handle, signature)) = rv {
|
||||
if let Ok((app_id, key_handle, signature)) = rv {
|
||||
let mut result = U2FResult::new();
|
||||
result.insert(RESBUF_ID_KEYHANDLE, key_handle);
|
||||
result.insert(RESBUF_ID_SIGNATURE, signature);
|
||||
result.insert(RESBUF_ID_APPID, app_id);
|
||||
callback(tid, Box::into_raw(Box::new(result)));
|
||||
} else {
|
||||
callback(tid, ptr::null_mut());
|
||||
|
|
|
@ -75,6 +75,10 @@ pub struct KeyHandle {
|
|||
pub transports: AuthenticatorTransports,
|
||||
}
|
||||
|
||||
pub type AppId = Vec<u8>;
|
||||
pub type RegisterResult = Vec<u8>;
|
||||
pub type SignResult = (AppId, Vec<u8>, Vec<u8>);
|
||||
|
||||
#[cfg(fuzzing)]
|
||||
pub use u2fprotocol::*;
|
||||
#[cfg(fuzzing)]
|
||||
|
|
|
@ -16,17 +16,17 @@ enum QueueAction {
|
|||
flags: ::RegisterFlags,
|
||||
timeout: u64,
|
||||
challenge: Vec<u8>,
|
||||
application: Vec<u8>,
|
||||
application: ::AppId,
|
||||
key_handles: Vec<::KeyHandle>,
|
||||
callback: OnceCallback<Vec<u8>>,
|
||||
callback: OnceCallback<::RegisterResult>,
|
||||
},
|
||||
Sign {
|
||||
flags: ::SignFlags,
|
||||
timeout: u64,
|
||||
challenge: Vec<u8>,
|
||||
application: Vec<u8>,
|
||||
app_ids: Vec<::AppId>,
|
||||
key_handles: Vec<::KeyHandle>,
|
||||
callback: OnceCallback<(Vec<u8>, Vec<u8>)>,
|
||||
callback: OnceCallback<::SignResult>,
|
||||
},
|
||||
Cancel,
|
||||
}
|
||||
|
@ -68,7 +68,7 @@ impl U2FManager {
|
|||
flags,
|
||||
timeout,
|
||||
challenge,
|
||||
application,
|
||||
app_ids,
|
||||
key_handles,
|
||||
callback,
|
||||
}) => {
|
||||
|
@ -77,7 +77,7 @@ impl U2FManager {
|
|||
flags,
|
||||
timeout,
|
||||
challenge,
|
||||
application,
|
||||
app_ids,
|
||||
key_handles,
|
||||
callback,
|
||||
);
|
||||
|
@ -109,12 +109,12 @@ impl U2FManager {
|
|||
flags: ::RegisterFlags,
|
||||
timeout: u64,
|
||||
challenge: Vec<u8>,
|
||||
application: Vec<u8>,
|
||||
application: ::AppId,
|
||||
key_handles: Vec<::KeyHandle>,
|
||||
callback: F,
|
||||
) -> io::Result<()>
|
||||
where
|
||||
F: FnOnce(io::Result<Vec<u8>>),
|
||||
F: FnOnce(io::Result<::RegisterResult>),
|
||||
F: Send + 'static,
|
||||
{
|
||||
if challenge.len() != PARAMETER_SIZE || application.len() != PARAMETER_SIZE {
|
||||
|
@ -150,21 +150,37 @@ impl U2FManager {
|
|||
flags: ::SignFlags,
|
||||
timeout: u64,
|
||||
challenge: Vec<u8>,
|
||||
application: Vec<u8>,
|
||||
app_ids: Vec<::AppId>,
|
||||
key_handles: Vec<::KeyHandle>,
|
||||
callback: F,
|
||||
) -> io::Result<()>
|
||||
where
|
||||
F: FnOnce(io::Result<(Vec<u8>, Vec<u8>)>),
|
||||
F: FnOnce(io::Result<::SignResult>),
|
||||
F: Send + 'static,
|
||||
{
|
||||
if challenge.len() != PARAMETER_SIZE || application.len() != PARAMETER_SIZE {
|
||||
if challenge.len() != PARAMETER_SIZE {
|
||||
return Err(io::Error::new(
|
||||
io::ErrorKind::InvalidInput,
|
||||
"Invalid parameter sizes",
|
||||
));
|
||||
}
|
||||
|
||||
if app_ids.len() < 1 {
|
||||
return Err(io::Error::new(
|
||||
io::ErrorKind::InvalidInput,
|
||||
"No app IDs given",
|
||||
));
|
||||
}
|
||||
|
||||
for app_id in &app_ids {
|
||||
if app_id.len() != PARAMETER_SIZE {
|
||||
return Err(io::Error::new(
|
||||
io::ErrorKind::InvalidInput,
|
||||
"Invalid app_id size",
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
for key_handle in &key_handles {
|
||||
if key_handle.credential.len() > 256 {
|
||||
return Err(io::Error::new(
|
||||
|
@ -179,7 +195,7 @@ impl U2FManager {
|
|||
flags,
|
||||
timeout,
|
||||
challenge,
|
||||
application,
|
||||
app_ids,
|
||||
key_handles,
|
||||
callback,
|
||||
};
|
||||
|
|
|
@ -14,6 +14,31 @@ fn is_valid_transport(transports: ::AuthenticatorTransports) -> bool {
|
|||
transports.is_empty() || transports.contains(::AuthenticatorTransports::USB)
|
||||
}
|
||||
|
||||
fn find_valid_key_handles<'a, F>(
|
||||
app_ids: &'a Vec<::AppId>,
|
||||
key_handles: &'a Vec<::KeyHandle>,
|
||||
mut is_valid: F,
|
||||
) -> (&'a ::AppId, Vec<&'a ::KeyHandle>)
|
||||
where
|
||||
F: FnMut(&Vec<u8>, &::KeyHandle) -> bool,
|
||||
{
|
||||
// Try all given app_ids in order.
|
||||
for app_id in app_ids {
|
||||
// Find all valid key handles for the current app_id.
|
||||
let valid_handles = key_handles
|
||||
.iter()
|
||||
.filter(|key_handle| is_valid(app_id, key_handle))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
// If there's at least one, stop.
|
||||
if valid_handles.len() > 0 {
|
||||
return (app_id, valid_handles);
|
||||
}
|
||||
}
|
||||
|
||||
return (&app_ids[0], vec![]);
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct StateMachine {
|
||||
transaction: Option<Transaction>,
|
||||
|
@ -29,9 +54,9 @@ impl StateMachine {
|
|||
flags: ::RegisterFlags,
|
||||
timeout: u64,
|
||||
challenge: Vec<u8>,
|
||||
application: Vec<u8>,
|
||||
application: ::AppId,
|
||||
key_handles: Vec<::KeyHandle>,
|
||||
callback: OnceCallback<Vec<u8>>,
|
||||
callback: OnceCallback<::RegisterResult>,
|
||||
) {
|
||||
// Abort any prior register/sign calls.
|
||||
self.cancel();
|
||||
|
@ -93,9 +118,9 @@ impl StateMachine {
|
|||
flags: ::SignFlags,
|
||||
timeout: u64,
|
||||
challenge: Vec<u8>,
|
||||
application: Vec<u8>,
|
||||
app_ids: Vec<::AppId>,
|
||||
key_handles: Vec<::KeyHandle>,
|
||||
callback: OnceCallback<(Vec<u8>, Vec<u8>)>,
|
||||
callback: OnceCallback<::SignResult>,
|
||||
) {
|
||||
// Abort any prior register/sign calls.
|
||||
self.cancel();
|
||||
|
@ -125,14 +150,15 @@ impl StateMachine {
|
|||
return;
|
||||
}
|
||||
|
||||
// Find all matching key handles.
|
||||
let key_handles = key_handles
|
||||
.iter()
|
||||
.filter(|key_handle| {
|
||||
u2f_is_keyhandle_valid(dev, &challenge, &application, &key_handle.credential)
|
||||
.unwrap_or(false) /* no match on failure */
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
// For each appId, try all key handles. If there's at least one
|
||||
// valid key handle for an appId, we'll use that appId below.
|
||||
let (app_id, valid_handles) =
|
||||
find_valid_key_handles(&app_ids, &key_handles,
|
||||
|app_id, key_handle| {
|
||||
u2f_is_keyhandle_valid(dev, &challenge, app_id,
|
||||
&key_handle.credential)
|
||||
.unwrap_or(false) /* no match on failure */
|
||||
});
|
||||
|
||||
// Aggregate distinct transports from all given credentials.
|
||||
let transports = key_handles.iter().fold(
|
||||
|
@ -149,7 +175,7 @@ impl StateMachine {
|
|||
while alive() {
|
||||
// If the device matches none of the given key handles
|
||||
// then just make it blink with bogus data.
|
||||
if key_handles.is_empty() {
|
||||
if valid_handles.is_empty() {
|
||||
let blank = vec![0u8; PARAMETER_SIZE];
|
||||
if let Ok(_) = u2f_register(dev, &blank, &blank) {
|
||||
callback.call(Err(io_err("invalid key")));
|
||||
|
@ -157,15 +183,17 @@ impl StateMachine {
|
|||
}
|
||||
} else {
|
||||
// Otherwise, try to sign.
|
||||
for key_handle in &key_handles {
|
||||
for key_handle in &valid_handles {
|
||||
if let Ok(bytes) = u2f_sign(
|
||||
dev,
|
||||
&challenge,
|
||||
&application,
|
||||
app_id,
|
||||
&key_handle.credential,
|
||||
)
|
||||
{
|
||||
callback.call(Ok((key_handle.credential.clone(), bytes)));
|
||||
callback.call(Ok((app_id.clone(),
|
||||
key_handle.credential.clone(),
|
||||
bytes)));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,6 +14,7 @@ extern "C" {
|
|||
const uint8_t U2F_RESBUF_ID_REGISTRATION = 0;
|
||||
const uint8_t U2F_RESBUF_ID_KEYHANDLE = 1;
|
||||
const uint8_t U2F_RESBUF_ID_SIGNATURE = 2;
|
||||
const uint8_t U2F_RESBUF_ID_APPID = 3;
|
||||
|
||||
const uint64_t U2F_FLAG_REQUIRE_RESIDENT_KEY = 1;
|
||||
const uint64_t U2F_FLAG_REQUIRE_USER_VERIFICATION = 2;
|
||||
|
@ -34,6 +35,9 @@ const uint8_t U2F_AUTHENTICATOR_TRANSPORT_BLE = 4;
|
|||
// The `rust_u2f_mgr` opaque type is equivalent to the rust type `U2FManager`
|
||||
struct rust_u2f_manager;
|
||||
|
||||
// The `rust_u2f_app_ids` opaque type is equivalent to the rust type `U2FAppIds`
|
||||
struct rust_u2f_app_ids;
|
||||
|
||||
// The `rust_u2f_key_handles` opaque type is equivalent to the rust type `U2FKeyHandles`
|
||||
struct rust_u2f_key_handles;
|
||||
|
||||
|
@ -65,13 +69,21 @@ uint64_t rust_u2f_mgr_sign(rust_u2f_manager* mgr,
|
|||
rust_u2f_callback,
|
||||
const uint8_t* challenge_ptr,
|
||||
size_t challenge_len,
|
||||
const uint8_t* application_ptr,
|
||||
size_t application_len,
|
||||
const rust_u2f_app_ids* app_ids,
|
||||
const rust_u2f_key_handles* khs);
|
||||
|
||||
uint64_t rust_u2f_mgr_cancel(rust_u2f_manager* mgr);
|
||||
|
||||
|
||||
/// U2FAppIds functions.
|
||||
|
||||
rust_u2f_app_ids* rust_u2f_app_ids_new();
|
||||
void rust_u2f_app_ids_add(rust_u2f_app_ids* ids,
|
||||
const uint8_t* id,
|
||||
size_t id_len);
|
||||
/* unsafe */ void rust_u2f_app_ids_free(rust_u2f_app_ids* ids);
|
||||
|
||||
|
||||
/// U2FKeyHandles functions.
|
||||
|
||||
rust_u2f_key_handles* rust_u2f_khs_new();
|
||||
|
|
|
@ -21,6 +21,8 @@ interface CredentialsContainer {
|
|||
Promise<Credential?> create(optional CredentialCreationOptions options);
|
||||
[Throws]
|
||||
Promise<Credential> store(Credential credential);
|
||||
[Throws]
|
||||
Promise<void> preventSilentAccess();
|
||||
};
|
||||
|
||||
dictionary CredentialRequestOptions {
|
||||
|
|
|
@ -13,8 +13,7 @@
|
|||
interface PublicKeyCredential : Credential {
|
||||
[SameObject] readonly attribute ArrayBuffer rawId;
|
||||
[SameObject] readonly attribute AuthenticatorResponse response;
|
||||
// Extensions are not supported yet.
|
||||
// [SameObject] readonly attribute AuthenticationExtensions clientExtensionResults; // Add in Bug 1406458
|
||||
AuthenticationExtensionsClientOutputs getClientExtensionResults();
|
||||
};
|
||||
|
||||
[SecureContext]
|
||||
|
@ -104,10 +103,18 @@ dictionary PublicKeyCredentialRequestOptions {
|
|||
AuthenticationExtensionsClientInputs extensions;
|
||||
};
|
||||
|
||||
// TODO - Use partial dictionaries when bug 1436329 is fixed.
|
||||
dictionary AuthenticationExtensionsClientInputs {
|
||||
// FIDO AppID Extension (appid)
|
||||
// <https://w3c.github.io/webauthn/#sctn-appid-extension>
|
||||
USVString appid;
|
||||
};
|
||||
|
||||
// TODO - Use partial dictionaries when bug 1436329 is fixed.
|
||||
dictionary AuthenticationExtensionsClientOutputs {
|
||||
// FIDO AppID Extension (appid)
|
||||
// <https://w3c.github.io/webauthn/#sctn-appid-extension>
|
||||
boolean appid;
|
||||
};
|
||||
|
||||
typedef record<DOMString, DOMString> AuthenticationExtensionsAuthenticatorInputs;
|
||||
|
@ -144,3 +151,16 @@ typedef sequence<AAGUID> AuthenticatorSelectionList;
|
|||
|
||||
typedef BufferSource AAGUID;
|
||||
|
||||
/*
|
||||
// FIDO AppID Extension (appid)
|
||||
// <https://w3c.github.io/webauthn/#sctn-appid-extension>
|
||||
partial dictionary AuthenticationExtensionsClientInputs {
|
||||
USVString appid;
|
||||
};
|
||||
|
||||
// FIDO AppID Extension (appid)
|
||||
// <https://w3c.github.io/webauthn/#sctn-appid-extension>
|
||||
partial dictionary AuthenticationExtensionsClientOutputs {
|
||||
boolean appid;
|
||||
};
|
||||
*/
|
||||
|
|
|
@ -235,7 +235,10 @@ XMLHttpRequestMainThread::~XMLHttpRequestMainThread()
|
|||
Abort();
|
||||
}
|
||||
|
||||
mParseEndListener = nullptr;
|
||||
if (mParseEndListener) {
|
||||
mParseEndListener->SetIsStale();
|
||||
mParseEndListener = nullptr;
|
||||
}
|
||||
|
||||
MOZ_ASSERT(!mFlagSyncLooping, "we rather crash than hang");
|
||||
mFlagSyncLooping = false;
|
||||
|
|
|
@ -875,8 +875,13 @@ public:
|
|||
mXHR = nullptr;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
explicit nsXHRParseEndListener(XMLHttpRequestMainThread* aXHR)
|
||||
: mXHR(aXHR) {}
|
||||
|
||||
void SetIsStale() {
|
||||
mXHR = nullptr;
|
||||
}
|
||||
private:
|
||||
virtual ~nsXHRParseEndListener() {}
|
||||
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
var ab = new ArrayBuffer(64 * 1024);
|
||||
var arr = new Uint8Array(ab);
|
||||
|
||||
(function(glob, imp, b) {
|
||||
"use asm";
|
||||
var arr = new glob.Uint8Array(b);
|
||||
return {}
|
||||
})(this, null, ab);
|
||||
|
||||
function testSimdX4() {
|
||||
for (var i = 10; i --> 0;) {
|
||||
var caught;
|
||||
try {
|
||||
v = SIMD.Int32x4.load(arr, 65534);
|
||||
} catch (e) {
|
||||
caught = e;
|
||||
}
|
||||
assertEq(caught instanceof RangeError, true);
|
||||
}
|
||||
}
|
||||
|
||||
setJitCompilerOption('ion.warmup.trigger', 0);
|
||||
testSimdX4();
|
||||
|
|
@ -4676,7 +4676,7 @@ BaselineCompiler::emit_JSOP_RESUME()
|
|||
GeneratorObject::ResumeKind resumeKind = GeneratorObject::getResumeKind(pc);
|
||||
|
||||
frame.syncStack(0);
|
||||
masm.checkStackAlignment();
|
||||
masm.assertStackAlignment(sizeof(Value), 0);
|
||||
|
||||
AllocatableGeneralRegisterSet regs(GeneralRegisterSet::All());
|
||||
regs.take(BaselineFrameReg);
|
||||
|
@ -4790,7 +4790,7 @@ BaselineCompiler::emit_JSOP_RESUME()
|
|||
masm.push(BaselineFrameReg);
|
||||
masm.moveStackPtrTo(BaselineFrameReg);
|
||||
masm.subFromStackPtr(Imm32(BaselineFrame::Size()));
|
||||
masm.checkStackAlignment();
|
||||
masm.assertStackAlignment(sizeof(Value), 0);
|
||||
|
||||
// Store flags and env chain.
|
||||
masm.store32(Imm32(BaselineFrame::HAS_INITIAL_ENV), frame.addressOfFlags());
|
||||
|
|
|
@ -3449,7 +3449,7 @@ ICCall_ClassHook::Compiler::generateStubCode(MacroAssembler& masm)
|
|||
pushCallArguments(masm, regs, argcReg, /* isJitCall = */ false, isConstructing_);
|
||||
regs.take(scratch);
|
||||
|
||||
masm.checkStackAlignment();
|
||||
masm.assertStackAlignment(sizeof(Value), 0);
|
||||
|
||||
// Native functions have the signature:
|
||||
//
|
||||
|
|
|
@ -3833,10 +3833,12 @@ void
|
|||
CodeGenerator::visitTypeBarrierV(LTypeBarrierV* lir)
|
||||
{
|
||||
ValueOperand operand = ToValue(lir, LTypeBarrierV::Input);
|
||||
Register scratch = ToTempRegisterOrInvalid(lir->temp());
|
||||
Register unboxScratch = ToTempRegisterOrInvalid(lir->unboxTemp());
|
||||
Register objScratch = ToTempRegisterOrInvalid(lir->objTemp());
|
||||
|
||||
Label miss;
|
||||
masm.guardTypeSet(operand, lir->mir()->resultTypeSet(), lir->mir()->barrierKind(), scratch, &miss);
|
||||
masm.guardTypeSet(operand, lir->mir()->resultTypeSet(), lir->mir()->barrierKind(),
|
||||
unboxScratch, objScratch, &miss);
|
||||
bailoutFrom(&miss, lir->snapshot());
|
||||
}
|
||||
|
||||
|
@ -3869,10 +3871,12 @@ void
|
|||
CodeGenerator::visitMonitorTypes(LMonitorTypes* lir)
|
||||
{
|
||||
ValueOperand operand = ToValue(lir, LMonitorTypes::Input);
|
||||
Register scratch = ToTempUnboxRegister(lir->temp());
|
||||
Register unboxScratch = ToTempRegisterOrInvalid(lir->unboxTemp());
|
||||
Register objScratch = ToTempRegisterOrInvalid(lir->objTemp());
|
||||
|
||||
Label matched, miss;
|
||||
masm.guardTypeSet(operand, lir->mir()->typeSet(), lir->mir()->barrierKind(), scratch, &miss);
|
||||
masm.guardTypeSet(operand, lir->mir()->typeSet(), lir->mir()->barrierKind(), unboxScratch,
|
||||
objScratch, &miss);
|
||||
bailoutFrom(&miss, lir->snapshot());
|
||||
}
|
||||
|
||||
|
@ -5086,7 +5090,9 @@ CodeGenerator::generateArgumentsChecks(bool assert)
|
|||
MResumePoint* rp = mir.entryResumePoint();
|
||||
|
||||
// No registers are allocated yet, so it's safe to grab anything.
|
||||
Register temp = AllocatableGeneralRegisterSet(GeneralRegisterSet::All()).getAny();
|
||||
AllocatableGeneralRegisterSet temps(GeneralRegisterSet::All());
|
||||
Register temp1 = temps.takeAny();
|
||||
Register temp2 = temps.takeAny();
|
||||
|
||||
const CompileInfo& info = gen->info();
|
||||
|
||||
|
@ -5103,7 +5109,8 @@ CodeGenerator::generateArgumentsChecks(bool assert)
|
|||
// ... * sizeof(Value) - Scale by value size.
|
||||
// ArgToStackOffset(...) - Compute displacement within arg vector.
|
||||
int32_t offset = ArgToStackOffset((i - info.startArgSlot()) * sizeof(Value));
|
||||
masm.guardTypeSet(Address(masm.getStackPointer(), offset), types, BarrierKind::TypeSet, temp, &miss);
|
||||
Address argAddr(masm.getStackPointer(), offset);
|
||||
masm.guardTypeSet(argAddr, types, BarrierKind::TypeSet, temp1, temp2, &miss);
|
||||
}
|
||||
|
||||
if (miss.used()) {
|
||||
|
@ -5124,8 +5131,8 @@ CodeGenerator::generateArgumentsChecks(bool assert)
|
|||
Label skip;
|
||||
Address addr(masm.getStackPointer(), ArgToStackOffset((i - info.startArgSlot()) * sizeof(Value)));
|
||||
masm.branchTestObject(Assembler::NotEqual, addr, &skip);
|
||||
Register obj = masm.extractObject(addr, temp);
|
||||
masm.guardTypeSetMightBeIncomplete(types, obj, temp, &success);
|
||||
Register obj = masm.extractObject(addr, temp1);
|
||||
masm.guardTypeSetMightBeIncomplete(types, obj, temp1, &success);
|
||||
masm.bind(&skip);
|
||||
}
|
||||
|
||||
|
@ -5469,7 +5476,7 @@ CodeGenerator::emitAssertResultV(const ValueOperand input, const TemporaryTypeSe
|
|||
if (typeset && !typeset->unknown()) {
|
||||
// We have a result TypeSet, assert this value is in it.
|
||||
Label miss, ok;
|
||||
masm.guardTypeSet(input, typeset, BarrierKind::TypeSet, temp1, &miss);
|
||||
masm.guardTypeSet(input, typeset, BarrierKind::TypeSet, temp1, temp2, &miss);
|
||||
masm.jump(&ok);
|
||||
|
||||
masm.bind(&miss);
|
||||
|
|
|
@ -1439,6 +1439,22 @@ EmitCheckPropertyTypes(MacroAssembler& masm, const PropertyTypeCheckInfo* typeCh
|
|||
masm.Push(obj);
|
||||
Register scratch1 = obj;
|
||||
|
||||
// We may also need a scratch register for guardTypeSet.
|
||||
Register objScratch = InvalidReg;
|
||||
if (propTypes && !propTypes->unknownObject() && propTypes->getObjectCount() > 0) {
|
||||
AllocatableGeneralRegisterSet regs(GeneralRegisterSet::All());
|
||||
if (!val.constant()) {
|
||||
TypedOrValueRegister valReg = val.reg();
|
||||
if (valReg.hasValue())
|
||||
regs.take(valReg.valueReg());
|
||||
else if (!valReg.typedReg().isFloat())
|
||||
regs.take(valReg.typedReg().gpr());
|
||||
}
|
||||
regs.take(scratch1);
|
||||
objScratch = regs.takeAny();
|
||||
masm.Push(objScratch);
|
||||
}
|
||||
|
||||
bool checkTypeSet = true;
|
||||
Label failedFastPath;
|
||||
|
||||
|
@ -1468,7 +1484,8 @@ EmitCheckPropertyTypes(MacroAssembler& masm, const PropertyTypeCheckInfo* typeCh
|
|||
if (propTypes) {
|
||||
// guardTypeSet can read from type sets without triggering read barriers.
|
||||
TypeSet::readBarrier(propTypes);
|
||||
masm.guardTypeSet(valReg, propTypes, BarrierKind::TypeSet, scratch1, &failedFastPath);
|
||||
masm.guardTypeSet(valReg, propTypes, BarrierKind::TypeSet, scratch1, objScratch,
|
||||
&failedFastPath);
|
||||
masm.jump(&done);
|
||||
} else {
|
||||
masm.jump(&failedFastPath);
|
||||
|
@ -1511,11 +1528,15 @@ EmitCheckPropertyTypes(MacroAssembler& masm, const PropertyTypeCheckInfo* typeCh
|
|||
masm.PopRegsInMaskIgnore(save, ignore);
|
||||
|
||||
masm.branchIfTrueBool(scratch1, &done);
|
||||
if (objScratch != InvalidReg)
|
||||
masm.pop(objScratch);
|
||||
masm.pop(obj);
|
||||
masm.jump(failures);
|
||||
}
|
||||
|
||||
masm.bind(&done);
|
||||
if (objScratch != InvalidReg)
|
||||
masm.Pop(objScratch);
|
||||
masm.Pop(obj);
|
||||
}
|
||||
|
||||
|
|
|
@ -2792,7 +2792,6 @@ LIRGenerator::visitTypeBarrier(MTypeBarrier* ins)
|
|||
// from inside a type barrier test.
|
||||
|
||||
const TemporaryTypeSet* types = ins->resultTypeSet();
|
||||
bool needTemp = !types->unknownObject() && types->getObjectCount() > 0;
|
||||
|
||||
MIRType inputType = ins->getOperand(0)->type();
|
||||
MOZ_ASSERT(inputType == ins->type());
|
||||
|
@ -2807,10 +2806,13 @@ LIRGenerator::visitTypeBarrier(MTypeBarrier* ins)
|
|||
return;
|
||||
}
|
||||
|
||||
bool needObjTemp = !types->unknownObject() && types->getObjectCount() > 0;
|
||||
|
||||
// Handle typebarrier with Value as input.
|
||||
if (inputType == MIRType::Value) {
|
||||
LDefinition tmp = needTemp ? temp() : tempToUnbox();
|
||||
LTypeBarrierV* barrier = new(alloc()) LTypeBarrierV(useBox(ins->input()), tmp);
|
||||
LDefinition objTemp = needObjTemp ? temp() : LDefinition::BogusTemp();
|
||||
LTypeBarrierV* barrier = new(alloc()) LTypeBarrierV(useBox(ins->input()), tempToUnbox(),
|
||||
objTemp);
|
||||
assignSnapshot(barrier, Bailout_TypeBarrierV);
|
||||
add(barrier, ins);
|
||||
redefine(ins, ins->input());
|
||||
|
@ -2829,7 +2831,7 @@ LIRGenerator::visitTypeBarrier(MTypeBarrier* ins)
|
|||
}
|
||||
|
||||
if (needsObjectBarrier) {
|
||||
LDefinition tmp = needTemp ? temp() : LDefinition::BogusTemp();
|
||||
LDefinition tmp = needObjTemp ? temp() : LDefinition::BogusTemp();
|
||||
LTypeBarrierO* barrier = new(alloc()) LTypeBarrierO(useRegister(ins->getOperand(0)), tmp);
|
||||
assignSnapshot(barrier, Bailout_TypeBarrierO);
|
||||
add(barrier, ins);
|
||||
|
@ -2848,10 +2850,11 @@ LIRGenerator::visitMonitorTypes(MMonitorTypes* ins)
|
|||
// from inside a type check.
|
||||
|
||||
const TemporaryTypeSet* types = ins->typeSet();
|
||||
bool needTemp = !types->unknownObject() && types->getObjectCount() > 0;
|
||||
LDefinition tmp = needTemp ? temp() : tempToUnbox();
|
||||
|
||||
LMonitorTypes* lir = new(alloc()) LMonitorTypes(useBox(ins->input()), tmp);
|
||||
bool needObjTemp = !types->unknownObject() && types->getObjectCount() > 0;
|
||||
LDefinition objTemp = needObjTemp ? temp() : LDefinition::BogusTemp();
|
||||
|
||||
LMonitorTypes* lir = new(alloc()) LMonitorTypes(useBox(ins->input()), tempToUnbox(), objTemp);
|
||||
assignSnapshot(lir, Bailout_MonitorTypes);
|
||||
add(lir, ins);
|
||||
}
|
||||
|
@ -3188,8 +3191,7 @@ LIRGenerator::visitSpectreMaskIndex(MSpectreMaskIndex* ins)
|
|||
MOZ_ASSERT(ins->length()->type() == MIRType::Int32);
|
||||
MOZ_ASSERT(ins->type() == MIRType::Int32);
|
||||
|
||||
LSpectreMaskIndex* lir =
|
||||
new(alloc()) LSpectreMaskIndex(useRegister(ins->index()), useAny(ins->length()));
|
||||
auto* lir = new(alloc()) LSpectreMaskIndex(useRegister(ins->index()), useAny(ins->length()));
|
||||
define(lir, ins);
|
||||
}
|
||||
|
||||
|
|
|
@ -4306,9 +4306,9 @@ SimdTypeToArrayElementType(SimdType type)
|
|||
}
|
||||
|
||||
bool
|
||||
IonBuilder::prepareForSimdLoadStore(CallInfo& callInfo, Scalar::Type simdType, MInstruction** elements,
|
||||
MDefinition** index, Scalar::Type* arrayType,
|
||||
BoundsCheckKind boundsCheckKind)
|
||||
IonBuilder::prepareForSimdLoadStore(CallInfo& callInfo, Scalar::Type simdType,
|
||||
MInstruction** elements, MDefinition** index,
|
||||
Scalar::Type* arrayType, BoundsCheckKind boundsCheckKind)
|
||||
{
|
||||
MDefinition* array = callInfo.getArg(0);
|
||||
*index = callInfo.getArg(1);
|
||||
|
@ -4320,31 +4320,35 @@ IonBuilder::prepareForSimdLoadStore(CallInfo& callInfo, Scalar::Type simdType, M
|
|||
current->add(indexAsInt32);
|
||||
*index = indexAsInt32;
|
||||
|
||||
MDefinition* indexForBoundsCheck = *index;
|
||||
MDefinition* indexLoadEnd = *index;
|
||||
|
||||
// Artificially make sure the index is in bounds by adding the difference
|
||||
// number of slots needed (e.g. reading from Float32Array we need to make
|
||||
// sure to be in bounds for 4 slots, so add 3, etc.).
|
||||
MOZ_ASSERT(Scalar::byteSize(simdType) % Scalar::byteSize(*arrayType) == 0);
|
||||
int32_t suppSlotsNeeded = Scalar::byteSize(simdType) / Scalar::byteSize(*arrayType) - 1;
|
||||
if (suppSlotsNeeded) {
|
||||
MConstant* suppSlots = constant(Int32Value(suppSlotsNeeded));
|
||||
MAdd* addedIndex = MAdd::New(alloc(), *index, suppSlots);
|
||||
// We're fine even with the add overflows, as long as the generated code
|
||||
// for the bounds check uses an unsigned comparison.
|
||||
int32_t byteLoadSize = Scalar::byteSize(simdType) / Scalar::byteSize(*arrayType);
|
||||
if (byteLoadSize > 1) {
|
||||
// Add the number of supplementary needed slots. Overflows are fine
|
||||
// because the bounds check code uses an unsigned comparison.
|
||||
MAdd* addedIndex = MAdd::New(alloc(), *index, constant(Int32Value(byteLoadSize - 1)));
|
||||
addedIndex->setInt32Specialization();
|
||||
current->add(addedIndex);
|
||||
indexForBoundsCheck = addedIndex;
|
||||
indexLoadEnd = addedIndex;
|
||||
}
|
||||
|
||||
MInstruction* length;
|
||||
addTypedArrayLengthAndData(array, SkipBoundsCheck, index, &length, elements, boundsCheckKind);
|
||||
|
||||
// It can be that the index is out of bounds, while the added index for the
|
||||
// bounds check is in bounds, so we actually need two bounds checks here.
|
||||
// If the index+size addition overflows, then indexLoadEnd might be
|
||||
// in bounds while the actual index isn't, so we need two bounds checks
|
||||
// here.
|
||||
if (byteLoadSize > 1) {
|
||||
indexLoadEnd = addBoundsCheck(indexLoadEnd, length, BoundsCheckKind::UnusedIndex);
|
||||
auto* sub = MSub::New(alloc(), indexLoadEnd, constant(Int32Value(byteLoadSize - 1)));
|
||||
sub->setInt32Specialization();
|
||||
current->add(sub);
|
||||
*index = sub;
|
||||
}
|
||||
|
||||
*index = addBoundsCheck(*index, length, boundsCheckKind);
|
||||
|
||||
addBoundsCheck(indexForBoundsCheck, length, BoundsCheckKind::UnusedIndex);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
@ -80,8 +80,14 @@ EmitTypeCheck(MacroAssembler& masm, Assembler::Condition cond, const T& src, Typ
|
|||
|
||||
template <typename Source> void
|
||||
MacroAssembler::guardTypeSet(const Source& address, const TypeSet* types, BarrierKind kind,
|
||||
Register scratch, Label* miss)
|
||||
Register unboxScratch, Register objScratch, Label* miss)
|
||||
{
|
||||
// unboxScratch may be InvalidReg on 32-bit platforms. It should only be
|
||||
// used for extracting the Value tag or payload.
|
||||
//
|
||||
// objScratch may be InvalidReg if the TypeSet does not contain specific
|
||||
// objects to guard on. It should only be used for guardObjectType.
|
||||
|
||||
MOZ_ASSERT(kind == BarrierKind::TypeTagOnly || kind == BarrierKind::TypeSet);
|
||||
MOZ_ASSERT(!types->unknown());
|
||||
|
||||
|
@ -119,7 +125,7 @@ MacroAssembler::guardTypeSet(const Source& address, const TypeSet* types, Barrie
|
|||
return;
|
||||
}
|
||||
|
||||
Register tag = extractTag(address, scratch);
|
||||
Register tag = extractTag(address, unboxScratch);
|
||||
|
||||
// Emit all typed tests.
|
||||
for (size_t i = 0; i < mozilla::ArrayLength(tests); i++) {
|
||||
|
@ -140,26 +146,24 @@ MacroAssembler::guardTypeSet(const Source& address, const TypeSet* types, Barrie
|
|||
}
|
||||
|
||||
// Test specific objects.
|
||||
MOZ_ASSERT(scratch != InvalidReg);
|
||||
MOZ_ASSERT(objScratch != InvalidReg);
|
||||
MOZ_ASSERT(objScratch != unboxScratch);
|
||||
|
||||
MOZ_ASSERT(numBranches == 1);
|
||||
branchTestObject(NotEqual, tag, miss);
|
||||
|
||||
if (kind != BarrierKind::TypeTagOnly) {
|
||||
Register obj = extractObject(address, scratch);
|
||||
guardObjectType(obj, types, scratch, miss);
|
||||
Register obj = extractObject(address, unboxScratch);
|
||||
guardObjectType(obj, types, objScratch, miss);
|
||||
} else {
|
||||
#ifdef DEBUG
|
||||
Label fail;
|
||||
Register obj = extractObject(address, scratch);
|
||||
guardObjectType(obj, types, scratch, &fail);
|
||||
Register obj = extractObject(address, unboxScratch);
|
||||
guardObjectType(obj, types, objScratch, &fail);
|
||||
jump(&matched);
|
||||
|
||||
bind(&fail);
|
||||
|
||||
if (obj == scratch)
|
||||
extractObject(address, scratch);
|
||||
guardTypeSetMightBeIncomplete(types, obj, scratch, &matched);
|
||||
|
||||
guardTypeSetMightBeIncomplete(types, obj, objScratch, &matched);
|
||||
assumeUnreachable("Unexpected object type");
|
||||
#endif
|
||||
}
|
||||
|
@ -209,6 +213,7 @@ void
|
|||
MacroAssembler::guardObjectType(Register obj, const TypeSet* types,
|
||||
Register scratch, Label* miss)
|
||||
{
|
||||
MOZ_ASSERT(obj != scratch);
|
||||
MOZ_ASSERT(!types->unknown());
|
||||
MOZ_ASSERT(!types->hasType(TypeSet::AnyObjectType()));
|
||||
MOZ_ASSERT_IF(types->getObjectCount() > 0, scratch != InvalidReg);
|
||||
|
@ -257,8 +262,6 @@ MacroAssembler::guardObjectType(Register obj, const TypeSet* types,
|
|||
if (hasObjectGroups) {
|
||||
comment("has object groups");
|
||||
|
||||
// Note: Some platforms give the same register for obj and scratch.
|
||||
// Make sure when writing to scratch, the obj register isn't used anymore!
|
||||
loadPtr(Address(obj, JSObject::offsetOfGroup()), scratch);
|
||||
|
||||
for (unsigned i = 0; i < count; i++) {
|
||||
|
@ -279,11 +282,14 @@ MacroAssembler::guardObjectType(Register obj, const TypeSet* types,
|
|||
}
|
||||
|
||||
template void MacroAssembler::guardTypeSet(const Address& address, const TypeSet* types,
|
||||
BarrierKind kind, Register scratch, Label* miss);
|
||||
BarrierKind kind, Register unboxScratch,
|
||||
Register objScratch, Label* miss);
|
||||
template void MacroAssembler::guardTypeSet(const ValueOperand& value, const TypeSet* types,
|
||||
BarrierKind kind, Register scratch, Label* miss);
|
||||
BarrierKind kind, Register unboxScratch,
|
||||
Register objScratch, Label* miss);
|
||||
template void MacroAssembler::guardTypeSet(const TypedOrValueRegister& value, const TypeSet* types,
|
||||
BarrierKind kind, Register scratch, Label* miss);
|
||||
BarrierKind kind, Register unboxScratch,
|
||||
Register objScratch, Label* miss);
|
||||
|
||||
template<typename S, typename T>
|
||||
static void
|
||||
|
|
|
@ -1933,7 +1933,8 @@ class MacroAssembler : public MacroAssemblerSpecific
|
|||
// Emits a test of a value against all types in a TypeSet. A scratch
|
||||
// register is required.
|
||||
template <typename Source>
|
||||
void guardTypeSet(const Source& address, const TypeSet* types, BarrierKind kind, Register scratch, Label* miss);
|
||||
void guardTypeSet(const Source& address, const TypeSet* types, BarrierKind kind,
|
||||
Register unboxScratch, Register objScratch, Label* miss);
|
||||
|
||||
void guardObjectType(Register obj, const TypeSet* types, Register scratch, Label* miss);
|
||||
|
||||
|
|
|
@ -833,6 +833,14 @@ class AssemblerMIPSShared : public AssemblerShared
|
|||
FCSR = 31
|
||||
};
|
||||
|
||||
enum FCSRBit {
|
||||
CauseI = 12,
|
||||
CauseU,
|
||||
CauseO,
|
||||
CauseZ,
|
||||
CauseV
|
||||
};
|
||||
|
||||
enum FloatFormat {
|
||||
SingleFloat,
|
||||
DoubleFloat
|
||||
|
|
|
@ -1481,17 +1481,19 @@ CodeGeneratorMIPSShared::visitWasmTruncateToInt32(LWasmTruncateToInt32* lir)
|
|||
|
||||
MOZ_ASSERT(fromType == MIRType::Double || fromType == MIRType::Float32);
|
||||
|
||||
auto* ool = new (alloc()) OutOfLineWasmTruncateCheck(mir, input);
|
||||
auto* ool = new (alloc()) OutOfLineWasmTruncateCheck(mir, input, output);
|
||||
addOutOfLineCode(ool, mir);
|
||||
|
||||
Label* oolEntry = ool->entry();
|
||||
if (mir->isUnsigned()) {
|
||||
if (fromType == MIRType::Double)
|
||||
masm.wasmTruncateDoubleToUInt32(input, output, oolEntry);
|
||||
masm.wasmTruncateDoubleToUInt32(input, output, mir->isSaturating(), oolEntry);
|
||||
else if (fromType == MIRType::Float32)
|
||||
masm.wasmTruncateFloat32ToUInt32(input, output, oolEntry);
|
||||
masm.wasmTruncateFloat32ToUInt32(input, output, mir->isSaturating(), oolEntry);
|
||||
else
|
||||
MOZ_CRASH("unexpected type");
|
||||
|
||||
masm.bind(ool->rejoin());
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -1508,9 +1510,15 @@ CodeGeneratorMIPSShared::visitWasmTruncateToInt32(LWasmTruncateToInt32* lir)
|
|||
void
|
||||
CodeGeneratorMIPSShared::visitOutOfLineWasmTruncateCheck(OutOfLineWasmTruncateCheck* ool)
|
||||
{
|
||||
masm.outOfLineWasmTruncateToIntCheck(ool->input(), ool->fromType(), ool->toType(),
|
||||
ool->isUnsigned(), ool->rejoin(),
|
||||
ool->bytecodeOffset());
|
||||
if(ool->toType() == MIRType::Int32)
|
||||
{
|
||||
masm.outOfLineWasmTruncateToInt32Check(ool->input(), ool->output(), ool->fromType(),
|
||||
ool->flags(), ool->rejoin(), ool->bytecodeOffset());
|
||||
} else {
|
||||
MOZ_ASSERT(ool->toType() == MIRType::Int64);
|
||||
masm.outOfLineWasmTruncateToInt64Check(ool->input(), ool->output64(), ool->fromType(),
|
||||
ool->flags(), ool->rejoin(), ool->bytecodeOffset());
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
|
|
|
@ -610,20 +610,10 @@ MacroAssembler::branchFloat(DoubleCondition cond, FloatRegister lhs, FloatRegist
|
|||
ma_bc1s(lhs, rhs, label, cond);
|
||||
}
|
||||
|
||||
void
|
||||
MacroAssembler::branchTruncateFloat32MaybeModUint32(FloatRegister src, Register dest, Label* fail)
|
||||
{
|
||||
Label test, success;
|
||||
as_truncws(ScratchFloat32Reg, src);
|
||||
as_mfc1(dest, ScratchFloat32Reg);
|
||||
|
||||
ma_b(dest, Imm32(INT32_MAX), fail, Assembler::Equal);
|
||||
}
|
||||
|
||||
void
|
||||
MacroAssembler::branchTruncateFloat32ToInt32(FloatRegister src, Register dest, Label* fail)
|
||||
{
|
||||
convertFloat32ToInt32(src, dest, fail);
|
||||
MOZ_CRASH();
|
||||
}
|
||||
|
||||
void
|
||||
|
@ -633,25 +623,10 @@ MacroAssembler::branchDouble(DoubleCondition cond, FloatRegister lhs, FloatRegis
|
|||
ma_bc1d(lhs, rhs, label, cond);
|
||||
}
|
||||
|
||||
// Convert the floating point value to an integer, if it did not fit, then it
|
||||
// was clamped to INT32_MIN/INT32_MAX, and we can test it.
|
||||
// NOTE: if the value really was supposed to be INT32_MAX / INT32_MIN then it
|
||||
// will be wrong.
|
||||
void
|
||||
MacroAssembler::branchTruncateDoubleMaybeModUint32(FloatRegister src, Register dest, Label* fail)
|
||||
{
|
||||
Label test, success;
|
||||
as_truncwd(ScratchDoubleReg, src);
|
||||
as_mfc1(dest, ScratchDoubleReg);
|
||||
|
||||
ma_b(dest, Imm32(INT32_MAX), fail, Assembler::Equal);
|
||||
ma_b(dest, Imm32(INT32_MIN), fail, Assembler::Equal);
|
||||
}
|
||||
|
||||
void
|
||||
MacroAssembler::branchTruncateDoubleToInt32(FloatRegister src, Register dest, Label* fail)
|
||||
{
|
||||
convertDoubleToInt32(src, dest, fail);
|
||||
MOZ_CRASH();
|
||||
}
|
||||
|
||||
template <typename T, typename L>
|
||||
|
|
|
@ -1110,8 +1110,12 @@ MacroAssemblerMIPSShared::ma_lis(FloatRegister dest, float value)
|
|||
{
|
||||
Imm32 imm(mozilla::BitwiseCast<uint32_t>(value));
|
||||
|
||||
ma_li(ScratchRegister, imm);
|
||||
moveToFloat32(ScratchRegister, dest);
|
||||
if(imm.value != 0) {
|
||||
ma_li(ScratchRegister, imm);
|
||||
moveToFloat32(ScratchRegister, dest);
|
||||
} else {
|
||||
moveToFloat32(zero, dest);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
|
@ -1654,7 +1658,7 @@ MacroAssembler::wasmTruncateDoubleToInt32(FloatRegister input, Register output,
|
|||
as_truncwd(ScratchFloat32Reg, input);
|
||||
as_cfc1(ScratchRegister, Assembler::FCSR);
|
||||
moveFromFloat32(ScratchFloat32Reg, output);
|
||||
ma_ext(ScratchRegister, ScratchRegister, 6, 1);
|
||||
ma_ext(ScratchRegister, ScratchRegister, Assembler::CauseV, 1);
|
||||
ma_b(ScratchRegister, Imm32(0), oolEntry, Assembler::NotEqual);
|
||||
}
|
||||
|
||||
|
@ -1666,113 +1670,198 @@ MacroAssembler::wasmTruncateFloat32ToInt32(FloatRegister input, Register output,
|
|||
as_truncws(ScratchFloat32Reg, input);
|
||||
as_cfc1(ScratchRegister, Assembler::FCSR);
|
||||
moveFromFloat32(ScratchFloat32Reg, output);
|
||||
ma_ext(ScratchRegister, ScratchRegister, 6, 1);
|
||||
ma_ext(ScratchRegister, ScratchRegister, Assembler::CauseV, 1);
|
||||
ma_b(ScratchRegister, Imm32(0), oolEntry, Assembler::NotEqual);
|
||||
}
|
||||
|
||||
void
|
||||
MacroAssembler::oolWasmTruncateCheckF32ToI32(FloatRegister input, Register, TruncFlags flags,
|
||||
wasm::BytecodeOffset off, Label* rejoin)
|
||||
MacroAssembler::oolWasmTruncateCheckF32ToI32(FloatRegister input, Register output,
|
||||
TruncFlags flags, wasm::BytecodeOffset off,
|
||||
Label* rejoin)
|
||||
{
|
||||
outOfLineWasmTruncateToIntCheck(input, MIRType::Float32, MIRType::Int32, flags & TRUNC_UNSIGNED,
|
||||
rejoin, off);
|
||||
outOfLineWasmTruncateToInt32Check(input, output, MIRType::Float32, flags, rejoin, off);
|
||||
}
|
||||
|
||||
void
|
||||
MacroAssembler::oolWasmTruncateCheckF64ToI32(FloatRegister input, Register, TruncFlags flags,
|
||||
wasm::BytecodeOffset off, Label* rejoin)
|
||||
MacroAssembler::oolWasmTruncateCheckF64ToI32(FloatRegister input, Register output,
|
||||
TruncFlags flags, wasm::BytecodeOffset off,
|
||||
Label* rejoin)
|
||||
{
|
||||
outOfLineWasmTruncateToIntCheck(input, MIRType::Double, MIRType::Int32, flags & TRUNC_UNSIGNED,
|
||||
rejoin, off);
|
||||
outOfLineWasmTruncateToInt32Check(input, output, MIRType::Double, flags, rejoin, off);
|
||||
}
|
||||
|
||||
void
|
||||
MacroAssembler::oolWasmTruncateCheckF32ToI64(FloatRegister input, Register64, TruncFlags flags,
|
||||
wasm::BytecodeOffset off, Label* rejoin)
|
||||
MacroAssembler::oolWasmTruncateCheckF32ToI64(FloatRegister input, Register64 output,
|
||||
TruncFlags flags, wasm::BytecodeOffset off,
|
||||
Label* rejoin)
|
||||
{
|
||||
outOfLineWasmTruncateToIntCheck(input, MIRType::Float32, MIRType::Int64, flags & TRUNC_UNSIGNED,
|
||||
rejoin, off);
|
||||
outOfLineWasmTruncateToInt64Check(input, output, MIRType::Float32, flags, rejoin, off);
|
||||
}
|
||||
|
||||
void
|
||||
MacroAssembler::oolWasmTruncateCheckF64ToI64(FloatRegister input, Register64, TruncFlags flags,
|
||||
wasm::BytecodeOffset off, Label* rejoin)
|
||||
MacroAssembler::oolWasmTruncateCheckF64ToI64(FloatRegister input, Register64 output,
|
||||
TruncFlags flags, wasm::BytecodeOffset off,
|
||||
Label* rejoin)
|
||||
{
|
||||
outOfLineWasmTruncateToIntCheck(input, MIRType::Double, MIRType::Int64, flags & TRUNC_UNSIGNED,
|
||||
rejoin, off);
|
||||
outOfLineWasmTruncateToInt64Check(input, output, MIRType::Double, flags, rejoin, off);
|
||||
}
|
||||
|
||||
void
|
||||
MacroAssemblerMIPSShared::outOfLineWasmTruncateToIntCheck(FloatRegister input, MIRType fromType,
|
||||
MIRType toType, bool isUnsigned,
|
||||
Label* rejoin,
|
||||
wasm::BytecodeOffset trapOffset)
|
||||
MacroAssemblerMIPSShared::outOfLineWasmTruncateToInt32Check(FloatRegister input, Register output,
|
||||
MIRType fromType, TruncFlags flags,
|
||||
Label* rejoin,
|
||||
wasm::BytecodeOffset trapOffset)
|
||||
{
|
||||
// Eagerly take care of NaNs.
|
||||
bool isUnsigned = flags & TRUNC_UNSIGNED;
|
||||
bool isSaturating = flags & TRUNC_SATURATING;
|
||||
|
||||
if(isSaturating) {
|
||||
|
||||
if(fromType == MIRType::Double)
|
||||
asMasm().loadConstantDouble(0.0, ScratchDoubleReg);
|
||||
else
|
||||
asMasm().loadConstantFloat32(0.0f, ScratchFloat32Reg);
|
||||
|
||||
if(isUnsigned) {
|
||||
|
||||
ma_li(output, Imm32(UINT32_MAX));
|
||||
|
||||
FloatTestKind moveCondition;
|
||||
compareFloatingPoint(fromType == MIRType::Double ? DoubleFloat : SingleFloat,
|
||||
input,
|
||||
fromType == MIRType::Double ? ScratchDoubleReg : ScratchFloat32Reg,
|
||||
Assembler::DoubleLessThanOrUnordered, &moveCondition);
|
||||
MOZ_ASSERT(moveCondition == TestForTrue);
|
||||
|
||||
as_movt(output, zero);
|
||||
} else {
|
||||
|
||||
// Positive overflow is already saturated to INT32_MAX, so we only have
|
||||
// to handle NaN and negative overflow here.
|
||||
|
||||
FloatTestKind moveCondition;
|
||||
compareFloatingPoint(fromType == MIRType::Double ? DoubleFloat : SingleFloat,
|
||||
input,
|
||||
input,
|
||||
Assembler::DoubleUnordered, &moveCondition);
|
||||
MOZ_ASSERT(moveCondition == TestForTrue);
|
||||
|
||||
as_movt(output, zero);
|
||||
|
||||
compareFloatingPoint(fromType == MIRType::Double ? DoubleFloat : SingleFloat,
|
||||
input,
|
||||
fromType == MIRType::Double ? ScratchDoubleReg : ScratchFloat32Reg,
|
||||
Assembler::DoubleLessThan, &moveCondition);
|
||||
MOZ_ASSERT(moveCondition == TestForTrue);
|
||||
|
||||
ma_li(ScratchRegister, Imm32(INT32_MIN));
|
||||
as_movt(output, ScratchRegister);
|
||||
}
|
||||
|
||||
MOZ_ASSERT(rejoin->bound());
|
||||
asMasm().jump(rejoin);
|
||||
return;
|
||||
}
|
||||
|
||||
Label inputIsNaN;
|
||||
|
||||
if (fromType == MIRType::Double)
|
||||
asMasm().branchDouble(Assembler::DoubleUnordered, input, input, &inputIsNaN);
|
||||
else if (fromType == MIRType::Float32)
|
||||
asMasm().branchFloat(Assembler::DoubleUnordered, input, input, &inputIsNaN);
|
||||
else
|
||||
MOZ_CRASH("unexpected type in visitOutOfLineWasmTruncateCheck");
|
||||
|
||||
// By default test for the following inputs and bail:
|
||||
// signed: ] -Inf, INTXX_MIN - 1.0 ] and [ INTXX_MAX + 1.0 : +Inf [
|
||||
// unsigned: ] -Inf, -1.0 ] and [ UINTXX_MAX + 1.0 : +Inf [
|
||||
// Note: we cannot always represent those exact values. As a result
|
||||
// this changes the actual comparison a bit.
|
||||
double minValue, maxValue;
|
||||
Assembler::DoubleCondition minCond = Assembler::DoubleLessThanOrEqual;
|
||||
Assembler::DoubleCondition maxCond = Assembler::DoubleGreaterThanOrEqual;
|
||||
if (toType == MIRType::Int64) {
|
||||
if (isUnsigned) {
|
||||
minValue = -1;
|
||||
maxValue = double(UINT64_MAX) + 1.0;
|
||||
asMasm().wasmTrap(wasm::Trap::IntegerOverflow, trapOffset);
|
||||
asMasm().bind(&inputIsNaN);
|
||||
asMasm().wasmTrap(wasm::Trap::InvalidConversionToInteger, trapOffset);
|
||||
}
|
||||
|
||||
void
|
||||
MacroAssemblerMIPSShared::outOfLineWasmTruncateToInt64Check(FloatRegister input, Register64 output_,
|
||||
MIRType fromType, TruncFlags flags,
|
||||
Label* rejoin,
|
||||
wasm::BytecodeOffset trapOffset)
|
||||
{
|
||||
bool isUnsigned = flags & TRUNC_UNSIGNED;
|
||||
bool isSaturating = flags & TRUNC_SATURATING;
|
||||
|
||||
|
||||
if(isSaturating) {
|
||||
#if defined(JS_CODEGEN_MIPS32)
|
||||
// Saturating callouts don't use ool path.
|
||||
return;
|
||||
#else
|
||||
Register output = output_.reg;
|
||||
|
||||
if(fromType == MIRType::Double)
|
||||
asMasm().loadConstantDouble(0.0, ScratchDoubleReg);
|
||||
else
|
||||
asMasm().loadConstantFloat32(0.0f, ScratchFloat32Reg);
|
||||
|
||||
if(isUnsigned) {
|
||||
|
||||
asMasm().ma_li(output, ImmWord(UINT64_MAX));
|
||||
|
||||
FloatTestKind moveCondition;
|
||||
compareFloatingPoint(fromType == MIRType::Double ? DoubleFloat : SingleFloat,
|
||||
input,
|
||||
fromType == MIRType::Double ? ScratchDoubleReg : ScratchFloat32Reg,
|
||||
Assembler::DoubleLessThanOrUnordered, &moveCondition);
|
||||
MOZ_ASSERT(moveCondition == TestForTrue);
|
||||
|
||||
as_movt(output, zero);
|
||||
} else {
|
||||
// In the float32/double range there exists no value between
|
||||
// INT64_MIN and INT64_MIN - 1.0. Making INT64_MIN the lower-bound.
|
||||
minValue = double(INT64_MIN);
|
||||
minCond = Assembler::DoubleLessThan;
|
||||
maxValue = double(INT64_MAX) + 1.0;
|
||||
}
|
||||
} else {
|
||||
if (isUnsigned) {
|
||||
minValue = -1;
|
||||
maxValue = double(UINT32_MAX) + 1.0;
|
||||
} else {
|
||||
if (fromType == MIRType::Float32) {
|
||||
// In the float32 range there exists no value between
|
||||
// INT32_MIN and INT32_MIN - 1.0. Making INT32_MIN the lower-bound.
|
||||
minValue = double(INT32_MIN);
|
||||
minCond = Assembler::DoubleLessThan;
|
||||
} else {
|
||||
minValue = double(INT32_MIN) - 1.0;
|
||||
}
|
||||
maxValue = double(INT32_MAX) + 1.0;
|
||||
|
||||
// Positive overflow is already saturated to INT64_MAX, so we only have
|
||||
// to handle NaN and negative overflow here.
|
||||
|
||||
FloatTestKind moveCondition;
|
||||
compareFloatingPoint(fromType == MIRType::Double ? DoubleFloat : SingleFloat,
|
||||
input,
|
||||
input,
|
||||
Assembler::DoubleUnordered, &moveCondition);
|
||||
MOZ_ASSERT(moveCondition == TestForTrue);
|
||||
|
||||
as_movt(output, zero);
|
||||
|
||||
compareFloatingPoint(fromType == MIRType::Double ? DoubleFloat : SingleFloat,
|
||||
input,
|
||||
fromType == MIRType::Double ? ScratchDoubleReg : ScratchFloat32Reg,
|
||||
Assembler::DoubleLessThan, &moveCondition);
|
||||
MOZ_ASSERT(moveCondition == TestForTrue);
|
||||
|
||||
asMasm().ma_li(ScratchRegister, ImmWord(INT64_MIN));
|
||||
as_movt(output, ScratchRegister);
|
||||
}
|
||||
|
||||
MOZ_ASSERT(rejoin->bound());
|
||||
asMasm().jump(rejoin);
|
||||
return;
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
Label fail;
|
||||
Label inputIsNaN;
|
||||
|
||||
if (fromType == MIRType::Double)
|
||||
asMasm().branchDouble(Assembler::DoubleUnordered, input, input, &inputIsNaN);
|
||||
else if (fromType == MIRType::Float32)
|
||||
asMasm().branchFloat(Assembler::DoubleUnordered, input, input, &inputIsNaN);
|
||||
|
||||
#if defined(JS_CODEGEN_MIPS32)
|
||||
|
||||
// Only possible valid input that produces INT64_MIN result.
|
||||
double validInput = isUnsigned ? double(uint64_t(INT64_MIN)) : double(int64_t(INT64_MIN));
|
||||
|
||||
if (fromType == MIRType::Double) {
|
||||
asMasm().loadConstantDouble(minValue, ScratchDoubleReg);
|
||||
asMasm().branchDouble(minCond, input, ScratchDoubleReg, &fail);
|
||||
|
||||
asMasm().loadConstantDouble(maxValue, ScratchDoubleReg);
|
||||
asMasm().branchDouble(maxCond, input, ScratchDoubleReg, &fail);
|
||||
asMasm().loadConstantDouble(validInput, ScratchDoubleReg);
|
||||
asMasm().branchDouble(Assembler::DoubleEqual, input, ScratchDoubleReg, rejoin);
|
||||
} else {
|
||||
asMasm().loadConstantFloat32(float(minValue), ScratchFloat32Reg);
|
||||
asMasm().branchFloat(minCond, input, ScratchFloat32Reg, &fail);
|
||||
|
||||
asMasm().loadConstantFloat32(float(maxValue), ScratchFloat32Reg);
|
||||
asMasm().branchFloat(maxCond, input, ScratchFloat32Reg, &fail);
|
||||
asMasm().loadConstantFloat32(float(validInput), ScratchFloat32Reg);
|
||||
asMasm().branchFloat(Assembler::DoubleEqual, input, ScratchDoubleReg, rejoin);
|
||||
}
|
||||
|
||||
asMasm().jump(rejoin);
|
||||
#endif
|
||||
|
||||
// Handle errors.
|
||||
asMasm().bind(&fail);
|
||||
asMasm().wasmTrap(wasm::Trap::IntegerOverflow, trapOffset);
|
||||
asMasm().bind(&inputIsNaN);
|
||||
asMasm().wasmTrap(wasm::Trap::InvalidConversionToInteger, trapOffset);
|
||||
|
|
|
@ -217,9 +217,12 @@ class MacroAssemblerMIPSShared : public Assembler
|
|||
void minMaxDouble(FloatRegister srcDest, FloatRegister other, bool handleNaN, bool isMax);
|
||||
void minMaxFloat32(FloatRegister srcDest, FloatRegister other, bool handleNaN, bool isMax);
|
||||
|
||||
void outOfLineWasmTruncateToIntCheck(FloatRegister input, MIRType fromType,
|
||||
MIRType toType, bool isUnsigned, Label* rejoin,
|
||||
wasm::BytecodeOffset trapOffset);
|
||||
void outOfLineWasmTruncateToInt32Check(FloatRegister input, Register output, MIRType fromType,
|
||||
TruncFlags flags, Label* rejoin,
|
||||
wasm::BytecodeOffset trapOffset);
|
||||
void outOfLineWasmTruncateToInt64Check(FloatRegister input, Register64 output, MIRType fromType,
|
||||
TruncFlags flags, Label* rejoin,
|
||||
wasm::BytecodeOffset trapOffset);
|
||||
|
||||
protected:
|
||||
void wasmLoadImpl(const wasm::MemoryAccessDesc& access, Register memoryBase, Register ptr,
|
||||
|
|
|
@ -69,7 +69,7 @@ class FloatRegisters : public FloatRegistersMIPSShared
|
|||
static const uint32_t TotalDouble = 16;
|
||||
static const uint32_t TotalSingle = 16;
|
||||
|
||||
static const uint32_t Allocatable = 28;
|
||||
static const uint32_t Allocatable = 30;
|
||||
static const SetType AllSingleMask = (1ULL << TotalSingle) - 1;
|
||||
|
||||
static const SetType AllDoubleMask = ((1ULL << TotalDouble) - 1) << TotalSingle;
|
||||
|
@ -95,8 +95,7 @@ class FloatRegisters : public FloatRegistersMIPSShared
|
|||
static const SetType WrapperMask = VolatileMask;
|
||||
|
||||
static const SetType NonAllocatableMask =
|
||||
((SetType(1) << (FloatRegisters::f16 >> 1)) |
|
||||
(SetType(1) << (FloatRegisters::f18 >> 1))) * ((1 << TotalSingle) + 1);
|
||||
(SetType(1) << (FloatRegisters::f18 >> 1)) * ((1 << TotalSingle) + 1);
|
||||
|
||||
static const SetType AllocatableMask = AllMask & ~NonAllocatableMask;
|
||||
};
|
||||
|
|
|
@ -83,8 +83,6 @@ static constexpr FloatRegister ReturnFloat32Reg = { FloatRegisters::f0, FloatReg
|
|||
static constexpr FloatRegister ReturnDoubleReg = { FloatRegisters::f0, FloatRegister::Double };
|
||||
static constexpr FloatRegister ScratchFloat32Reg = { FloatRegisters::f18, FloatRegister::Single };
|
||||
static constexpr FloatRegister ScratchDoubleReg = { FloatRegisters::f18, FloatRegister::Double };
|
||||
static constexpr FloatRegister SecondScratchFloat32Reg = { FloatRegisters::f16, FloatRegister::Single };
|
||||
static constexpr FloatRegister SecondScratchDoubleReg = { FloatRegisters::f16, FloatRegister::Double };
|
||||
|
||||
struct ScratchFloat32Scope : public AutoFloatRegisterScope
|
||||
{
|
||||
|
|
|
@ -619,40 +619,45 @@ void
|
|||
CodeGeneratorMIPS::visitWasmTruncateToInt64(LWasmTruncateToInt64* lir)
|
||||
{
|
||||
FloatRegister input = ToFloatRegister(lir->input());
|
||||
FloatRegister scratch = input;
|
||||
FloatRegister arg = input;
|
||||
Register64 output = ToOutRegister64(lir);
|
||||
MWasmTruncateToInt64* mir = lir->mir();
|
||||
MIRType fromType = mir->input()->type();
|
||||
|
||||
auto* ool = new(alloc()) OutOfLineWasmTruncateCheck(mir, input);
|
||||
auto* ool = new(alloc()) OutOfLineWasmTruncateCheck(mir, input, Register64::Invalid());
|
||||
addOutOfLineCode(ool, mir);
|
||||
|
||||
if (fromType == MIRType::Double) {
|
||||
masm.branchDouble(Assembler::DoubleUnordered, input, input, ool->entry());
|
||||
} else if (fromType == MIRType::Float32) {
|
||||
masm.branchFloat(Assembler::DoubleUnordered, input, input, ool->entry());
|
||||
scratch = ScratchDoubleReg;
|
||||
masm.convertFloat32ToDouble(input, scratch);
|
||||
} else {
|
||||
MOZ_CRASH("unexpected type in visitOutOfLineWasmTruncateCheck");
|
||||
if (fromType == MIRType::Float32) {
|
||||
arg = ScratchDoubleReg;
|
||||
masm.convertFloat32ToDouble(input, arg);
|
||||
}
|
||||
|
||||
masm.Push(input);
|
||||
if (!lir->mir()->isSaturating()) {
|
||||
masm.Push(input);
|
||||
|
||||
masm.setupWasmABICall();
|
||||
masm.passABIArg(scratch, MoveOp::DOUBLE);
|
||||
if (lir->mir()->isUnsigned())
|
||||
masm.callWithABI(mir->bytecodeOffset(), wasm::SymbolicAddress::TruncateDoubleToUint64);
|
||||
else
|
||||
masm.callWithABI(mir->bytecodeOffset(), wasm::SymbolicAddress::TruncateDoubleToInt64);
|
||||
masm.setupWasmABICall();
|
||||
masm.passABIArg(arg, MoveOp::DOUBLE);
|
||||
|
||||
masm.Pop(input);
|
||||
if (lir->mir()->isUnsigned())
|
||||
masm.callWithABI(mir->bytecodeOffset(), wasm::SymbolicAddress::TruncateDoubleToUint64);
|
||||
else
|
||||
masm.callWithABI(mir->bytecodeOffset(), wasm::SymbolicAddress::TruncateDoubleToInt64);
|
||||
|
||||
masm.ma_b(output.high, Imm32(0x80000000), ool->rejoin(), Assembler::NotEqual);
|
||||
masm.ma_b(output.low, Imm32(0x00000000), ool->rejoin(), Assembler::NotEqual);
|
||||
masm.ma_b(ool->entry());
|
||||
masm.Pop(input);
|
||||
|
||||
masm.bind(ool->rejoin());
|
||||
masm.ma_xor(ScratchRegister, output.high, Imm32(0x80000000));
|
||||
masm.ma_or(ScratchRegister, output.low);
|
||||
masm.ma_b(ScratchRegister, Imm32(0), ool->entry(), Assembler::Equal);
|
||||
|
||||
masm.bind(ool->rejoin());
|
||||
} else {
|
||||
masm.setupWasmABICall();
|
||||
masm.passABIArg(arg, MoveOp::DOUBLE);
|
||||
if (lir->mir()->isUnsigned())
|
||||
masm.callWithABI(mir->bytecodeOffset(), wasm::SymbolicAddress::SaturatingTruncateDoubleToUint64);
|
||||
else
|
||||
masm.callWithABI(mir->bytecodeOffset(), wasm::SymbolicAddress::SaturatingTruncateDoubleToInt64);
|
||||
}
|
||||
|
||||
MOZ_ASSERT(ReturnReg64 == output);
|
||||
}
|
||||
|
@ -661,7 +666,7 @@ void
|
|||
CodeGeneratorMIPS::visitInt64ToFloatingPoint(LInt64ToFloatingPoint* lir)
|
||||
{
|
||||
Register64 input = ToRegister64(lir->getInt64Operand(0));
|
||||
FloatRegister output = ToFloatRegister(lir->output());
|
||||
mozilla::DebugOnly<FloatRegister> output = ToFloatRegister(lir->output());
|
||||
|
||||
MInt64ToFloatingPoint* mir = lir->mir();
|
||||
MIRType toType = mir->type();
|
||||
|
@ -686,8 +691,8 @@ CodeGeneratorMIPS::visitInt64ToFloatingPoint(LInt64ToFloatingPoint* lir)
|
|||
else
|
||||
masm.callWithABI(mir->bytecodeOffset(), wasm::SymbolicAddress::Int64ToFloat32, MoveOp::FLOAT32);
|
||||
|
||||
MOZ_ASSERT_IF(toType == MIRType::Double, output == ReturnDoubleReg);
|
||||
MOZ_ASSERT_IF(toType == MIRType::Float32, output == ReturnFloat32Reg);
|
||||
MOZ_ASSERT_IF(toType == MIRType::Double, *(&output) == ReturnDoubleReg);
|
||||
MOZ_ASSERT_IF(toType == MIRType::Float32, *(&output) == ReturnFloat32Reg);
|
||||
}
|
||||
|
||||
void
|
||||
|
|
|
@ -996,6 +996,26 @@ MacroAssembler::branchToComputedAddress(const BaseIndex& addr)
|
|||
as_nop();
|
||||
}
|
||||
|
||||
void
|
||||
MacroAssembler::branchTruncateDoubleMaybeModUint32(FloatRegister src, Register dest, Label* fail)
|
||||
{
|
||||
as_truncwd(ScratchFloat32Reg, src);
|
||||
as_cfc1(ScratchRegister, Assembler::FCSR);
|
||||
moveFromFloat32(ScratchFloat32Reg, dest);
|
||||
ma_ext(ScratchRegister, ScratchRegister, Assembler::CauseV, 1);
|
||||
ma_b(ScratchRegister, Imm32(0), fail, Assembler::NotEqual);
|
||||
}
|
||||
|
||||
void
|
||||
MacroAssembler::branchTruncateFloat32MaybeModUint32(FloatRegister src, Register dest, Label* fail)
|
||||
{
|
||||
as_truncws(ScratchFloat32Reg, src);
|
||||
as_cfc1(ScratchRegister, Assembler::FCSR);
|
||||
moveFromFloat32(ScratchFloat32Reg, dest);
|
||||
ma_ext(ScratchRegister, ScratchRegister, Assembler::CauseV, 1);
|
||||
ma_b(ScratchRegister, Imm32(0), fail, Assembler::NotEqual);
|
||||
}
|
||||
|
||||
// ========================================================================
|
||||
// Memory access primitives.
|
||||
void
|
||||
|
|
|
@ -62,20 +62,26 @@ MacroAssemblerMIPSCompat::convertInt32ToDouble(const BaseIndex& src, FloatRegist
|
|||
void
|
||||
MacroAssemblerMIPSCompat::convertUInt32ToDouble(Register src, FloatRegister dest)
|
||||
{
|
||||
// We use SecondScratchDoubleReg because MacroAssembler::loadFromTypedArray
|
||||
// calls with ScratchDoubleReg as dest.
|
||||
MOZ_ASSERT(dest != SecondScratchDoubleReg);
|
||||
Label positive, done;
|
||||
ma_b(src, src, &positive, NotSigned, ShortJump);
|
||||
|
||||
// Subtract INT32_MIN to get a positive number
|
||||
ma_subu(ScratchRegister, src, Imm32(INT32_MIN));
|
||||
const uint32_t kExponentShift = mozilla::FloatingPoint<double>::kExponentShift - 32;
|
||||
const uint32_t kExponent = (31 + mozilla::FloatingPoint<double>::kExponentBias);
|
||||
|
||||
// Convert value
|
||||
as_mtc1(ScratchRegister, dest);
|
||||
as_cvtdw(dest, dest);
|
||||
ma_ext(SecondScratchReg, src, 31 - kExponentShift, kExponentShift);
|
||||
ma_li(ScratchRegister, Imm32(kExponent << kExponentShift));
|
||||
ma_or(SecondScratchReg, ScratchRegister);
|
||||
ma_sll(ScratchRegister, src, Imm32(kExponentShift + 1));
|
||||
moveToDoubleHi(SecondScratchReg, dest);
|
||||
moveToDoubleLo(ScratchRegister, dest);
|
||||
|
||||
ma_b(&done, ShortJump);
|
||||
|
||||
bind(&positive);
|
||||
convertInt32ToDouble(src, dest);
|
||||
|
||||
bind(&done);
|
||||
|
||||
// Add unsigned value of INT32_MIN
|
||||
ma_lid(SecondScratchDoubleReg, 2147483648.0);
|
||||
as_addd(dest, dest, SecondScratchDoubleReg);
|
||||
}
|
||||
|
||||
void
|
||||
|
@ -84,10 +90,19 @@ MacroAssemblerMIPSCompat::convertUInt32ToFloat32(Register src, FloatRegister des
|
|||
Label positive, done;
|
||||
ma_b(src, src, &positive, NotSigned, ShortJump);
|
||||
|
||||
// We cannot do the same as convertUInt32ToDouble because float32 doesn't
|
||||
// have enough precision.
|
||||
convertUInt32ToDouble(src, dest);
|
||||
convertDoubleToFloat32(dest, dest);
|
||||
const uint32_t kExponentShift = mozilla::FloatingPoint<double>::kExponentShift - 32;
|
||||
const uint32_t kExponent = (31 + mozilla::FloatingPoint<double>::kExponentBias);
|
||||
|
||||
ma_ext(SecondScratchReg, src, 31 - kExponentShift, kExponentShift);
|
||||
ma_li(ScratchRegister, Imm32(kExponent << kExponentShift));
|
||||
ma_or(SecondScratchReg, ScratchRegister);
|
||||
ma_sll(ScratchRegister, src, Imm32(kExponentShift + 1));
|
||||
FloatRegister destDouble = dest.asDouble();
|
||||
moveToDoubleHi(SecondScratchReg, destDouble);
|
||||
moveToDoubleLo(ScratchRegister, destDouble);
|
||||
|
||||
convertDoubleToFloat32(destDouble, dest);
|
||||
|
||||
ma_b(&done, ShortJump);
|
||||
|
||||
bind(&positive);
|
||||
|
@ -111,17 +126,18 @@ MacroAssemblerMIPSCompat::convertDoubleToInt32(FloatRegister src, Register dest,
|
|||
{
|
||||
if (negativeZeroCheck) {
|
||||
moveFromDoubleHi(src, dest);
|
||||
moveFromDoubleLo(src, ScratchRegister);
|
||||
as_movn(dest, zero, ScratchRegister);
|
||||
ma_b(dest, Imm32(INT32_MIN), fail, Assembler::Equal);
|
||||
moveFromDoubleLo(src, SecondScratchReg);
|
||||
ma_xor(dest, Imm32(INT32_MIN));
|
||||
ma_or(dest, SecondScratchReg);
|
||||
ma_b(dest, Imm32(0), fail, Assembler::Equal);
|
||||
}
|
||||
|
||||
// Convert double to int, then convert back and check if we have the
|
||||
// same number.
|
||||
as_cvtwd(ScratchDoubleReg, src);
|
||||
as_mfc1(dest, ScratchDoubleReg);
|
||||
as_cvtdw(ScratchDoubleReg, ScratchDoubleReg);
|
||||
ma_bc1d(src, ScratchDoubleReg, fail, Assembler::DoubleNotEqualOrUnordered);
|
||||
// Truncate double to int ; if result is inexact fail
|
||||
as_truncwd(ScratchFloat32Reg, src);
|
||||
as_cfc1(ScratchRegister, Assembler::FCSR);
|
||||
moveFromFloat32(ScratchFloat32Reg, dest);
|
||||
ma_ext(ScratchRegister, ScratchRegister, Assembler::CauseI, 1);
|
||||
ma_b(ScratchRegister, Imm32(0), fail, Assembler::NotEqual);
|
||||
}
|
||||
|
||||
// Checks whether a float32 is representable as a 32-bit integer. If so, the
|
||||
|
@ -136,18 +152,11 @@ MacroAssemblerMIPSCompat::convertFloat32ToInt32(FloatRegister src, Register dest
|
|||
ma_b(dest, Imm32(INT32_MIN), fail, Assembler::Equal);
|
||||
}
|
||||
|
||||
// Converting the floating point value to an integer and then converting it
|
||||
// back to a float32 would not work, as float to int32 conversions are
|
||||
// clamping (e.g. float(INT32_MAX + 1) would get converted into INT32_MAX
|
||||
// and then back to float(INT32_MAX + 1)). If this ever happens, we just
|
||||
// bail out.
|
||||
as_cvtws(ScratchFloat32Reg, src);
|
||||
as_mfc1(dest, ScratchFloat32Reg);
|
||||
as_cvtsw(ScratchFloat32Reg, ScratchFloat32Reg);
|
||||
ma_bc1s(src, ScratchFloat32Reg, fail, Assembler::DoubleNotEqualOrUnordered);
|
||||
|
||||
// Bail out in the clamped cases.
|
||||
ma_b(dest, Imm32(INT32_MAX), fail, Assembler::Equal);
|
||||
as_truncws(ScratchFloat32Reg, src);
|
||||
as_cfc1(ScratchRegister, Assembler::FCSR);
|
||||
moveFromFloat32(ScratchFloat32Reg, dest);
|
||||
ma_ext(ScratchRegister, ScratchRegister, Assembler::CauseI, 1);
|
||||
ma_b(ScratchRegister, Imm32(0), fail, Assembler::NotEqual);
|
||||
}
|
||||
|
||||
void
|
||||
|
@ -1377,49 +1386,19 @@ MacroAssemblerMIPSCompat::storeUnalignedDouble(const wasm::MemoryAccessDesc& acc
|
|||
append(access, store.getOffset(), framePushed);
|
||||
}
|
||||
|
||||
// Note: this function clobbers the input register.
|
||||
void
|
||||
MacroAssembler::clampDoubleToUint8(FloatRegister input, Register output)
|
||||
{
|
||||
MOZ_ASSERT(input != ScratchDoubleReg);
|
||||
Label positive, done;
|
||||
|
||||
// <= 0 or NaN --> 0
|
||||
zeroDouble(ScratchDoubleReg);
|
||||
branchDouble(DoubleGreaterThan, input, ScratchDoubleReg, &positive);
|
||||
{
|
||||
move32(Imm32(0), output);
|
||||
jump(&done);
|
||||
}
|
||||
|
||||
bind(&positive);
|
||||
|
||||
// Add 0.5 and truncate.
|
||||
loadConstantDouble(0.5, ScratchDoubleReg);
|
||||
addDouble(ScratchDoubleReg, input);
|
||||
|
||||
Label outOfRange;
|
||||
|
||||
branchTruncateDoubleMaybeModUint32(input, output, &outOfRange);
|
||||
asMasm().branch32(Assembler::Above, output, Imm32(255), &outOfRange);
|
||||
{
|
||||
// Check if we had a tie.
|
||||
convertInt32ToDouble(output, ScratchDoubleReg);
|
||||
branchDouble(DoubleNotEqual, input, ScratchDoubleReg, &done);
|
||||
|
||||
// It was a tie. Mask out the ones bit to get an even value.
|
||||
// See also js_TypedArray_uint8_clamp_double.
|
||||
and32(Imm32(~1), output);
|
||||
jump(&done);
|
||||
}
|
||||
|
||||
// > 255 --> 255
|
||||
bind(&outOfRange);
|
||||
{
|
||||
move32(Imm32(255), output);
|
||||
}
|
||||
|
||||
bind(&done);
|
||||
as_roundwd(ScratchDoubleReg, input);
|
||||
ma_li(ScratchRegister, Imm32(255));
|
||||
as_mfc1(output, ScratchDoubleReg);
|
||||
zeroDouble(ScratchDoubleReg);
|
||||
as_sltiu(SecondScratchReg, output, 255);
|
||||
as_colt(DoubleFloat, ScratchDoubleReg, input);
|
||||
// if res > 255; res = 255;
|
||||
as_movz(output, ScratchRegister, SecondScratchReg);
|
||||
// if !(input > 0); res = 0;
|
||||
as_movf(output, zero);
|
||||
}
|
||||
|
||||
// higher level tag testing code
|
||||
|
@ -2549,25 +2528,26 @@ void
|
|||
MacroAssembler::wasmTruncateDoubleToUInt32(FloatRegister input, Register output, bool isSaturating,
|
||||
Label* oolEntry)
|
||||
{
|
||||
MOZ_ASSERT(!isSaturating, "NYI");
|
||||
Label done;
|
||||
|
||||
loadConstantDouble(double(-1.0), ScratchDoubleReg);
|
||||
branchDouble(Assembler::DoubleLessThanOrEqual, input, ScratchDoubleReg, oolEntry);
|
||||
as_truncwd(ScratchFloat32Reg, input);
|
||||
ma_li(ScratchRegister, Imm32(INT32_MAX));
|
||||
moveFromFloat32(ScratchFloat32Reg, output);
|
||||
|
||||
loadConstantDouble(double(UINT32_MAX) + 1.0, ScratchDoubleReg);
|
||||
branchDouble(Assembler::DoubleGreaterThanOrEqualOrUnordered, input, ScratchDoubleReg, oolEntry);
|
||||
Label done, simple;
|
||||
loadConstantDouble(double(0x80000000UL), ScratchDoubleReg);
|
||||
branchDouble(Assembler::DoubleLessThan, input, ScratchDoubleReg, &simple);
|
||||
// For numbers in -1.[ : ]INT32_MAX range do nothing more
|
||||
ma_b(output, ScratchRegister, &done, Assembler::Below, ShortJump);
|
||||
|
||||
loadConstantDouble(double(INT32_MAX + 1ULL), ScratchDoubleReg);
|
||||
ma_li(ScratchRegister, Imm32(INT32_MIN));
|
||||
as_subd(ScratchDoubleReg, input, ScratchDoubleReg);
|
||||
as_truncwd(ScratchDoubleReg, ScratchDoubleReg);
|
||||
moveFromFloat32(ScratchDoubleReg, output);
|
||||
ma_li(ScratchRegister, Imm32(0x80000000UL));
|
||||
ma_or(output, ScratchRegister);
|
||||
ma_b(&done);
|
||||
bind(&simple);
|
||||
as_truncwd(ScratchDoubleReg, input);
|
||||
moveFromFloat32(ScratchDoubleReg, output);
|
||||
as_truncwd(ScratchFloat32Reg, ScratchDoubleReg);
|
||||
as_cfc1(SecondScratchReg, Assembler::FCSR);
|
||||
moveFromFloat32(ScratchFloat32Reg, output);
|
||||
ma_ext(SecondScratchReg, SecondScratchReg, Assembler::CauseV, 1);
|
||||
ma_addu(output, ScratchRegister);
|
||||
|
||||
ma_b(SecondScratchReg, Imm32(0), oolEntry, Assembler::NotEqual);
|
||||
|
||||
bind(&done);
|
||||
}
|
||||
|
||||
|
@ -2575,25 +2555,29 @@ void
|
|||
MacroAssembler::wasmTruncateFloat32ToUInt32(FloatRegister input, Register output, bool isSaturating,
|
||||
Label* oolEntry)
|
||||
{
|
||||
MOZ_ASSERT(!isSaturating, "NYI");
|
||||
Label done;
|
||||
|
||||
loadConstantFloat32(double(-1.0), ScratchDoubleReg);
|
||||
branchFloat(Assembler::DoubleLessThanOrEqualOrUnordered, input, ScratchDoubleReg, oolEntry);
|
||||
as_truncws(ScratchFloat32Reg, input);
|
||||
ma_li(ScratchRegister, Imm32(INT32_MAX));
|
||||
moveFromFloat32(ScratchFloat32Reg, output);
|
||||
// For numbers in -1.[ : ]INT32_MAX range do nothing more
|
||||
ma_b(output, ScratchRegister, &done, Assembler::Below, ShortJump);
|
||||
|
||||
loadConstantFloat32(float(INT32_MAX + 1ULL), ScratchFloat32Reg);
|
||||
ma_li(ScratchRegister, Imm32(INT32_MIN));
|
||||
as_subs(ScratchFloat32Reg, input, ScratchFloat32Reg);
|
||||
as_truncws(ScratchFloat32Reg, ScratchFloat32Reg);
|
||||
as_cfc1(SecondScratchReg, Assembler::FCSR);
|
||||
moveFromFloat32(ScratchFloat32Reg, output);
|
||||
ma_ext(SecondScratchReg, SecondScratchReg, Assembler::CauseV, 1);
|
||||
ma_addu(output, ScratchRegister);
|
||||
|
||||
// Guard against negative values that result in 0 due the precision loss.
|
||||
as_sltiu(ScratchRegister, output, 1);
|
||||
ma_or(SecondScratchReg, ScratchRegister);
|
||||
|
||||
ma_b(SecondScratchReg, Imm32(0), oolEntry, Assembler::NotEqual);
|
||||
|
||||
loadConstantFloat32(double(UINT32_MAX) + 1.0, ScratchDoubleReg);
|
||||
branchFloat(Assembler::DoubleGreaterThanOrEqualOrUnordered, input, ScratchDoubleReg, oolEntry);
|
||||
Label done, simple;
|
||||
loadConstantFloat32(double(0x80000000UL), ScratchDoubleReg);
|
||||
branchFloat(Assembler::DoubleLessThan, input, ScratchDoubleReg, &simple);
|
||||
as_subs(ScratchDoubleReg, input, ScratchDoubleReg);
|
||||
as_truncws(ScratchDoubleReg, ScratchDoubleReg);
|
||||
moveFromFloat32(ScratchDoubleReg, output);
|
||||
ma_li(ScratchRegister, Imm32(0x80000000UL));
|
||||
ma_or(output, ScratchRegister);
|
||||
ma_b(&done);
|
||||
bind(&simple);
|
||||
as_truncws(ScratchDoubleReg, input);
|
||||
moveFromFloat32(ScratchDoubleReg, output);
|
||||
bind(&done);
|
||||
}
|
||||
|
||||
|
|
|
@ -1576,24 +1576,34 @@ Simulator::setFCSRRoundError(double original, double rounded)
|
|||
{
|
||||
bool ret = false;
|
||||
|
||||
setFCSRBit(kFCSRInexactCauseBit, false);
|
||||
setFCSRBit(kFCSRUnderflowCauseBit, false);
|
||||
setFCSRBit(kFCSROverflowCauseBit, false);
|
||||
setFCSRBit(kFCSRInvalidOpCauseBit, false);
|
||||
|
||||
if (!std::isfinite(original) || !std::isfinite(rounded)) {
|
||||
setFCSRBit(kFCSRInvalidOpFlagBit, true);
|
||||
setFCSRBit(kFCSRInvalidOpCauseBit, true);
|
||||
ret = true;
|
||||
}
|
||||
|
||||
if (original != rounded) {
|
||||
setFCSRBit(kFCSRInexactFlagBit, true);
|
||||
setFCSRBit(kFCSRInexactCauseBit, true);
|
||||
}
|
||||
|
||||
if (rounded < DBL_MIN && rounded > -DBL_MIN && rounded != 0) {
|
||||
setFCSRBit(kFCSRUnderflowFlagBit, true);
|
||||
setFCSRBit(kFCSRUnderflowCauseBit, true);
|
||||
ret = true;
|
||||
}
|
||||
|
||||
if (rounded > INT_MAX || rounded < INT_MIN) {
|
||||
setFCSRBit(kFCSROverflowFlagBit, true);
|
||||
setFCSRBit(kFCSROverflowCauseBit, true);
|
||||
// The reference is not really clear but it seems this is required:
|
||||
setFCSRBit(kFCSRInvalidOpFlagBit, true);
|
||||
setFCSRBit(kFCSRInvalidOpCauseBit, true);
|
||||
ret = true;
|
||||
}
|
||||
|
||||
|
@ -1621,15 +1631,15 @@ Simulator::get_pc() const
|
|||
return registers_[pc];
|
||||
}
|
||||
|
||||
void
|
||||
Simulator::startInterrupt(JitActivation* activation)
|
||||
JS::ProfilingFrameIterator::RegisterState
|
||||
Simulator::registerState()
|
||||
{
|
||||
JS::ProfilingFrameIterator::RegisterState state;
|
||||
wasm::RegisterState state;
|
||||
state.pc = (void*) get_pc();
|
||||
state.fp = (void*) getRegister(fp);
|
||||
state.sp = (void*) getRegister(sp);
|
||||
state.lr = (void*) getRegister(ra);
|
||||
activation->startWasmInterrupt(state);
|
||||
return state;
|
||||
}
|
||||
|
||||
// The signal handler only redirects the PC to the interrupt stub when the PC is
|
||||
|
@ -1651,7 +1661,9 @@ Simulator::handleWasmInterrupt()
|
|||
if (!segment || !segment->isModule() || !segment->containsCodePC(pc))
|
||||
return;
|
||||
|
||||
startInterrupt(activation);
|
||||
if (!activation->startWasmInterrupt(registerState()))
|
||||
return;
|
||||
|
||||
set_pc(int32_t(segment->asModule()->interruptCode()));
|
||||
}
|
||||
|
||||
|
@ -1682,14 +1694,19 @@ Simulator::handleWasmFault(int32_t addr, unsigned numBytes)
|
|||
const wasm::ModuleSegment* moduleSegment = segment->asModule();
|
||||
|
||||
wasm::Instance* instance = wasm::LookupFaultingInstance(*moduleSegment, pc, fp);
|
||||
if (!instance || !instance->memoryAccessInGuardRegion((uint8_t*)addr, numBytes))
|
||||
if (!instance)
|
||||
return false;
|
||||
|
||||
MOZ_RELEASE_ASSERT(&instance->code() == &moduleSegment->code());
|
||||
|
||||
if (!instance->memoryAccessInGuardRegion((uint8_t*)addr, numBytes))
|
||||
return false;
|
||||
|
||||
LLBit_ = false;
|
||||
|
||||
const wasm::MemoryAccess* memoryAccess = instance->code().lookupMemoryAccess(pc);
|
||||
if (!memoryAccess) {
|
||||
startInterrupt(act);
|
||||
MOZ_ALWAYS_TRUE(act->startWasmInterrupt(registerState()));
|
||||
if (!instance->code().containsCodePC(pc))
|
||||
MOZ_CRASH("Cannot map PC to trap handler");
|
||||
set_pc(int32_t(moduleSegment->outOfBoundsCode()));
|
||||
|
@ -1713,7 +1730,6 @@ Simulator::handleWasmTrapFault()
|
|||
JitActivation* act = cx->activation()->asJit();
|
||||
|
||||
void* pc = reinterpret_cast<void*>(get_pc());
|
||||
uint8_t* fp = reinterpret_cast<uint8_t*>(getRegister(Register::fp));
|
||||
|
||||
const wasm::CodeSegment* segment = wasm::LookupCodeSegment(pc);
|
||||
if (!segment || !segment->isModule())
|
||||
|
@ -1725,7 +1741,7 @@ Simulator::handleWasmTrapFault()
|
|||
if (!moduleSegment->code().lookupTrap(pc, &trap, &bytecode))
|
||||
return false;
|
||||
|
||||
act->startWasmTrap(trap, bytecode.offset, pc, fp);
|
||||
act->startWasmTrap(trap, bytecode.offset, registerState());
|
||||
set_pc(int32_t(moduleSegment->trapCode()));
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -34,6 +34,7 @@
|
|||
#include "mozilla/Atomics.h"
|
||||
|
||||
#include "jit/IonTypes.h"
|
||||
#include "js/ProfilingFrameIterator.h"
|
||||
#include "threading/Thread.h"
|
||||
#include "vm/MutexIDs.h"
|
||||
#include "wasm/WasmCode.h"
|
||||
|
@ -77,6 +78,12 @@ const uint32_t kFCSROverflowFlagBit = 4;
|
|||
const uint32_t kFCSRDivideByZeroFlagBit = 5;
|
||||
const uint32_t kFCSRInvalidOpFlagBit = 6;
|
||||
|
||||
const uint32_t kFCSRInexactCauseBit = 12;
|
||||
const uint32_t kFCSRUnderflowCauseBit = 13;
|
||||
const uint32_t kFCSROverflowCauseBit = 14;
|
||||
const uint32_t kFCSRDivideByZeroCauseBit = 15;
|
||||
const uint32_t kFCSRInvalidOpCauseBit = 16;
|
||||
|
||||
const uint32_t kFCSRInexactFlagMask = 1 << kFCSRInexactFlagBit;
|
||||
const uint32_t kFCSRUnderflowFlagMask = 1 << kFCSRUnderflowFlagBit;
|
||||
const uint32_t kFCSROverflowFlagMask = 1 << kFCSROverflowFlagBit;
|
||||
|
@ -299,7 +306,7 @@ class Simulator {
|
|||
|
||||
// Handle a wasm interrupt triggered by an async signal handler.
|
||||
void handleWasmInterrupt();
|
||||
void startInterrupt(JitActivation* act);
|
||||
JS::ProfilingFrameIterator::RegisterState registerState();
|
||||
|
||||
// Handle any wasm faults, returning true if the fault was handled.
|
||||
bool handleWasmFault(int32_t addr, unsigned numBytes);
|
||||
|
|
|
@ -41,7 +41,7 @@ class FloatRegisters : public FloatRegistersMIPSShared
|
|||
static Encoding FromName(const char* name);
|
||||
|
||||
static const uint32_t Total = 32 * NumTypes;
|
||||
static const uint32_t Allocatable = 60;
|
||||
static const uint32_t Allocatable = 62;
|
||||
// When saving all registers we only need to do is save double registers.
|
||||
static const uint32_t TotalPhys = 32;
|
||||
|
||||
|
@ -79,10 +79,7 @@ class FloatRegisters : public FloatRegistersMIPSShared
|
|||
static const SetType WrapperMask = VolatileMask;
|
||||
|
||||
static const SetType NonAllocatableMask =
|
||||
( // f21 and f23 are MIPS scratch float registers.
|
||||
(1U << FloatRegisters::f21) |
|
||||
(1U << FloatRegisters::f23)
|
||||
) * Spread;
|
||||
(1U << FloatRegisters::f23) * Spread;
|
||||
|
||||
static const SetType AllocatableMask = AllMask & ~NonAllocatableMask;
|
||||
};
|
||||
|
|
|
@ -77,8 +77,6 @@ static constexpr FloatRegister ReturnFloat32Reg = { FloatRegisters::f0, FloatReg
|
|||
static constexpr FloatRegister ReturnDoubleReg = { FloatRegisters::f0, FloatRegisters::Double };
|
||||
static constexpr FloatRegister ScratchFloat32Reg = { FloatRegisters::f23, FloatRegisters::Single };
|
||||
static constexpr FloatRegister ScratchDoubleReg = { FloatRegisters::f23, FloatRegisters::Double };
|
||||
static constexpr FloatRegister SecondScratchFloat32Reg = { FloatRegisters::f21, FloatRegisters::Single };
|
||||
static constexpr FloatRegister SecondScratchDoubleReg = { FloatRegisters::f21, FloatRegisters::Double };
|
||||
|
||||
struct ScratchFloat32Scope : public AutoFloatRegisterScope
|
||||
{
|
||||
|
|
|
@ -594,18 +594,35 @@ void
|
|||
CodeGeneratorMIPS64::visitWasmTruncateToInt64(LWasmTruncateToInt64* lir)
|
||||
{
|
||||
FloatRegister input = ToFloatRegister(lir->input());
|
||||
Register output = ToRegister(lir->output());
|
||||
Register64 output = ToOutRegister64(lir);
|
||||
|
||||
MWasmTruncateToInt64* mir = lir->mir();
|
||||
MIRType fromType = mir->input()->type();
|
||||
|
||||
MOZ_ASSERT(fromType == MIRType::Double || fromType == MIRType::Float32);
|
||||
|
||||
auto* ool = new (alloc()) OutOfLineWasmTruncateCheck(mir, input);
|
||||
auto* ool = new (alloc()) OutOfLineWasmTruncateCheck(mir, input, output);
|
||||
addOutOfLineCode(ool, mir);
|
||||
|
||||
masm.wasmTruncateToI64(input, output, fromType, mir->isUnsigned(),
|
||||
ool->entry(), ool->rejoin());
|
||||
Label* oolEntry = ool->entry();
|
||||
Label* oolRejoin = ool->rejoin();
|
||||
bool isSaturating = mir->isSaturating();
|
||||
|
||||
if (fromType == MIRType::Double) {
|
||||
if (mir->isUnsigned())
|
||||
masm.wasmTruncateDoubleToUInt64(input, output, isSaturating, oolEntry, oolRejoin,
|
||||
InvalidFloatReg);
|
||||
else
|
||||
masm.wasmTruncateDoubleToInt64(input, output, isSaturating, oolEntry, oolRejoin,
|
||||
InvalidFloatReg);
|
||||
} else {
|
||||
if (mir->isUnsigned())
|
||||
masm.wasmTruncateFloat32ToUInt64(input, output, isSaturating, oolEntry, oolRejoin,
|
||||
InvalidFloatReg);
|
||||
else
|
||||
masm.wasmTruncateFloat32ToInt64(input, output, isSaturating, oolEntry, oolRejoin,
|
||||
InvalidFloatReg);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
|
|
|
@ -744,6 +744,30 @@ MacroAssembler::branchToComputedAddress(const BaseIndex& addr)
|
|||
as_nop();
|
||||
}
|
||||
|
||||
void
|
||||
MacroAssembler::branchTruncateDoubleMaybeModUint32(FloatRegister src, Register dest, Label* fail)
|
||||
{
|
||||
as_truncld(ScratchDoubleReg, src);
|
||||
as_cfc1(ScratchRegister, Assembler::FCSR);
|
||||
moveFromDouble(ScratchDoubleReg, dest);
|
||||
ma_ext(ScratchRegister, ScratchRegister, Assembler::CauseV, 1);
|
||||
ma_b(ScratchRegister, Imm32(0), fail, Assembler::NotEqual);
|
||||
|
||||
as_sll(dest, dest, 0);
|
||||
}
|
||||
|
||||
void
|
||||
MacroAssembler::branchTruncateFloat32MaybeModUint32(FloatRegister src, Register dest, Label* fail)
|
||||
{
|
||||
as_truncls(ScratchDoubleReg, src);
|
||||
as_cfc1(ScratchRegister, Assembler::FCSR);
|
||||
moveFromDouble(ScratchDoubleReg, dest);
|
||||
ma_ext(ScratchRegister, ScratchRegister, Assembler::CauseV, 1);
|
||||
ma_b(ScratchRegister, Imm32(0), fail, Assembler::NotEqual);
|
||||
|
||||
as_sll(dest, dest, 0);
|
||||
}
|
||||
|
||||
// ========================================================================
|
||||
// Memory access primitives.
|
||||
void
|
||||
|
|
|
@ -58,20 +58,8 @@ MacroAssemblerMIPS64Compat::convertInt32ToDouble(const BaseIndex& src, FloatRegi
|
|||
void
|
||||
MacroAssemblerMIPS64Compat::convertUInt32ToDouble(Register src, FloatRegister dest)
|
||||
{
|
||||
// We use SecondScratchDoubleReg because MacroAssembler::loadFromTypedArray
|
||||
// calls with ScratchDoubleReg as dest.
|
||||
MOZ_ASSERT(dest != SecondScratchDoubleReg);
|
||||
|
||||
// Subtract INT32_MIN to get a positive number
|
||||
ma_subu(ScratchRegister, src, Imm32(INT32_MIN));
|
||||
|
||||
// Convert value
|
||||
as_mtc1(ScratchRegister, dest);
|
||||
as_cvtdw(dest, dest);
|
||||
|
||||
// Add unsigned value of INT32_MIN
|
||||
ma_lid(SecondScratchDoubleReg, 2147483648.0);
|
||||
as_addd(dest, dest, SecondScratchDoubleReg);
|
||||
ma_dext(ScratchRegister, src, Imm32(0), Imm32(32));
|
||||
asMasm().convertInt64ToDouble(Register64(ScratchRegister), dest);
|
||||
}
|
||||
|
||||
void
|
||||
|
@ -101,19 +89,8 @@ MacroAssemblerMIPS64Compat::convertUInt64ToDouble(Register src, FloatRegister de
|
|||
void
|
||||
MacroAssemblerMIPS64Compat::convertUInt32ToFloat32(Register src, FloatRegister dest)
|
||||
{
|
||||
Label positive, done;
|
||||
ma_b(src, src, &positive, NotSigned, ShortJump);
|
||||
|
||||
// We cannot do the same as convertUInt32ToDouble because float32 doesn't
|
||||
// have enough precision.
|
||||
convertUInt32ToDouble(src, dest);
|
||||
convertDoubleToFloat32(dest, dest);
|
||||
ma_b(&done, ShortJump);
|
||||
|
||||
bind(&positive);
|
||||
convertInt32ToFloat32(src, dest);
|
||||
|
||||
bind(&done);
|
||||
ma_dext(ScratchRegister, src, Imm32(0), Imm32(32));
|
||||
asMasm().convertInt64ToFloat32(Register64(ScratchRegister), dest);
|
||||
}
|
||||
|
||||
void
|
||||
|
@ -135,12 +112,12 @@ MacroAssemblerMIPS64Compat::convertDoubleToInt32(FloatRegister src, Register des
|
|||
ma_b(dest, Imm32(1), fail, Assembler::Equal);
|
||||
}
|
||||
|
||||
// Convert double to int, then convert back and check if we have the
|
||||
// same number.
|
||||
as_cvtwd(ScratchDoubleReg, src);
|
||||
as_mfc1(dest, ScratchDoubleReg);
|
||||
as_cvtdw(ScratchDoubleReg, ScratchDoubleReg);
|
||||
ma_bc1d(src, ScratchDoubleReg, fail, Assembler::DoubleNotEqualOrUnordered);
|
||||
// Truncate double to int ; if result is inexact fail
|
||||
as_truncwd(ScratchFloat32Reg, src);
|
||||
as_cfc1(ScratchRegister, Assembler::FCSR);
|
||||
moveFromFloat32(ScratchFloat32Reg, dest);
|
||||
ma_ext(ScratchRegister, ScratchRegister, Assembler::CauseI, 1);
|
||||
ma_b(ScratchRegister, Imm32(0), fail, Assembler::NotEqual);
|
||||
}
|
||||
|
||||
// Checks whether a float32 is representable as a 32-bit integer. If so, the
|
||||
|
@ -155,18 +132,11 @@ MacroAssemblerMIPS64Compat::convertFloat32ToInt32(FloatRegister src, Register de
|
|||
ma_b(dest, Imm32(INT32_MIN), fail, Assembler::Equal);
|
||||
}
|
||||
|
||||
// Converting the floating point value to an integer and then converting it
|
||||
// back to a float32 would not work, as float to int32 conversions are
|
||||
// clamping (e.g. float(INT32_MAX + 1) would get converted into INT32_MAX
|
||||
// and then back to float(INT32_MAX + 1)). If this ever happens, we just
|
||||
// bail out.
|
||||
as_cvtws(ScratchFloat32Reg, src);
|
||||
as_mfc1(dest, ScratchFloat32Reg);
|
||||
as_cvtsw(ScratchFloat32Reg, ScratchFloat32Reg);
|
||||
ma_bc1s(src, ScratchFloat32Reg, fail, Assembler::DoubleNotEqualOrUnordered);
|
||||
|
||||
// Bail out in the clamped cases.
|
||||
ma_b(dest, Imm32(INT32_MAX), fail, Assembler::Equal);
|
||||
as_truncws(ScratchFloat32Reg, src);
|
||||
as_cfc1(ScratchRegister, Assembler::FCSR);
|
||||
moveFromFloat32(ScratchFloat32Reg, dest);
|
||||
ma_ext(ScratchRegister, ScratchRegister, Assembler::CauseI, 1);
|
||||
ma_b(ScratchRegister, Imm32(0), fail, Assembler::NotEqual);
|
||||
}
|
||||
|
||||
void
|
||||
|
@ -855,8 +825,13 @@ MacroAssemblerMIPS64::ma_lid(FloatRegister dest, double value)
|
|||
{
|
||||
ImmWord imm(mozilla::BitwiseCast<uint64_t>(value));
|
||||
|
||||
ma_li(ScratchRegister, imm);
|
||||
moveToDouble(ScratchRegister, dest);
|
||||
if(imm.value != 0){
|
||||
ma_li(ScratchRegister, imm);
|
||||
moveToDouble(ScratchRegister, dest);
|
||||
} else {
|
||||
moveToDouble(zero, dest);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void
|
||||
|
@ -1363,49 +1338,19 @@ MacroAssemblerMIPS64Compat::storeUnalignedDouble(const wasm::MemoryAccessDesc& a
|
|||
append(access, store.getOffset(), asMasm().framePushed());
|
||||
}
|
||||
|
||||
// Note: this function clobbers the input register.
|
||||
void
|
||||
MacroAssembler::clampDoubleToUint8(FloatRegister input, Register output)
|
||||
{
|
||||
MOZ_ASSERT(input != ScratchDoubleReg);
|
||||
Label positive, done;
|
||||
|
||||
// <= 0 or NaN --> 0
|
||||
zeroDouble(ScratchDoubleReg);
|
||||
branchDouble(DoubleGreaterThan, input, ScratchDoubleReg, &positive);
|
||||
{
|
||||
move32(Imm32(0), output);
|
||||
jump(&done);
|
||||
}
|
||||
|
||||
bind(&positive);
|
||||
|
||||
// Add 0.5 and truncate.
|
||||
loadConstantDouble(0.5, ScratchDoubleReg);
|
||||
addDouble(ScratchDoubleReg, input);
|
||||
|
||||
Label outOfRange;
|
||||
|
||||
branchTruncateDoubleMaybeModUint32(input, output, &outOfRange);
|
||||
asMasm().branch32(Assembler::Above, output, Imm32(255), &outOfRange);
|
||||
{
|
||||
// Check if we had a tie.
|
||||
convertInt32ToDouble(output, ScratchDoubleReg);
|
||||
branchDouble(DoubleNotEqual, input, ScratchDoubleReg, &done);
|
||||
|
||||
// It was a tie. Mask out the ones bit to get an even value.
|
||||
// See also js_TypedArray_uint8_clamp_double.
|
||||
and32(Imm32(~1), output);
|
||||
jump(&done);
|
||||
}
|
||||
|
||||
// > 255 --> 255
|
||||
bind(&outOfRange);
|
||||
{
|
||||
move32(Imm32(255), output);
|
||||
}
|
||||
|
||||
bind(&done);
|
||||
as_roundwd(ScratchDoubleReg, input);
|
||||
ma_li(ScratchRegister, Imm32(255));
|
||||
as_mfc1(output, ScratchDoubleReg);
|
||||
zeroDouble(ScratchDoubleReg);
|
||||
as_sltiu(SecondScratchReg, output, 255);
|
||||
as_colt(DoubleFloat, ScratchDoubleReg, input);
|
||||
// if res > 255; res = 255;
|
||||
as_movz(output, ScratchRegister, SecondScratchReg);
|
||||
// if !(input > 0); res = 0;
|
||||
as_movf(output, zero);
|
||||
}
|
||||
|
||||
void
|
||||
|
@ -2441,33 +2386,22 @@ void
|
|||
MacroAssembler::wasmTruncateDoubleToUInt32(FloatRegister input, Register output, bool isSaturating,
|
||||
Label* oolEntry)
|
||||
{
|
||||
MOZ_ASSERT(!isSaturating, "NYI");
|
||||
|
||||
as_truncld(ScratchDoubleReg, input);
|
||||
moveFromDoubleHi(ScratchDoubleReg, output);
|
||||
as_cfc1(ScratchRegister, Assembler::FCSR);
|
||||
ma_ext(ScratchRegister, ScratchRegister, 6, 1);
|
||||
ma_or(ScratchRegister, output);
|
||||
moveFromFloat32(ScratchDoubleReg, output);
|
||||
moveFromDouble(ScratchDoubleReg, output);
|
||||
ma_dsrl(ScratchRegister, output, Imm32(32));
|
||||
as_sll(output, output, 0);
|
||||
ma_b(ScratchRegister, Imm32(0), oolEntry, Assembler::NotEqual);
|
||||
|
||||
|
||||
}
|
||||
|
||||
void
|
||||
MacroAssembler::wasmTruncateFloat32ToUInt32(FloatRegister input, Register output, bool isSaturating,
|
||||
Label* oolEntry)
|
||||
{
|
||||
MOZ_ASSERT(!isSaturating, "NYI");
|
||||
|
||||
as_truncls(ScratchDoubleReg, input);
|
||||
moveFromDoubleHi(ScratchDoubleReg, output);
|
||||
as_cfc1(ScratchRegister, Assembler::FCSR);
|
||||
ma_ext(ScratchRegister, ScratchRegister, 6, 1);
|
||||
ma_or(ScratchRegister, output);
|
||||
moveFromFloat32(ScratchDoubleReg, output);
|
||||
moveFromDouble(ScratchDoubleReg, output);
|
||||
ma_dsrl(ScratchRegister, output, Imm32(32));
|
||||
as_sll(output, output, 0);
|
||||
ma_b(ScratchRegister, Imm32(0), oolEntry, Assembler::NotEqual);
|
||||
|
||||
}
|
||||
|
||||
void
|
||||
|
@ -2501,113 +2435,117 @@ MacroAssembler::wasmUnalignedStoreI64(const wasm::MemoryAccessDesc& access, Regi
|
|||
}
|
||||
|
||||
void
|
||||
MacroAssembler::wasmTruncateDoubleToInt64(FloatRegister input, Register64 output, bool,
|
||||
Label* oolEntry, Label* oolRejoin,
|
||||
FloatRegister tempDouble)
|
||||
MacroAssembler::wasmTruncateDoubleToInt64(FloatRegister input, Register64 output,
|
||||
bool isSaturating, Label* oolEntry,
|
||||
Label* oolRejoin, FloatRegister tempDouble)
|
||||
{
|
||||
MOZ_ASSERT(tempDouble.isInvalid());
|
||||
wasmTruncateToI64(input, output.reg, MIRType::Double, false, oolEntry, oolRejoin);
|
||||
|
||||
as_truncld(ScratchDoubleReg, input);
|
||||
as_cfc1(ScratchRegister, Assembler::FCSR);
|
||||
moveFromDouble(ScratchDoubleReg, output.reg);
|
||||
ma_ext(ScratchRegister, ScratchRegister, Assembler::CauseV, 1);
|
||||
ma_b(ScratchRegister, Imm32(0), oolEntry, Assembler::NotEqual);
|
||||
|
||||
if (isSaturating)
|
||||
bind(oolRejoin);
|
||||
}
|
||||
|
||||
void
|
||||
MacroAssembler::wasmTruncateDoubleToUInt64(FloatRegister input, Register64 output, bool,
|
||||
Label* oolEntry, Label* oolRejoin,
|
||||
FloatRegister tempDouble)
|
||||
MacroAssembler::wasmTruncateDoubleToUInt64(FloatRegister input, Register64 output_,
|
||||
bool isSaturating, Label* oolEntry,
|
||||
Label* oolRejoin, FloatRegister tempDouble)
|
||||
{
|
||||
MOZ_ASSERT(tempDouble.isInvalid());
|
||||
wasmTruncateToI64(input, output.reg, MIRType::Double, true, oolEntry, oolRejoin);
|
||||
Register output = output_.reg;
|
||||
|
||||
Label done;
|
||||
|
||||
as_truncld(ScratchDoubleReg, input);
|
||||
// ma_li INT64_MAX
|
||||
ma_li(SecondScratchReg, Imm32(-1));
|
||||
ma_dext(SecondScratchReg, SecondScratchReg, Imm32(0), Imm32(63));
|
||||
moveFromDouble(ScratchDoubleReg, output);
|
||||
// For numbers in -1.[ : ]INT64_MAX range do nothing more
|
||||
ma_b(output, SecondScratchReg, &done, Assembler::Below, ShortJump);
|
||||
|
||||
loadConstantDouble(double(INT64_MAX + 1ULL), ScratchDoubleReg);
|
||||
// ma_li INT64_MIN
|
||||
ma_daddu(SecondScratchReg, Imm32(1));
|
||||
as_subd(ScratchDoubleReg, input, ScratchDoubleReg);
|
||||
as_truncld(ScratchDoubleReg, ScratchDoubleReg);
|
||||
as_cfc1(ScratchRegister, Assembler::FCSR);
|
||||
moveFromDouble(ScratchDoubleReg, output);
|
||||
ma_ext(ScratchRegister, ScratchRegister, Assembler::CauseV, 1);
|
||||
ma_daddu(output, SecondScratchReg);
|
||||
|
||||
// Guard against negative values that result in 0 due the precision loss.
|
||||
as_sltiu(SecondScratchReg, output, 1);
|
||||
ma_or(ScratchRegister, SecondScratchReg);
|
||||
|
||||
ma_b(ScratchRegister, Imm32(0), oolEntry, Assembler::NotEqual);
|
||||
|
||||
bind(&done);
|
||||
|
||||
if (isSaturating)
|
||||
bind(oolRejoin);
|
||||
}
|
||||
|
||||
void
|
||||
MacroAssembler::wasmTruncateFloat32ToInt64(FloatRegister input, Register64 output, bool,
|
||||
Label* oolEntry, Label* oolRejoin,
|
||||
FloatRegister tempFloat)
|
||||
MacroAssembler::wasmTruncateFloat32ToInt64(FloatRegister input, Register64 output,
|
||||
bool isSaturating, Label* oolEntry,
|
||||
Label* oolRejoin, FloatRegister tempFloat)
|
||||
{
|
||||
MOZ_ASSERT(tempFloat.isInvalid());
|
||||
wasmTruncateToI64(input, output.reg, MIRType::Float32, false, oolEntry, oolRejoin);
|
||||
|
||||
as_truncls(ScratchDoubleReg, input);
|
||||
as_cfc1(ScratchRegister, Assembler::FCSR);
|
||||
moveFromDouble(ScratchDoubleReg, output.reg);
|
||||
ma_ext(ScratchRegister, ScratchRegister, Assembler::CauseV, 1);
|
||||
ma_b(ScratchRegister, Imm32(0), oolEntry, Assembler::NotEqual);
|
||||
|
||||
if (isSaturating)
|
||||
bind(oolRejoin);
|
||||
}
|
||||
|
||||
void
|
||||
MacroAssembler::wasmTruncateFloat32ToUInt64(FloatRegister input, Register64 output, bool,
|
||||
Label* oolEntry, Label* oolRejoin,
|
||||
FloatRegister tempFloat)
|
||||
MacroAssembler::wasmTruncateFloat32ToUInt64(FloatRegister input, Register64 output_,
|
||||
bool isSaturating, Label* oolEntry,
|
||||
Label* oolRejoin, FloatRegister tempFloat)
|
||||
{
|
||||
MOZ_ASSERT(tempFloat.isInvalid());
|
||||
wasmTruncateToI64(input, output.reg, MIRType::Float32, true, oolEntry, oolRejoin);
|
||||
}
|
||||
Register output = output_.reg;
|
||||
|
||||
void
|
||||
MacroAssemblerMIPS64Compat::wasmTruncateToI64(FloatRegister input, Register output, MIRType fromType,
|
||||
bool isUnsigned, Label* oolEntry, Label* oolRejoin)
|
||||
{
|
||||
if (isUnsigned) {
|
||||
Label isLarge, done;
|
||||
Label done;
|
||||
|
||||
if (fromType == MIRType::Double) {
|
||||
asMasm().loadConstantDouble(double(INT64_MAX), ScratchDoubleReg);
|
||||
asMasm().ma_bc1d(ScratchDoubleReg, input, &isLarge,
|
||||
Assembler::DoubleLessThanOrEqual, ShortJump);
|
||||
as_truncls(ScratchDoubleReg, input);
|
||||
// ma_li INT64_MAX
|
||||
ma_li(SecondScratchReg, Imm32(-1));
|
||||
ma_dext(SecondScratchReg, SecondScratchReg, Imm32(0), Imm32(63));
|
||||
moveFromDouble(ScratchDoubleReg, output);
|
||||
// For numbers in -1.[ : ]INT64_MAX range do nothing more
|
||||
ma_b(output, SecondScratchReg, &done, Assembler::Below, ShortJump);
|
||||
|
||||
asMasm().as_truncld(ScratchDoubleReg, input);
|
||||
} else {
|
||||
asMasm().loadConstantFloat32(float(INT64_MAX), ScratchFloat32Reg);
|
||||
asMasm().ma_bc1s(ScratchFloat32Reg, input, &isLarge,
|
||||
Assembler::DoubleLessThanOrEqual, ShortJump);
|
||||
loadConstantFloat32(float(INT64_MAX + 1ULL), ScratchFloat32Reg);
|
||||
// ma_li INT64_MIN
|
||||
ma_daddu(SecondScratchReg, Imm32(1));
|
||||
as_subs(ScratchFloat32Reg, input, ScratchFloat32Reg);
|
||||
as_truncls(ScratchDoubleReg, ScratchFloat32Reg);
|
||||
as_cfc1(ScratchRegister, Assembler::FCSR);
|
||||
moveFromDouble(ScratchDoubleReg, output);
|
||||
ma_ext(ScratchRegister, ScratchRegister, Assembler::CauseV, 1);
|
||||
ma_daddu(output, SecondScratchReg);
|
||||
|
||||
asMasm().as_truncls(ScratchDoubleReg, input);
|
||||
}
|
||||
// Guard against negative values that result in 0 due the precision loss.
|
||||
as_sltiu(SecondScratchReg, output, 1);
|
||||
ma_or(ScratchRegister, SecondScratchReg);
|
||||
|
||||
// Check that the result is in the uint64_t range.
|
||||
asMasm().moveFromDouble(ScratchDoubleReg, output);
|
||||
asMasm().as_cfc1(ScratchRegister, Assembler::FCSR);
|
||||
// extract invalid operation flag (bit 6) from FCSR
|
||||
asMasm().ma_ext(ScratchRegister, ScratchRegister, 16, 1);
|
||||
asMasm().ma_dsrl(SecondScratchReg, output, Imm32(63));
|
||||
asMasm().ma_or(SecondScratchReg, ScratchRegister);
|
||||
asMasm().ma_b(SecondScratchReg, Imm32(0), oolEntry, Assembler::NotEqual);
|
||||
ma_b(ScratchRegister, Imm32(0), oolEntry, Assembler::NotEqual);
|
||||
|
||||
asMasm().ma_b(&done, ShortJump);
|
||||
bind(&done);
|
||||
|
||||
// The input is greater than double(INT64_MAX).
|
||||
asMasm().bind(&isLarge);
|
||||
if (fromType == MIRType::Double) {
|
||||
asMasm().as_subd(ScratchDoubleReg, input, ScratchDoubleReg);
|
||||
asMasm().as_truncld(ScratchDoubleReg, ScratchDoubleReg);
|
||||
} else {
|
||||
asMasm().as_subs(ScratchDoubleReg, input, ScratchDoubleReg);
|
||||
asMasm().as_truncls(ScratchDoubleReg, ScratchDoubleReg);
|
||||
}
|
||||
|
||||
// Check that the result is in the uint64_t range.
|
||||
asMasm().moveFromDouble(ScratchDoubleReg, output);
|
||||
asMasm().as_cfc1(ScratchRegister, Assembler::FCSR);
|
||||
asMasm().ma_ext(ScratchRegister, ScratchRegister, 16, 1);
|
||||
asMasm().ma_dsrl(SecondScratchReg, output, Imm32(63));
|
||||
asMasm().ma_or(SecondScratchReg, ScratchRegister);
|
||||
asMasm().ma_b(SecondScratchReg, Imm32(0), oolEntry, Assembler::NotEqual);
|
||||
|
||||
asMasm().ma_li(ScratchRegister, Imm32(1));
|
||||
asMasm().ma_dins(output, ScratchRegister, Imm32(63), Imm32(1));
|
||||
|
||||
asMasm().bind(&done);
|
||||
asMasm().bind(oolRejoin);
|
||||
return;
|
||||
}
|
||||
|
||||
// When the input value is Infinity, NaN, or rounds to an integer outside the
|
||||
// range [INT64_MIN; INT64_MAX + 1[, the Invalid Operation flag is set in the FCSR.
|
||||
if (fromType == MIRType::Double)
|
||||
asMasm().as_truncld(ScratchDoubleReg, input);
|
||||
else
|
||||
asMasm().as_truncls(ScratchDoubleReg, input);
|
||||
|
||||
// Check that the result is in the int64_t range.
|
||||
asMasm().as_cfc1(output, Assembler::FCSR);
|
||||
asMasm().ma_ext(output, output, 16, 1);
|
||||
asMasm().ma_b(output, Imm32(0), oolEntry, Assembler::NotEqual);
|
||||
|
||||
asMasm().bind(oolRejoin);
|
||||
asMasm().moveFromDouble(ScratchDoubleReg, output);
|
||||
if (isSaturating)
|
||||
bind(oolRejoin);
|
||||
}
|
||||
|
||||
void
|
||||
|
|
|
@ -733,8 +733,6 @@ class MacroAssemblerMIPS64Compat : public MacroAssemblerMIPS64
|
|||
|
||||
void convertUInt64ToDouble(Register src, FloatRegister dest);
|
||||
|
||||
void wasmTruncateToI64(FloatRegister input, Register output, MIRType fromType,
|
||||
bool isUnsigned, Label* oolEntry, Label* oolRejoin);
|
||||
|
||||
void breakpoint();
|
||||
|
||||
|
|
|
@ -1573,23 +1573,34 @@ Simulator::setFCSRRoundError(double original, double rounded)
|
|||
{
|
||||
bool ret = false;
|
||||
|
||||
setFCSRBit(kFCSRInexactCauseBit, false);
|
||||
setFCSRBit(kFCSRUnderflowCauseBit, false);
|
||||
setFCSRBit(kFCSROverflowCauseBit, false);
|
||||
setFCSRBit(kFCSRInvalidOpCauseBit, false);
|
||||
|
||||
if (!std::isfinite(original) || !std::isfinite(rounded)) {
|
||||
setFCSRBit(kFCSRInvalidOpFlagBit, true);
|
||||
setFCSRBit(kFCSRInvalidOpCauseBit, true);
|
||||
ret = true;
|
||||
}
|
||||
|
||||
if (original != rounded)
|
||||
if (original != rounded) {
|
||||
setFCSRBit(kFCSRInexactFlagBit, true);
|
||||
setFCSRBit(kFCSRInexactCauseBit, true);
|
||||
}
|
||||
|
||||
if (rounded < DBL_MIN && rounded > -DBL_MIN && rounded != 0) {
|
||||
setFCSRBit(kFCSRUnderflowFlagBit, true);
|
||||
setFCSRBit(kFCSRUnderflowCauseBit, true);
|
||||
ret = true;
|
||||
}
|
||||
|
||||
if (rounded > INT_MAX || rounded < INT_MIN) {
|
||||
setFCSRBit(kFCSROverflowFlagBit, true);
|
||||
setFCSRBit(kFCSROverflowCauseBit, true);
|
||||
// The reference is not really clear but it seems this is required:
|
||||
setFCSRBit(kFCSRInvalidOpFlagBit, true);
|
||||
setFCSRBit(kFCSRInvalidOpCauseBit, true);
|
||||
ret = true;
|
||||
}
|
||||
|
||||
|
@ -1617,15 +1628,15 @@ Simulator::get_pc() const
|
|||
return registers_[pc];
|
||||
}
|
||||
|
||||
void
|
||||
Simulator::startInterrupt(JitActivation* activation)
|
||||
JS::ProfilingFrameIterator::RegisterState
|
||||
Simulator::registerState()
|
||||
{
|
||||
JS::ProfilingFrameIterator::RegisterState state;
|
||||
wasm::RegisterState state;
|
||||
state.pc = (void*) get_pc();
|
||||
state.fp = (void*) getRegister(fp);
|
||||
state.sp = (void*) getRegister(sp);
|
||||
state.lr = (void*) getRegister(ra);
|
||||
activation->startWasmInterrupt(state);
|
||||
return state;
|
||||
}
|
||||
|
||||
// The signal handler only redirects the PC to the interrupt stub when the PC is
|
||||
|
@ -1651,7 +1662,9 @@ Simulator::handleWasmInterrupt()
|
|||
if (!fp)
|
||||
return;
|
||||
|
||||
startInterrupt(activation);
|
||||
if (!activation->startWasmInterrupt(registerState()))
|
||||
return;
|
||||
|
||||
set_pc(int64_t(segment->asModule()->interruptCode()));
|
||||
}
|
||||
|
||||
|
@ -1681,14 +1694,19 @@ Simulator::handleWasmFault(uint64_t addr, unsigned numBytes)
|
|||
const wasm::ModuleSegment* moduleSegment = segment->asModule();
|
||||
|
||||
wasm::Instance* instance = wasm::LookupFaultingInstance(*moduleSegment, pc, fp);
|
||||
if (!instance || !instance->memoryAccessInGuardRegion((uint8_t*)addr, numBytes))
|
||||
if (!instance)
|
||||
return false;
|
||||
|
||||
MOZ_RELEASE_ASSERT(&instance->code() == &moduleSegment->code());
|
||||
|
||||
if (!instance->memoryAccessInGuardRegion((uint8_t*)addr, numBytes))
|
||||
return false;
|
||||
|
||||
LLBit_ = false;
|
||||
|
||||
const wasm::MemoryAccess* memoryAccess = instance->code().lookupMemoryAccess(pc);
|
||||
if (!memoryAccess) {
|
||||
startInterrupt(act);
|
||||
MOZ_ALWAYS_TRUE(act->startWasmInterrupt(registerState()));
|
||||
if (!instance->code().containsCodePC(pc))
|
||||
MOZ_CRASH("Cannot map PC to trap handler");
|
||||
set_pc(int64_t(moduleSegment->outOfBoundsCode()));
|
||||
|
@ -1712,7 +1730,6 @@ Simulator::handleWasmTrapFault()
|
|||
JitActivation* act = cx->activation()->asJit();
|
||||
|
||||
void* pc = reinterpret_cast<void*>(get_pc());
|
||||
uint8_t* fp = reinterpret_cast<uint8_t*>(getRegister(Register::fp));
|
||||
|
||||
const wasm::CodeSegment* segment = wasm::LookupCodeSegment(pc);
|
||||
if (!segment || !segment->isModule())
|
||||
|
@ -1724,7 +1741,7 @@ Simulator::handleWasmTrapFault()
|
|||
if (!moduleSegment->code().lookupTrap(pc, &trap, &bytecode))
|
||||
return false;
|
||||
|
||||
act->startWasmTrap(trap, bytecode.offset, pc, fp);
|
||||
act->startWasmTrap(trap, bytecode.offset, registerState());
|
||||
set_pc(int64_t(moduleSegment->trapCode()));
|
||||
return true;
|
||||
}
|
||||
|
@ -4007,9 +4024,6 @@ Simulator::instructionDecode(SimInstruction* instr)
|
|||
void
|
||||
Simulator::branchDelayInstructionDecode(SimInstruction* instr)
|
||||
{
|
||||
if (single_stepping_)
|
||||
single_step_callback_(single_step_callback_arg_, this, (void*)instr);
|
||||
|
||||
if (instr->instructionBits() == NopInst) {
|
||||
// Short-cut generic nop instructions. They are always valid and they
|
||||
// never change the simulator state.
|
||||
|
|
|
@ -35,6 +35,7 @@
|
|||
#include "mozilla/Atomics.h"
|
||||
|
||||
#include "jit/IonTypes.h"
|
||||
#include "js/ProfilingFrameIterator.h"
|
||||
#include "threading/Thread.h"
|
||||
#include "vm/MutexIDs.h"
|
||||
|
||||
|
@ -82,6 +83,12 @@ const uint32_t kFCSROverflowFlagBit = 4;
|
|||
const uint32_t kFCSRDivideByZeroFlagBit = 5;
|
||||
const uint32_t kFCSRInvalidOpFlagBit = 6;
|
||||
|
||||
const uint32_t kFCSRInexactCauseBit = 12;
|
||||
const uint32_t kFCSRUnderflowCauseBit = 13;
|
||||
const uint32_t kFCSROverflowCauseBit = 14;
|
||||
const uint32_t kFCSRDivideByZeroCauseBit = 15;
|
||||
const uint32_t kFCSRInvalidOpCauseBit = 16;
|
||||
|
||||
const uint32_t kFCSRInexactFlagMask = 1 << kFCSRInexactFlagBit;
|
||||
const uint32_t kFCSRUnderflowFlagMask = 1 << kFCSRUnderflowFlagBit;
|
||||
const uint32_t kFCSROverflowFlagMask = 1 << kFCSROverflowFlagBit;
|
||||
|
@ -314,7 +321,7 @@ class Simulator {
|
|||
|
||||
// Handle a wasm interrupt triggered by an async signal handler.
|
||||
void handleWasmInterrupt();
|
||||
void startInterrupt(JitActivation* act);
|
||||
JS::ProfilingFrameIterator::RegisterState registerState();
|
||||
|
||||
// Handle any wasm faults, returning true if the fault was handled.
|
||||
bool handleWasmFault(uint64_t addr, unsigned numBytes);
|
||||
|
|
|
@ -7788,14 +7788,16 @@ class LLoadUnboxedExpando : public LInstructionHelper<1, 1, 0>
|
|||
};
|
||||
|
||||
// Guard that a value is in a TypeSet.
|
||||
class LTypeBarrierV : public LInstructionHelper<0, BOX_PIECES, 1>
|
||||
class LTypeBarrierV : public LInstructionHelper<0, BOX_PIECES, 2>
|
||||
{
|
||||
public:
|
||||
LIR_HEADER(TypeBarrierV)
|
||||
|
||||
LTypeBarrierV(const LBoxAllocation& input, const LDefinition& temp) {
|
||||
LTypeBarrierV(const LBoxAllocation& input, const LDefinition& unboxTemp,
|
||||
const LDefinition& objTemp) {
|
||||
setBoxOperand(Input, input);
|
||||
setTemp(0, temp);
|
||||
setTemp(0, unboxTemp);
|
||||
setTemp(1, objTemp);
|
||||
}
|
||||
|
||||
static const size_t Input = 0;
|
||||
|
@ -7803,9 +7805,12 @@ class LTypeBarrierV : public LInstructionHelper<0, BOX_PIECES, 1>
|
|||
const MTypeBarrier* mir() const {
|
||||
return mir_->toTypeBarrier();
|
||||
}
|
||||
const LDefinition* temp() {
|
||||
const LDefinition* unboxTemp() {
|
||||
return getTemp(0);
|
||||
}
|
||||
const LDefinition* objTemp() {
|
||||
return getTemp(1);
|
||||
}
|
||||
};
|
||||
|
||||
// Guard that a object is in a TypeSet.
|
||||
|
@ -7830,14 +7835,16 @@ class LTypeBarrierO : public LInstructionHelper<0, 1, 1>
|
|||
};
|
||||
|
||||
// Guard that a value is in a TypeSet.
|
||||
class LMonitorTypes : public LInstructionHelper<0, BOX_PIECES, 1>
|
||||
class LMonitorTypes : public LInstructionHelper<0, BOX_PIECES, 2>
|
||||
{
|
||||
public:
|
||||
LIR_HEADER(MonitorTypes)
|
||||
|
||||
LMonitorTypes(const LBoxAllocation& input, const LDefinition& temp) {
|
||||
LMonitorTypes(const LBoxAllocation& input, const LDefinition& unboxTemp,
|
||||
const LDefinition& objTemp) {
|
||||
setBoxOperand(Input, input);
|
||||
setTemp(0, temp);
|
||||
setTemp(0, unboxTemp);
|
||||
setTemp(1, objTemp);
|
||||
}
|
||||
|
||||
static const size_t Input = 0;
|
||||
|
@ -7845,9 +7852,12 @@ class LMonitorTypes : public LInstructionHelper<0, BOX_PIECES, 1>
|
|||
const MMonitorTypes* mir() const {
|
||||
return mir_->toMonitorTypes();
|
||||
}
|
||||
const LDefinition* temp() {
|
||||
const LDefinition* unboxTemp() {
|
||||
return getTemp(0);
|
||||
}
|
||||
const LDefinition* objTemp() {
|
||||
return getTemp(1);
|
||||
}
|
||||
};
|
||||
|
||||
// Generational write barrier used when writing an object to another object.
|
||||
|
|
|
@ -1427,6 +1427,14 @@ static const LiveRegisterSet AllRegsExceptPCSP(
|
|||
(uint32_t(1) << Registers::pc))),
|
||||
FloatRegisterSet(FloatRegisters::AllDoubleMask));
|
||||
static_assert(!SupportsSimd, "high lanes of SIMD registers need to be saved too.");
|
||||
#elif defined(JS_CODEGEN_MIPS32) || defined(JS_CODEGEN_MIPS64)
|
||||
static const LiveRegisterSet AllUserRegsExceptSP(
|
||||
GeneralRegisterSet(Registers::AllMask & ~((uint32_t(1) << Registers::k0) |
|
||||
(uint32_t(1) << Registers::k1) |
|
||||
(uint32_t(1) << Registers::sp) |
|
||||
(uint32_t(1) << Registers::zero))),
|
||||
FloatRegisterSet(FloatRegisters::AllDoubleMask));
|
||||
static_assert(!SupportsSimd, "high lanes of SIMD registers need to be saved too.");
|
||||
#else
|
||||
static const LiveRegisterSet AllRegsExceptSP(
|
||||
GeneralRegisterSet(Registers::AllMask & ~(uint32_t(1) << Registers::StackPointer)),
|
||||
|
@ -1487,14 +1495,16 @@ GenerateInterruptExit(MacroAssembler& masm, Label* throwLabel, Offsets* offsets)
|
|||
#elif defined(JS_CODEGEN_MIPS32) || defined(JS_CODEGEN_MIPS64)
|
||||
// Reserve space to store resumePC and HeapReg.
|
||||
masm.subFromStackPtr(Imm32(2 * sizeof(intptr_t)));
|
||||
// set to zero so we can use masm.framePushed() below.
|
||||
// Set to zero so we can use masm.framePushed() below.
|
||||
masm.setFramePushed(0);
|
||||
static_assert(!SupportsSimd, "high lanes of SIMD registers need to be saved too.");
|
||||
// save all registers,except sp. After this stack is alligned.
|
||||
masm.PushRegsInMask(AllRegsExceptSP);
|
||||
|
||||
// Save the stack pointer in a non-volatile register.
|
||||
// Save all registers, except sp.
|
||||
masm.PushRegsInMask(AllUserRegsExceptSP);
|
||||
|
||||
// Save the stack pointer and FCSR in a non-volatile registers.
|
||||
masm.moveStackPtrTo(s0);
|
||||
masm.as_cfc1(s1, Assembler::FCSR);
|
||||
|
||||
// Align the stack.
|
||||
masm.ma_and(StackPointer, StackPointer, Imm32(~(ABIStackAlignment - 1)));
|
||||
|
||||
|
@ -1509,19 +1519,18 @@ GenerateInterruptExit(MacroAssembler& masm, Label* throwLabel, Offsets* offsets)
|
|||
masm.assertStackAlignment(ABIStackAlignment);
|
||||
masm.call(SymbolicAddress::HandleExecutionInterrupt);
|
||||
|
||||
# ifdef USES_O32_ABI
|
||||
masm.addToStackPtr(Imm32(4 * sizeof(intptr_t)));
|
||||
# endif
|
||||
|
||||
masm.branchTestPtr(Assembler::Zero, ReturnReg, ReturnReg, throwLabel);
|
||||
|
||||
// This will restore stack to the address before the call.
|
||||
masm.moveToStackPtr(s0);
|
||||
|
||||
// Restore FCSR.
|
||||
masm.as_ctc1(s1, Assembler::FCSR);
|
||||
|
||||
// Store resumePC into the reserved space.
|
||||
masm.storePtr(ReturnReg, Address(s0, masm.framePushed()));
|
||||
|
||||
masm.PopRegsInMask(AllRegsExceptSP);
|
||||
masm.PopRegsInMask(AllUserRegsExceptSP);
|
||||
|
||||
// Pop resumePC into PC. Clobber HeapReg to make the jump and restore it
|
||||
// during jump delay slot.
|
||||
|
|
|
@ -137,7 +137,7 @@ class ReftestRunner(MozbuildObject):
|
|||
args.printDeviceInfo = False
|
||||
|
||||
from mozrunner.devices.android_device import grant_runtime_permissions, get_adb_path
|
||||
grant_runtime_permissions(self)
|
||||
grant_runtime_permissions(self, args.app)
|
||||
|
||||
if not args.adb_path:
|
||||
args.adb_path = get_adb_path(self)
|
||||
|
|
|
@ -860,6 +860,10 @@ public abstract class GeckoApp extends GeckoActivity
|
|||
public void onFocusRequest(final GeckoSession session) {
|
||||
}
|
||||
|
||||
@Override // GeckoSession.ContentListener
|
||||
public void onCloseRequest(final GeckoSession session) {
|
||||
}
|
||||
|
||||
@Override // GeckoSession.ContentListener
|
||||
public void onFullScreen(final GeckoSession session, final boolean fullScreen) {
|
||||
if (fullScreen) {
|
||||
|
|
|
@ -649,6 +649,13 @@ public class CustomTabsActivity extends AppCompatActivity
|
|||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNewSession(final GeckoSession session, final String uri,
|
||||
final GeckoSession.Response<GeckoSession> response) {
|
||||
// We should never get here because we abort loads that need a new session in onLoadUri()
|
||||
throw new IllegalStateException("Unexpected new session");
|
||||
}
|
||||
|
||||
/* GeckoSession.ProgressListener */
|
||||
@Override
|
||||
public void onPageStart(GeckoSession session, String url) {
|
||||
|
@ -686,6 +693,11 @@ public class CustomTabsActivity extends AppCompatActivity
|
|||
startActivity(intent);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCloseRequest(GeckoSession session) {
|
||||
// Ignore
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFullScreen(GeckoSession session, boolean fullScreen) {
|
||||
ActivityUtils.setFullScreen(this, fullScreen);
|
||||
|
|
|
@ -356,6 +356,11 @@ public class WebAppActivity extends AppCompatActivity
|
|||
startActivity(intent);
|
||||
}
|
||||
|
||||
@Override // GeckoSession.ContentListener
|
||||
public void onCloseRequest(GeckoSession session) {
|
||||
// Ignore
|
||||
}
|
||||
|
||||
@Override // GeckoSession.ContentListener
|
||||
public void onContextMenu(GeckoSession session, int screenX, int screenY,
|
||||
String uri, String elementSrc) {
|
||||
|
@ -422,6 +427,13 @@ public class WebAppActivity extends AppCompatActivity
|
|||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNewSession(final GeckoSession session, final String uri,
|
||||
final GeckoSession.Response<GeckoSession> response) {
|
||||
// We should never get here because we abort loads that need a new session in onLoadUri()
|
||||
throw new IllegalStateException("Unexpected new session");
|
||||
}
|
||||
|
||||
private void updateFullScreen() {
|
||||
boolean fullScreen = mIsFullScreenContent || mIsFullScreenMode;
|
||||
if (ActivityUtils.isFullScreen(this) == fullScreen) {
|
||||
|
|
|
@ -24,6 +24,7 @@ class GeckoViewContent extends GeckoViewContentModule {
|
|||
|
||||
addEventListener("DOMTitleChanged", this, false);
|
||||
addEventListener("DOMWindowFocus", this, false);
|
||||
addEventListener("DOMWindowClose", this, false);
|
||||
addEventListener("MozDOMFullscreen:Entered", this, false);
|
||||
addEventListener("MozDOMFullscreen:Exit", this, false);
|
||||
addEventListener("MozDOMFullscreen:Exited", this, false);
|
||||
|
@ -43,6 +44,7 @@ class GeckoViewContent extends GeckoViewContentModule {
|
|||
|
||||
removeEventListener("DOMTitleChanged", this);
|
||||
removeEventListener("DOMWindowFocus", this);
|
||||
removeEventListener("DOMWindowClose", this);
|
||||
removeEventListener("MozDOMFullscreen:Entered", this);
|
||||
removeEventListener("MozDOMFullscreen:Exit", this);
|
||||
removeEventListener("MozDOMFullscreen:Exited", this);
|
||||
|
@ -179,6 +181,16 @@ class GeckoViewContent extends GeckoViewContentModule {
|
|||
type: "GeckoView:DOMWindowFocus"
|
||||
});
|
||||
break;
|
||||
case "DOMWindowClose":
|
||||
if (!aEvent.isTrusted) {
|
||||
return;
|
||||
}
|
||||
|
||||
aEvent.preventDefault();
|
||||
this.eventDispatcher.sendRequest({
|
||||
type: "GeckoView:DOMWindowClose"
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,6 +9,8 @@ ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
|
|||
|
||||
ChromeUtils.defineModuleGetter(this, "EventDispatcher",
|
||||
"resource://gre/modules/Messaging.jsm");
|
||||
ChromeUtils.defineModuleGetter(this, "Services",
|
||||
"resource://gre/modules/Services.jsm");
|
||||
XPCOMUtils.defineLazyGetter(this, "WindowEventDispatcher",
|
||||
() => EventDispatcher.for(window));
|
||||
|
||||
|
@ -23,8 +25,8 @@ XPCOMUtils.defineLazyGetter(this, "dump", () =>
|
|||
// and remove by calling
|
||||
// remove(<type name>)
|
||||
var ModuleManager = {
|
||||
init: function() {
|
||||
this.browser = document.getElementById("content");
|
||||
init: function(aBrowser) {
|
||||
this.browser = aBrowser;
|
||||
this.modules = {};
|
||||
},
|
||||
|
||||
|
@ -45,8 +47,24 @@ var ModuleManager = {
|
|||
}
|
||||
};
|
||||
|
||||
function createBrowser() {
|
||||
const browser = window.browser = document.createElement("browser");
|
||||
browser.setAttribute("type", "content");
|
||||
browser.setAttribute("primary", "true");
|
||||
browser.setAttribute("flex", "1");
|
||||
|
||||
// There may be a GeckoViewNavigation module in another window waiting for us to
|
||||
// create a browser so it can call presetOpenerWindow(), so allow them to do that now.
|
||||
Services.obs.notifyObservers(window, "geckoview-window-created");
|
||||
window.document.getElementById("main-window").appendChild(browser);
|
||||
|
||||
browser.stop();
|
||||
return browser;
|
||||
}
|
||||
|
||||
function startup() {
|
||||
ModuleManager.init();
|
||||
const browser = createBrowser();
|
||||
ModuleManager.init(browser);
|
||||
|
||||
// GeckoViewNavigation needs to go first because nsIDOMBrowserWindow must set up
|
||||
// before the first remote browser. Bug 1365364.
|
||||
|
@ -69,5 +87,5 @@ function startup() {
|
|||
|
||||
// Move focus to the content window at the end of startup,
|
||||
// so things like text selection can work properly.
|
||||
document.getElementById("content").focus();
|
||||
browser.focus();
|
||||
}
|
||||
|
|
|
@ -3,14 +3,9 @@
|
|||
- License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
|
||||
|
||||
<?xml-stylesheet href="chrome://browser/content/browser.css" type="text/css"?>
|
||||
|
||||
<window id="main-window"
|
||||
onload="startup();"
|
||||
windowtype="navigator:geckoview"
|
||||
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
|
||||
|
||||
<browser id="content" type="content" primary="true" src="about:blank" flex="1"/>
|
||||
|
||||
<script type="application/javascript" src="chrome://geckoview/content/geckoview.js"/>
|
||||
</window>
|
||||
|
|
|
@ -17,10 +17,28 @@ GeckoViewStartup.prototype = {
|
|||
|
||||
QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver]),
|
||||
|
||||
/**
|
||||
* Register resource://android as the APK root.
|
||||
*
|
||||
* Consumers can access Android assets using resource://android/assets/FILENAME.
|
||||
*/
|
||||
setResourceSubstitutions: function() {
|
||||
let registry = Cc["@mozilla.org/chrome/chrome-registry;1"].getService(Ci.nsIChromeRegistry);
|
||||
// Like jar:jar:file:///data/app/org.mozilla.geckoview.test.apk!/assets/omni.ja!/chrome/geckoview/content/geckoview.js
|
||||
let url = registry.convertChromeURL(Services.io.newURI("chrome://geckoview/content/geckoview.js")).spec;
|
||||
// Like jar:file:///data/app/org.mozilla.geckoview.test.apk!/
|
||||
url = url.substring(4, url.indexOf("!/") + 2);
|
||||
|
||||
let protocolHandler = Services.io.getProtocolHandler("resource").QueryInterface(Ci.nsIResProtocolHandler);
|
||||
protocolHandler.setSubstitution("android", Services.io.newURI(url));
|
||||
},
|
||||
|
||||
/* ---------- nsIObserver ---------- */
|
||||
observe: function(aSubject, aTopic, aData) {
|
||||
switch (aTopic) {
|
||||
case "app-startup": {
|
||||
this.setResourceSubstitutions();
|
||||
|
||||
// Parent and content process.
|
||||
Services.obs.addObserver(this, "chrome-document-global-created");
|
||||
Services.obs.addObserver(this, "content-document-global-created");
|
||||
|
|
|
@ -50,6 +50,8 @@ android {
|
|||
versionName "${mozconfig.substs.MOZ_APP_VERSION}-${mozconfig.substs.MOZ_UPDATE_CHANNEL}"
|
||||
consumerProguardFiles 'proguard-rules.txt'
|
||||
|
||||
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
|
||||
|
||||
// TODO: ensure these fields always agree with mobile/android/geckoview/BuildConfig.java.in,
|
||||
// either by diffing the processed files or by generating the output from a single source.
|
||||
buildConfigField 'String', "GRE_MILESTONE", "\"${mozconfig.substs.GRE_MILESTONE}\""
|
||||
|
@ -152,6 +154,11 @@ dependencies {
|
|||
testImplementation 'junit:junit:4.12'
|
||||
testImplementation 'org.robolectric:robolectric:3.5.1'
|
||||
testImplementation 'org.mockito:mockito-core:1.10.19'
|
||||
|
||||
androidTestImplementation 'com.android.support.test:runner:0.5'
|
||||
androidTestImplementation 'com.android.support.test:rules:0.5'
|
||||
androidTestImplementation 'com.android.support.test.espresso:espresso-core:2.2.2'
|
||||
androidTestImplementation "com.android.support:support-annotations:${mozconfig.substs.ANDROID_SUPPORT_LIBRARY_VERSION}"
|
||||
}
|
||||
|
||||
apply from: "${topsrcdir}/mobile/android/gradle/with_gecko_binaries.gradle"
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="org.mozilla.geckoview.test">
|
||||
|
||||
<application
|
||||
android:allowBackup="true"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
android:supportsRtl="true"
|
||||
android:theme="@style/AppTheme">
|
||||
<activity android:name=".TestRunnerActivity" android:exported="true"/>
|
||||
<activity-alias android:name=".App" android:targetActivity=".TestRunnerActivity">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN"/>
|
||||
<category android:name="android.intent.category.DEFAULT"/>
|
||||
<category android:name="android.intent.category.LAUNCHER"/>
|
||||
</intent-filter>
|
||||
</activity-alias>
|
||||
</application>
|
||||
|
||||
</manifest>
|
|
@ -0,0 +1,6 @@
|
|||
<html>
|
||||
<head><title>Hello, world!</title></head>
|
||||
<body>
|
||||
<p>Hello, world!</p>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,6 @@
|
|||
<html>
|
||||
<head><title>Hello, world! Again!</title></head>
|
||||
<body>
|
||||
<p>Hello, world! Again!</p>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,20 @@
|
|||
<html>
|
||||
<head><title>Hello, world!</title></head>
|
||||
<body>
|
||||
<p id="message"></p>
|
||||
<script>
|
||||
const msg = document.getElementById("message");
|
||||
msg.innerText = "Waiting for click...";
|
||||
window.addEventListener("click", function() {
|
||||
msg.innerText = "Opening window....";
|
||||
try {
|
||||
const win = window.open("newSession_child.html");
|
||||
|
||||
msg.innerText = "Opened window: " + win;
|
||||
} catch (e) {
|
||||
msg.innerText = "Failed to open window: " + e;
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,11 @@
|
|||
<html>
|
||||
<head><title>Hello, world!</title></head>
|
||||
<body>
|
||||
<p>I'm the child</p>
|
||||
<script>
|
||||
setTimeout(function() {
|
||||
window.close();
|
||||
}, 1000);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,118 @@
|
|||
/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
|
||||
* 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/. */
|
||||
|
||||
package org.mozilla.geckoview.test;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.mozilla.gecko.GeckoSession;
|
||||
import org.mozilla.gecko.GeckoView;
|
||||
|
||||
import android.net.Uri;
|
||||
import android.os.SystemClock;
|
||||
import android.support.test.espresso.Espresso;
|
||||
import android.support.test.espresso.IdlingResource;
|
||||
import android.support.test.espresso.assertion.ViewAssertions;
|
||||
import android.support.test.espresso.matcher.ViewMatchers;
|
||||
import android.support.test.rule.ActivityTestRule;
|
||||
import android.view.MotionEvent;
|
||||
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
public class BaseGeckoViewTest {
|
||||
protected GeckoSession mSession;
|
||||
protected GeckoView mView;
|
||||
private ExplicitIdlingResource mIdlingResource;
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
mView = mActivityRule.getActivity().getGeckoView();
|
||||
mSession = mActivityRule.getActivity().getGeckoSession();
|
||||
|
||||
mIdlingResource = new ExplicitIdlingResource();
|
||||
Espresso.registerIdlingResources(mIdlingResource);
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() {
|
||||
Espresso.unregisterIdlingResources(mIdlingResource);
|
||||
}
|
||||
|
||||
@Rule
|
||||
public ActivityTestRule<TestRunnerActivity> mActivityRule = new ActivityTestRule<>(
|
||||
TestRunnerActivity.class);
|
||||
|
||||
protected void waitUntilDone() {
|
||||
Espresso.onView(ViewMatchers.isRoot()).check(ViewAssertions.matches(ViewMatchers.isRoot()));
|
||||
}
|
||||
|
||||
protected void done() {
|
||||
mIdlingResource.done();
|
||||
}
|
||||
|
||||
protected String buildAssetUrl(String path) {
|
||||
return "resource://android/assets/www/" + path;
|
||||
}
|
||||
|
||||
protected void loadTestPath(final String path, Runnable finished) {
|
||||
loadTestPage(buildAssetUrl(path), finished);
|
||||
}
|
||||
|
||||
protected void loadTestPage(final String url, final Runnable finished) {
|
||||
final String path = Uri.parse(url).getPath();
|
||||
mSession.setProgressListener(new GeckoSession.ProgressListener() {
|
||||
@Override
|
||||
public void onPageStart(GeckoSession session, String loadingUrl) {
|
||||
assertTrue("Loaded url should end with " + path, loadingUrl.endsWith(path));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPageStop(GeckoSession session, boolean success) {
|
||||
assertTrue("Load should succeed", success);
|
||||
mSession.setProgressListener(null);
|
||||
finished.run();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSecurityChange(GeckoSession session, SecurityInformation securityInfo) {
|
||||
}
|
||||
});
|
||||
|
||||
mSession.loadUri(url);
|
||||
}
|
||||
|
||||
protected void sendClick(int x, int y) {
|
||||
mSession.getPanZoomController().onTouchEvent(MotionEvent.obtain(SystemClock.uptimeMillis(), SystemClock.uptimeMillis(), MotionEvent.ACTION_DOWN, 5, 5, 0));
|
||||
mSession.getPanZoomController().onTouchEvent(MotionEvent.obtain(SystemClock.uptimeMillis(), SystemClock.uptimeMillis(), MotionEvent.ACTION_UP, 5, 5, 0));
|
||||
}
|
||||
|
||||
private class ExplicitIdlingResource implements IdlingResource {
|
||||
private boolean mIsIdle;
|
||||
private ResourceCallback mCallback;
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "Explicit Idling Resource";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isIdleNow() {
|
||||
return mIsIdle;
|
||||
}
|
||||
|
||||
public void done() {
|
||||
mIsIdle = true;
|
||||
if (mCallback != null) {
|
||||
mCallback.onTransitionToIdle();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerIdleTransitionCallback(ResourceCallback callback) {
|
||||
mCallback = callback;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,298 @@
|
|||
/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
|
||||
* 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/. */
|
||||
|
||||
package org.mozilla.geckoview.test;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mozilla.gecko.GeckoSession;
|
||||
|
||||
import android.support.test.InstrumentationRegistry;
|
||||
import android.support.test.runner.AndroidJUnit4;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class NavigationTests extends BaseGeckoViewTest {
|
||||
@Test
|
||||
public void testLoadUri() {
|
||||
loadTestPath("hello.html", new Runnable() {
|
||||
@Override public void run() {
|
||||
done();
|
||||
}
|
||||
});
|
||||
|
||||
waitUntilDone();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGoBack() {
|
||||
final String startPath = "hello.html";
|
||||
loadTestPath(startPath, new Runnable() {
|
||||
@Override public void run() {
|
||||
loadTestPath("hello2.html", new Runnable() {
|
||||
@Override public void run() {
|
||||
mSession.setNavigationListener(new GeckoSession.NavigationListener() {
|
||||
@Override
|
||||
public void onLocationChange(GeckoSession session, String url) {
|
||||
assertTrue("URL should end with " + startPath + ", got " + url, url.endsWith(startPath));
|
||||
done();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCanGoBack(GeckoSession session, boolean canGoBack) {
|
||||
assertFalse("Should not be able to go back", canGoBack);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCanGoForward(GeckoSession session, boolean canGoForward) {
|
||||
assertTrue("Should be able to go forward", canGoForward);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onLoadUri(GeckoSession session, String uri, TargetWindow where) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNewSession(GeckoSession session, String uri, GeckoSession.Response<GeckoSession> response) {
|
||||
response.respond(null);
|
||||
}
|
||||
});
|
||||
|
||||
mSession.goBack();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
waitUntilDone();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReload() {
|
||||
loadTestPath("hello.html", new Runnable() {
|
||||
@Override public void run() {
|
||||
mSession.setProgressListener(new GeckoSession.ProgressListener() {
|
||||
@Override
|
||||
public void onPageStart(GeckoSession session, String url) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPageStop(GeckoSession session, boolean success) {
|
||||
assertTrue(success);
|
||||
done();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSecurityChange(GeckoSession session, SecurityInformation securityInfo) {
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
mSession.reload();
|
||||
}
|
||||
});
|
||||
|
||||
waitUntilDone();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExpiredCert() {
|
||||
mSession.setProgressListener(new GeckoSession.ProgressListener() {
|
||||
private boolean mNotBlank;
|
||||
|
||||
@Override
|
||||
public void onPageStart(GeckoSession session, String url) {
|
||||
mNotBlank = !url.equals("about:blank");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPageStop(GeckoSession session, boolean success) {
|
||||
if (mNotBlank) {
|
||||
assertFalse("Expected unsuccessful page load", success);
|
||||
done();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSecurityChange(GeckoSession session, SecurityInformation securityInfo) {
|
||||
assertFalse(securityInfo.isSecure);
|
||||
assertEquals(securityInfo.securityMode, SecurityInformation.SECURITY_MODE_UNKNOWN);
|
||||
}
|
||||
});
|
||||
|
||||
mSession.loadUri("https://expired.badssl.com/");
|
||||
waitUntilDone();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testValidTLS() {
|
||||
mSession.setProgressListener(new GeckoSession.ProgressListener() {
|
||||
private boolean mNotBlank;
|
||||
|
||||
@Override
|
||||
public void onPageStart(GeckoSession session, String url) {
|
||||
mNotBlank = !url.equals("about:blank");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPageStop(GeckoSession session, boolean success) {
|
||||
if (mNotBlank) {
|
||||
assertTrue("Expected successful page load", success);
|
||||
done();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSecurityChange(GeckoSession session, SecurityInformation securityInfo) {
|
||||
assertTrue(securityInfo.isSecure);
|
||||
assertEquals(securityInfo.securityMode, SecurityInformation.SECURITY_MODE_IDENTIFIED);
|
||||
}
|
||||
});
|
||||
|
||||
mSession.loadUri("https://mozilla-modern.badssl.com/");
|
||||
waitUntilDone();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOnNewSession() {
|
||||
mSession.setNavigationListener(new GeckoSession.NavigationListener() {
|
||||
@Override
|
||||
public void onLocationChange(GeckoSession session, String url) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCanGoBack(GeckoSession session, boolean canGoBack) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCanGoForward(GeckoSession session, boolean canGoForward) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onLoadUri(GeckoSession session, String uri, TargetWindow where) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNewSession(GeckoSession session, String uri, GeckoSession.Response<GeckoSession> response) {
|
||||
final GeckoSession newSession = new GeckoSession(session.getSettings());
|
||||
newSession.setContentListener(new GeckoSession.ContentListener() {
|
||||
@Override
|
||||
public void onTitleChange(GeckoSession session, String title) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFocusRequest(GeckoSession session) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCloseRequest(GeckoSession session) {
|
||||
session.closeWindow();
|
||||
done();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFullScreen(GeckoSession session, boolean fullScreen) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onContextMenu(GeckoSession session, int screenX, int screenY, String uri, String elementSrc) {
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
newSession.openWindow(InstrumentationRegistry.getTargetContext());
|
||||
response.respond(newSession);
|
||||
}
|
||||
});
|
||||
|
||||
mSession.setProgressListener(new GeckoSession.ProgressListener() {
|
||||
@Override
|
||||
public void onPageStart(GeckoSession session, String url) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPageStop(GeckoSession session, boolean success) {
|
||||
// Send a click to open the window
|
||||
sendClick(100, 100);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSecurityChange(GeckoSession session, SecurityInformation securityInfo) {
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
mSession.loadUri(buildAssetUrl("newSession.html"));
|
||||
|
||||
waitUntilDone();
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void testOnNewSessionNoExisting() {
|
||||
// This makes sure that we get an exception if you try to return
|
||||
// an existing GeckoSession instance from the NavigationListener.onNewSession()
|
||||
// implementation.
|
||||
|
||||
mSession.setNavigationListener(new GeckoSession.NavigationListener() {
|
||||
@Override
|
||||
public void onLocationChange(GeckoSession session, String url) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCanGoBack(GeckoSession session, boolean canGoBack) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCanGoForward(GeckoSession session, boolean canGoForward) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onLoadUri(GeckoSession session, String uri, TargetWindow where) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNewSession(GeckoSession session, String uri, GeckoSession.Response<GeckoSession> response) {
|
||||
// This is where the throw should occur
|
||||
response.respond(mSession);
|
||||
}
|
||||
});
|
||||
|
||||
mSession.setProgressListener(new GeckoSession.ProgressListener() {
|
||||
@Override
|
||||
public void onPageStart(GeckoSession session, String url) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPageStop(GeckoSession session, boolean success) {
|
||||
sendClick(100, 100);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSecurityChange(GeckoSession session, SecurityInformation securityInfo) {
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
mSession.loadUri(buildAssetUrl("newSession.html"));
|
||||
waitUntilDone();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,139 @@
|
|||
package org.mozilla.geckoview.test;
|
||||
|
||||
import org.mozilla.gecko.GeckoSession;
|
||||
import org.mozilla.gecko.GeckoSessionSettings;
|
||||
import org.mozilla.gecko.GeckoView;
|
||||
import org.mozilla.gecko.mozglue.GeckoLoader;
|
||||
import org.mozilla.gecko.mozglue.SafeIntent;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
|
||||
public class TestRunnerActivity extends Activity {
|
||||
private static final String LOGTAG = "TestRunnerActivity";
|
||||
|
||||
GeckoSession mSession;
|
||||
GeckoView mView;
|
||||
|
||||
private GeckoSession.NavigationListener mNavigationListener = new GeckoSession.NavigationListener() {
|
||||
@Override
|
||||
public void onLocationChange(GeckoSession session, String url) {
|
||||
getActionBar().setSubtitle(url);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCanGoBack(GeckoSession session, boolean canGoBack) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCanGoForward(GeckoSession session, boolean canGoForward) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onLoadUri(GeckoSession session, String uri, TargetWindow where) {
|
||||
// Allow Gecko to load all URIs
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNewSession(GeckoSession session, String uri, GeckoSession.Response<GeckoSession> response) {
|
||||
response.respond(createSession(session.getSettings()));
|
||||
}
|
||||
};
|
||||
|
||||
private GeckoSession.ContentListener mContentListener = new GeckoSession.ContentListener() {
|
||||
@Override
|
||||
public void onTitleChange(GeckoSession session, String title) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFocusRequest(GeckoSession session) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCloseRequest(GeckoSession session) {
|
||||
session.closeWindow();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFullScreen(GeckoSession session, boolean fullScreen) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onContextMenu(GeckoSession session, int screenX, int screenY, String uri, String elementSrc) {
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
private GeckoSession createSession() {
|
||||
return createSession(null);
|
||||
}
|
||||
|
||||
private GeckoSession createSession(GeckoSessionSettings settings) {
|
||||
if (settings == null) {
|
||||
settings = new GeckoSessionSettings();
|
||||
|
||||
// We can't use e10s because we get deadlocked when quickly creating and
|
||||
// destroying sessions. Bug 1348361.
|
||||
settings.setBoolean(GeckoSessionSettings.USE_MULTIPROCESS, false);
|
||||
}
|
||||
|
||||
final GeckoSession session = new GeckoSession(settings);
|
||||
session.setNavigationListener(mNavigationListener);
|
||||
session.openWindow(this);
|
||||
return session;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
Intent intent = getIntent();
|
||||
GeckoLoader.setLastIntent(new SafeIntent(getIntent()));
|
||||
|
||||
final String intentArgs = intent.getStringExtra("args");
|
||||
final String args = intentArgs != null ? "-purgecaches " + intentArgs : "-purgecaches";
|
||||
GeckoSession.preload(this, args, false /* no multiprocess, see below */);
|
||||
|
||||
// We can't use e10s because we get deadlocked when quickly creating and
|
||||
// destroying sessions. Bug 1348361.
|
||||
mSession = createSession();
|
||||
|
||||
// If we were passed a URI in the Intent, open it
|
||||
final Uri uri = intent.getData();
|
||||
if (uri != null) {
|
||||
mSession.loadUri(uri);
|
||||
}
|
||||
|
||||
mView = new GeckoView(this);
|
||||
mView.setSession(mSession);
|
||||
setContentView(mView);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
mSession.closeWindow();
|
||||
super.onDestroy();
|
||||
}
|
||||
|
||||
public GeckoView getGeckoView() {
|
||||
return mView;
|
||||
}
|
||||
|
||||
public GeckoSession getGeckoSession() {
|
||||
return mSession;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,77 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- 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/. -->
|
||||
<vector
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:height="108dp"
|
||||
android:width="108dp"
|
||||
android:viewportHeight="108"
|
||||
android:viewportWidth="108">
|
||||
<path android:fillColor="#26A69A"
|
||||
android:pathData="M0,0h108v108h-108z"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M9,0L9,108"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M19,0L19,108"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M29,0L29,108"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M39,0L39,108"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M49,0L49,108"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M59,0L59,108"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M69,0L69,108"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M79,0L79,108"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M89,0L89,108"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M99,0L99,108"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M0,9L108,9"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M0,19L108,19"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M0,29L108,29"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M0,39L108,39"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M0,49L108,49"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M0,59L108,59"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M0,69L108,69"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M0,79L108,79"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M0,89L108,89"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M0,99L108,99"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M19,29L89,29"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M19,39L89,39"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M19,49L89,49"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M19,59L89,59"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M19,69L89,69"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M19,79L89,79"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M29,19L29,89"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M39,19L39,89"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M49,19L49,89"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M59,19L59,89"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M69,19L69,89"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M79,19L79,89"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
</vector>
|
Двоичные данные
mobile/android/geckoview/src/androidTest/res/mipmap-hdpi/ic_launcher.png
Normal file
После Ширина: | Высота: | Размер: 3.0 KiB |
Двоичные данные
mobile/android/geckoview/src/androidTest/res/mipmap-hdpi/ic_launcher_round.png
Normal file
После Ширина: | Высота: | Размер: 4.9 KiB |
Двоичные данные
mobile/android/geckoview/src/androidTest/res/mipmap-mdpi/ic_launcher.png
Normal file
После Ширина: | Высота: | Размер: 2.0 KiB |
Двоичные данные
mobile/android/geckoview/src/androidTest/res/mipmap-mdpi/ic_launcher_round.png
Normal file
После Ширина: | Высота: | Размер: 2.8 KiB |
Двоичные данные
mobile/android/geckoview/src/androidTest/res/mipmap-xhdpi/ic_launcher.png
Normal file
После Ширина: | Высота: | Размер: 4.5 KiB |
Двоичные данные
mobile/android/geckoview/src/androidTest/res/mipmap-xhdpi/ic_launcher_round.png
Normal file
После Ширина: | Высота: | Размер: 6.9 KiB |
Двоичные данные
mobile/android/geckoview/src/androidTest/res/mipmap-xxhdpi/ic_launcher.png
Normal file
После Ширина: | Высота: | Размер: 6.3 KiB |
Двоичные данные
mobile/android/geckoview/src/androidTest/res/mipmap-xxhdpi/ic_launcher_round.png
Normal file
После Ширина: | Высота: | Размер: 10 KiB |
Двоичные данные
mobile/android/geckoview/src/androidTest/res/mipmap-xxxhdpi/ic_launcher.png
Normal file
После Ширина: | Высота: | Размер: 9.0 KiB |
Двоичные данные
mobile/android/geckoview/src/androidTest/res/mipmap-xxxhdpi/ic_launcher_round.png
Normal file
После Ширина: | Высота: | Размер: 15 KiB |
|
@ -0,0 +1,9 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- 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/. -->
|
||||
<resources>
|
||||
<color name="colorPrimary">#3F51B5</color>
|
||||
<color name="colorPrimaryDark">#303F9F</color>
|
||||
<color name="colorAccent">#FF4081</color>
|
||||
</resources>
|
|
@ -0,0 +1,7 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- 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/. -->
|
||||
<resources>
|
||||
<string name="app_name">GeckoView Test Runner</string>
|
||||
</resources>
|
|
@ -0,0 +1,11 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- 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/. -->
|
||||
|
||||
<resources>
|
||||
<!-- Base application theme. -->
|
||||
<style name="AppTheme" parent="android:Theme.Holo.Light.DarkActionBar">
|
||||
<!-- Customize your theme here. -->
|
||||
</style>
|
||||
</resources>
|
|
@ -83,6 +83,11 @@ public class GeckoThread extends Thread {
|
|||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return name();
|
||||
}
|
||||
}
|
||||
|
||||
private static final NativeQueue sNativeQueue =
|
||||
|
@ -495,13 +500,17 @@ public class GeckoThread extends Thread {
|
|||
|
||||
@WrapForJNI(calledFrom = "gecko")
|
||||
private static void setState(final State newState) {
|
||||
sNativeQueue.setState(newState);
|
||||
checkAndSetState(null, newState);
|
||||
}
|
||||
|
||||
@WrapForJNI(calledFrom = "gecko")
|
||||
private static boolean checkAndSetState(final State expectedState,
|
||||
final State newState) {
|
||||
return sNativeQueue.checkAndSetState(expectedState, newState);
|
||||
final boolean result = sNativeQueue.checkAndSetState(expectedState, newState);
|
||||
if (result) {
|
||||
Log.d(LOGTAG, "State changed to " + newState);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@WrapForJNI(stubName = "SpeculativeConnect")
|
||||
|
|
|
@ -9,6 +9,7 @@ package org.mozilla.geckoview;
|
|||
import java.net.URLConnection;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.UUID;
|
||||
|
||||
import org.mozilla.gecko.annotation.WrapForJNI;
|
||||
import org.mozilla.gecko.EventDispatcher;
|
||||
|
@ -73,6 +74,9 @@ public class GeckoSession extends LayerSession
|
|||
|
||||
private final TextInputController mTextInput = new TextInputController(this, mNativeQueue);
|
||||
|
||||
private String mId = UUID.randomUUID().toString().replace("-", "");
|
||||
/* package */ String getId() { return mId; }
|
||||
|
||||
private final GeckoSessionHandler<ContentListener> mContentHandler =
|
||||
new GeckoSessionHandler<ContentListener>(
|
||||
"GeckoViewContent", this,
|
||||
|
@ -80,6 +84,7 @@ public class GeckoSession extends LayerSession
|
|||
"GeckoView:ContextMenu",
|
||||
"GeckoView:DOMTitleChanged",
|
||||
"GeckoView:DOMWindowFocus",
|
||||
"GeckoView:DOMWindowClose",
|
||||
"GeckoView:FullScreenEnter",
|
||||
"GeckoView:FullScreenExit"
|
||||
}
|
||||
|
@ -101,6 +106,8 @@ public class GeckoSession extends LayerSession
|
|||
message.getString("title"));
|
||||
} else if ("GeckoView:DOMWindowFocus".equals(event)) {
|
||||
listener.onFocusRequest(GeckoSession.this);
|
||||
} else if ("GeckoView:DOMWindowClose".equals(event)) {
|
||||
listener.onCloseRequest(GeckoSession.this);
|
||||
} else if ("GeckoView:FullScreenEnter".equals(event)) {
|
||||
listener.onFullScreen(GeckoSession.this, true);
|
||||
} else if ("GeckoView:FullScreenExit".equals(event)) {
|
||||
|
@ -114,7 +121,8 @@ public class GeckoSession extends LayerSession
|
|||
"GeckoViewNavigation", this,
|
||||
new String[]{
|
||||
"GeckoView:LocationChange",
|
||||
"GeckoView:OnLoadUri"
|
||||
"GeckoView:OnLoadUri",
|
||||
"GeckoView:OnNewSession"
|
||||
}
|
||||
) {
|
||||
@Override
|
||||
|
@ -137,6 +145,19 @@ public class GeckoSession extends LayerSession
|
|||
final boolean result =
|
||||
listener.onLoadUri(GeckoSession.this, uri, where);
|
||||
callback.sendSuccess(result);
|
||||
} else if ("GeckoView:OnNewSession".equals(event)) {
|
||||
final String uri = message.getString("uri");
|
||||
listener.onNewSession(GeckoSession.this, uri,
|
||||
new Response<GeckoSession>() {
|
||||
@Override
|
||||
public void respond(GeckoSession session) {
|
||||
if (session != null && session.isOpen() && session.isReady()) {
|
||||
throw new IllegalArgumentException("Must use a new GeckoSession instance");
|
||||
}
|
||||
|
||||
callback.sendSuccess(session != null ? session.getId() : null);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -353,7 +374,7 @@ public class GeckoSession extends LayerSession
|
|||
public static native void open(Window instance, Compositor compositor,
|
||||
EventDispatcher dispatcher,
|
||||
GeckoBundle settings, String chromeUri,
|
||||
int screenId, boolean privateMode);
|
||||
int screenId, boolean privateMode, String id);
|
||||
|
||||
@Override // JNIObject
|
||||
protected void disposeNative() {
|
||||
|
@ -434,7 +455,7 @@ public class GeckoSession extends LayerSession
|
|||
private GeckoSessionSettings mSettings;
|
||||
|
||||
public GeckoSession() {
|
||||
this(/* settings */ null);
|
||||
this(null);
|
||||
}
|
||||
|
||||
public GeckoSession(final GeckoSessionSettings settings) {
|
||||
|
@ -447,13 +468,15 @@ public class GeckoSession extends LayerSession
|
|||
mListener.registerListeners();
|
||||
}
|
||||
|
||||
private void transferFrom(final Window window, final GeckoSessionSettings settings) {
|
||||
private void transferFrom(final Window window, final GeckoSessionSettings settings,
|
||||
final String id) {
|
||||
if (isOpen()) {
|
||||
throw new IllegalStateException("Session is open");
|
||||
}
|
||||
|
||||
mWindow = window;
|
||||
mSettings = new GeckoSessionSettings(settings, this);
|
||||
mId = id;
|
||||
|
||||
if (mWindow != null) {
|
||||
if (GeckoThread.isStateAtLeast(GeckoThread.State.PROFILE_READY)) {
|
||||
|
@ -471,7 +494,7 @@ public class GeckoSession extends LayerSession
|
|||
}
|
||||
|
||||
/* package */ void transferFrom(final GeckoSession session) {
|
||||
transferFrom(session.mWindow, session.mSettings);
|
||||
transferFrom(session.mWindow, session.mSettings, session.mId);
|
||||
session.mWindow = null;
|
||||
session.onWindowChanged();
|
||||
}
|
||||
|
@ -485,6 +508,7 @@ public class GeckoSession extends LayerSession
|
|||
public void writeToParcel(Parcel out, int flags) {
|
||||
out.writeStrongInterface(mWindow);
|
||||
out.writeParcelable(mSettings, flags);
|
||||
out.writeString(mId);
|
||||
}
|
||||
|
||||
// AIDL code may call readFromParcel even though it's not part of Parcelable.
|
||||
|
@ -495,7 +519,8 @@ public class GeckoSession extends LayerSession
|
|||
final Window window = (ifce instanceof Window) ? (Window) ifce : null;
|
||||
final GeckoSessionSettings settings =
|
||||
source.readParcelable(getClass().getClassLoader());
|
||||
transferFrom(window, settings);
|
||||
final String id = source.readString();
|
||||
transferFrom(window, settings, id);
|
||||
}
|
||||
|
||||
public static final Creator<GeckoSession> CREATOR = new Creator<GeckoSession>() {
|
||||
|
@ -546,6 +571,10 @@ public class GeckoSession extends LayerSession
|
|||
return mWindow != null;
|
||||
}
|
||||
|
||||
/* package */ boolean isReady() {
|
||||
return mNativeQueue.isReady();
|
||||
}
|
||||
|
||||
public void openWindow(final Context appContext) {
|
||||
ThreadUtils.assertOnUiThread();
|
||||
|
||||
|
@ -567,7 +596,7 @@ public class GeckoSession extends LayerSession
|
|||
|
||||
if (GeckoThread.isStateAtLeast(GeckoThread.State.PROFILE_READY)) {
|
||||
Window.open(mWindow, mCompositor, mEventDispatcher,
|
||||
mSettings.asBundle(), chromeUri, screenId, isPrivate);
|
||||
mSettings.asBundle(), chromeUri, screenId, isPrivate, mId);
|
||||
} else {
|
||||
GeckoThread.queueNativeCallUntil(
|
||||
GeckoThread.State.PROFILE_READY,
|
||||
|
@ -577,7 +606,7 @@ public class GeckoSession extends LayerSession
|
|||
EventDispatcher.class, mEventDispatcher,
|
||||
GeckoBundle.class, mSettings.asBundle(),
|
||||
String.class, chromeUri,
|
||||
screenId, isPrivate);
|
||||
screenId, isPrivate, mId);
|
||||
}
|
||||
|
||||
onWindowChanged();
|
||||
|
@ -762,7 +791,7 @@ public class GeckoSession extends LayerSession
|
|||
/**
|
||||
* Set the tracking protection callback handler.
|
||||
* This will replace the current handler.
|
||||
* @param listener An implementation of TrackingProtectionDelegate.
|
||||
* @param delegate An implementation of TrackingProtectionDelegate.
|
||||
*/
|
||||
public void setTrackingProtectionDelegate(TrackingProtectionDelegate delegate) {
|
||||
mTrackingProtectionHandler.setListener(delegate, this);
|
||||
|
@ -1278,6 +1307,12 @@ public class GeckoSession extends LayerSession
|
|||
*/
|
||||
void onFocusRequest(GeckoSession session);
|
||||
|
||||
/**
|
||||
* A page has requested to close
|
||||
* @param session The GeckoSession that initiated the callback.
|
||||
*/
|
||||
void onCloseRequest(GeckoSession session);
|
||||
|
||||
/**
|
||||
* A page has entered or exited full screen mode. Typically, the implementation
|
||||
* would set the Activity containing the GeckoSession to full screen when the page is
|
||||
|
@ -1306,6 +1341,16 @@ public class GeckoSession extends LayerSession
|
|||
String uri, String elementSrc);
|
||||
}
|
||||
|
||||
/**
|
||||
* This is used to send responses in delegate methods that have asynchronous responses.
|
||||
*/
|
||||
public interface Response<T> {
|
||||
/**
|
||||
* @param val The value contained in the response
|
||||
*/
|
||||
void respond(T val);
|
||||
}
|
||||
|
||||
public interface NavigationListener {
|
||||
/**
|
||||
* A view has started loading content from the network.
|
||||
|
@ -1378,10 +1423,22 @@ public class GeckoSession extends LayerSession
|
|||
* @param uri The URI to be loaded.
|
||||
* @param where The target window.
|
||||
*
|
||||
* @return True if the URI loading has been handled, false if Gecko
|
||||
* should handle the loading.
|
||||
* @return Whether or not the load was handled. Returning false will allow Gecko
|
||||
* to continue the load as normal.
|
||||
*/
|
||||
boolean onLoadUri(GeckoSession session, String uri, TargetWindow where);
|
||||
|
||||
/**
|
||||
* A request has been made to open a new session. The URI is provided only for
|
||||
* informational purposes. Do not call GeckoSession.loadUri() here. Additionally, the
|
||||
* returned GeckoSession must be a newly-created one.
|
||||
*
|
||||
* @param session The GeckoSession that initiated the callback.
|
||||
* @param uri The URI to be loaded.
|
||||
*
|
||||
* @param response A Response which will hold the returned GeckoSession
|
||||
*/
|
||||
void onNewSession(GeckoSession session, String uri, Response<GeckoSession> response);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1877,7 +1934,7 @@ public class GeckoSession extends LayerSession
|
|||
* @param session The GeckoSession that initiated the callback.
|
||||
* @param uri The URI of the blocked element.
|
||||
* @param categories The tracker categories of the blocked element.
|
||||
* One or more of the {@link #CATEGORY_AD CATEGORY_*}
|
||||
* One or more of the {@link TrackingProtectionDelegate#CATEGORY_AD}
|
||||
* flags.
|
||||
*/
|
||||
void onTrackerBlocked(GeckoSession session, String uri, int categories);
|
||||
|
@ -1886,7 +1943,7 @@ public class GeckoSession extends LayerSession
|
|||
/**
|
||||
* Enable tracking protection.
|
||||
* @param categories The categories of trackers that should be blocked.
|
||||
* Use one or more of the {@link #CATEGORY_AD CATEGORY_*}
|
||||
* Use one or more of the {@link TrackingProtectionDelegate#CATEGORY_AD}
|
||||
* flags.
|
||||
**/
|
||||
public void enableTrackingProtection(int categories) {
|
||||
|
|
|
@ -89,6 +89,8 @@ import android.util.Log;
|
|||
|
||||
if (mListener != null) {
|
||||
handleMessage(mListener, event, message, callback);
|
||||
} else {
|
||||
callback.sendError("No listener registered");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -23,6 +23,7 @@ import org.mozilla.gecko.util.GeckoBundle;
|
|||
import org.mozilla.geckoview.GeckoSession;
|
||||
import org.mozilla.geckoview.GeckoSessionSettings;
|
||||
import org.mozilla.geckoview.GeckoSession.PermissionDelegate.MediaSource;
|
||||
import org.mozilla.geckoview.GeckoSession.Response;
|
||||
import org.mozilla.geckoview.GeckoSession.TrackingProtectionDelegate;
|
||||
import org.mozilla.geckoview.GeckoView;
|
||||
|
||||
|
@ -87,8 +88,8 @@ public class GeckoViewActivity extends Activity {
|
|||
permission.androidPermissionRequestCode = REQUEST_PERMISSIONS;
|
||||
mGeckoSession.setPermissionDelegate(permission);
|
||||
|
||||
mGeckoView.getSettings().setBoolean(GeckoSessionSettings.USE_MULTIPROCESS,
|
||||
useMultiprocess);
|
||||
mGeckoSession.getSettings().setBoolean(GeckoSessionSettings.USE_MULTIPROCESS,
|
||||
useMultiprocess);
|
||||
|
||||
mGeckoSession.enableTrackingProtection(
|
||||
TrackingProtectionDelegate.CATEGORY_AD |
|
||||
|
@ -173,11 +174,6 @@ public class GeckoViewActivity extends Activity {
|
|||
Log.i(LOGTAG, "Content title changed to " + title);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFocusRequest(GeckoSession session) {
|
||||
Log.i(LOGTAG, "Content requesting focus");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFullScreen(final GeckoSession session, final boolean fullScreen) {
|
||||
getWindow().setFlags(fullScreen ? WindowManager.LayoutParams.FLAG_FULLSCREEN : 0,
|
||||
|
@ -189,6 +185,18 @@ public class GeckoViewActivity extends Activity {
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFocusRequest(final GeckoSession session) {
|
||||
Log.i(LOGTAG, "Content requesting focus");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCloseRequest(final GeckoSession session) {
|
||||
if (session != mGeckoSession) {
|
||||
session.closeWindow();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onContextMenu(GeckoSession session, int screenX, int screenY,
|
||||
String uri, String elementSrc) {
|
||||
|
@ -346,13 +354,13 @@ public class GeckoViewActivity extends Activity {
|
|||
@Override
|
||||
public boolean onLoadUri(final GeckoSession session, final String uri,
|
||||
final TargetWindow where) {
|
||||
Log.d(LOGTAG, "onLoadUri=" + uri +
|
||||
" where=" + where);
|
||||
if (where != TargetWindow.NEW) {
|
||||
return false;
|
||||
}
|
||||
session.loadUri(uri);
|
||||
return true;
|
||||
Log.d(LOGTAG, "onLoadUri=" + uri + " where=" + where);
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNewSession(final GeckoSession session, final String uri, Response<GeckoSession> response) {
|
||||
response.respond(null);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -43,10 +43,11 @@ ext.configureVariantWithGeckoBinaries = { variant ->
|
|||
def distDir = "${topobjdir}/dist/${omnijar_dir}"
|
||||
|
||||
def syncOmnijarFromDistDir = task("syncOmnijarFromDistDirFor${variant.name.capitalize()}", type: Sync) {
|
||||
doFirst {
|
||||
onlyIf {
|
||||
if (source.empty) {
|
||||
throw new GradleException("Required omnijar not found in ${source.asPath}. Have you built and packaged?")
|
||||
throw new StopExecutionException("Required omnijar not found in ${distDir}/{omni.ja,assets/omni.ja}. Have you built and packaged?")
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
into("${project.buildDir}/moz.build/src/${variant.name}/omnijar")
|
||||
|
@ -58,10 +59,11 @@ ext.configureVariantWithGeckoBinaries = { variant ->
|
|||
}
|
||||
|
||||
def syncLibsFromDistDir = task("syncLibsFromDistDirFor${variant.name.capitalize()}", type: Sync) {
|
||||
doFirst {
|
||||
onlyIf {
|
||||
if (source.empty) {
|
||||
throw new GradleException("Required JNI libraries not found in ${source.asPath}. Have you built and packaged?")
|
||||
throw new StopExecutionException("Required JNI libraries not found in ${distDir}/lib. Have you built and packaged?")
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
into("${project.buildDir}/moz.build/src/${variant.name}/jniLibs")
|
||||
|
@ -69,10 +71,11 @@ ext.configureVariantWithGeckoBinaries = { variant ->
|
|||
}
|
||||
|
||||
def syncAssetsFromDistDir = task("syncAssetsFromDistDirFor${variant.name.capitalize()}", type: Sync) {
|
||||
doFirst {
|
||||
onlyIf {
|
||||
if (source.empty) {
|
||||
throw new GradleException("Required assets not found in ${source.asPath}. Have you built and packaged?")
|
||||
throw new StopExecutionException("Required assets not found in ${distDir}/assets. Have you built and packaged?")
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
into("${project.buildDir}/moz.build/src/${variant.name}/assets")
|
||||
|
|