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_filter-flags.js]
|
||||||
[browser_net_footer-summary.js]
|
[browser_net_footer-summary.js]
|
||||||
[browser_net_headers-alignment.js]
|
[browser_net_headers-alignment.js]
|
||||||
|
[browser_net_headers_filter.js]
|
||||||
[browser_net_headers_sorted.js]
|
[browser_net_headers_sorted.js]
|
||||||
[browser_net_image-tooltip.js]
|
[browser_net_image-tooltip.js]
|
||||||
[browser_net_json-b64.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 */
|
/* Filtering */
|
||||||
.treeTable .treeRow.hidden {
|
.treeTable .treeRow.hidden {
|
||||||
display: none;
|
display: none !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.treeTable .treeValueCellDivider {
|
.treeTable .treeValueCellDivider {
|
||||||
|
|
|
@ -143,5 +143,23 @@ CredentialsContainer::Store(const Credential& aCredential, ErrorResult& aRv)
|
||||||
return mManager->Store(aCredential);
|
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 dom
|
||||||
} // namespace mozilla
|
} // namespace mozilla
|
||||||
|
|
|
@ -41,6 +41,9 @@ public:
|
||||||
already_AddRefed<Promise>
|
already_AddRefed<Promise>
|
||||||
Store(const Credential& aCredential, ErrorResult& aRv);
|
Store(const Credential& aCredential, ErrorResult& aRv);
|
||||||
|
|
||||||
|
already_AddRefed<Promise>
|
||||||
|
PreventSilentAccess(ErrorResult& aRv);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
~CredentialsContainer();
|
~CredentialsContainer();
|
||||||
|
|
||||||
|
|
|
@ -42,6 +42,15 @@ function testSameOrigin() {
|
||||||
.catch(function sameOriginCatch(aResult) {
|
.catch(function sameOriginCatch(aResult) {
|
||||||
local_ok(false, "Should not have failed " + 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() {
|
.then(function() {
|
||||||
local_finished();
|
local_finished();
|
||||||
});
|
});
|
||||||
|
@ -58,6 +67,15 @@ function testCrossOrigin() {
|
||||||
local_ok(aResult.toString().startsWith("NotAllowedError"),
|
local_ok(aResult.toString().startsWith("NotAllowedError"),
|
||||||
"Expecting a NotAllowedError, received " + aResult);
|
"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() {
|
.then(function() {
|
||||||
local_finished();
|
local_finished();
|
||||||
});
|
});
|
||||||
|
|
123
dom/u2f/U2F.cpp
|
@ -9,6 +9,7 @@
|
||||||
#include "mozilla/ipc/PBackgroundChild.h"
|
#include "mozilla/ipc/PBackgroundChild.h"
|
||||||
#include "mozilla/ipc/BackgroundChild.h"
|
#include "mozilla/ipc/BackgroundChild.h"
|
||||||
#include "mozilla/dom/WebAuthnTransactionChild.h"
|
#include "mozilla/dom/WebAuthnTransactionChild.h"
|
||||||
|
#include "mozilla/dom/WebAuthnUtil.h"
|
||||||
#include "nsContentUtils.h"
|
#include "nsContentUtils.h"
|
||||||
#include "nsICryptoHash.h"
|
#include "nsICryptoHash.h"
|
||||||
#include "nsIEffectiveTLDService.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
|
static nsresult
|
||||||
BuildTransactionHashes(const nsCString& aRpId,
|
BuildTransactionHashes(const nsCString& aRpId,
|
||||||
const nsCString& aClientDataJSON,
|
const nsCString& aClientDataJSON,
|
||||||
|
@ -375,13 +272,10 @@ U2F::Register(const nsAString& aAppId,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Evaluate the AppID
|
// Evaluate the AppID
|
||||||
nsString adjustedAppId;
|
nsString adjustedAppId(aAppId);
|
||||||
adjustedAppId.Assign(aAppId);
|
if (!EvaluateAppID(mParent, mOrigin, U2FOperation::Register, adjustedAppId)) {
|
||||||
ErrorCode appIdResult = EvaluateAppID(mParent, mOrigin, U2FOperation::Register,
|
|
||||||
adjustedAppId);
|
|
||||||
if (appIdResult != ErrorCode::OK) {
|
|
||||||
RegisterResponse response;
|
RegisterResponse response;
|
||||||
response.mErrorCode.Construct(static_cast<uint32_t>(appIdResult));
|
response.mErrorCode.Construct(static_cast<uint32_t>(ErrorCode::BAD_REQUEST));
|
||||||
ExecuteCallback(response, callback);
|
ExecuteCallback(response, callback);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -538,13 +432,10 @@ U2F::Sign(const nsAString& aAppId,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Evaluate the AppID
|
// Evaluate the AppID
|
||||||
nsString adjustedAppId;
|
nsString adjustedAppId(aAppId);
|
||||||
adjustedAppId.Assign(aAppId);
|
if (!EvaluateAppID(mParent, mOrigin, U2FOperation::Sign, adjustedAppId)) {
|
||||||
ErrorCode appIdResult = EvaluateAppID(mParent, mOrigin, U2FOperation::Sign,
|
|
||||||
adjustedAppId);
|
|
||||||
if (appIdResult != ErrorCode::OK) {
|
|
||||||
SignResponse response;
|
SignResponse response;
|
||||||
response.mErrorCode.Construct(static_cast<uint32_t>(appIdResult));
|
response.mErrorCode.Construct(static_cast<uint32_t>(ErrorCode::BAD_REQUEST));
|
||||||
ExecuteCallback(response, callback);
|
ExecuteCallback(response, callback);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,8 +30,20 @@ struct WebAuthnScopedCredential {
|
||||||
uint8_t transports;
|
uint8_t transports;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct WebAuthnExtension {
|
struct WebAuthnExtensionAppId {
|
||||||
/* TODO Fill in with predefined extensions */
|
uint8_t[] AppId;
|
||||||
|
};
|
||||||
|
|
||||||
|
union WebAuthnExtension {
|
||||||
|
WebAuthnExtensionAppId;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct WebAuthnExtensionResultAppId {
|
||||||
|
bool AppId;
|
||||||
|
};
|
||||||
|
|
||||||
|
union WebAuthnExtensionResult {
|
||||||
|
WebAuthnExtensionResultAppId;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct WebAuthnMakeCredentialInfo {
|
struct WebAuthnMakeCredentialInfo {
|
||||||
|
@ -57,8 +69,10 @@ struct WebAuthnGetAssertionInfo {
|
||||||
};
|
};
|
||||||
|
|
||||||
struct WebAuthnGetAssertionResult {
|
struct WebAuthnGetAssertionResult {
|
||||||
|
uint8_t[] RpIdHash;
|
||||||
uint8_t[] CredentialID;
|
uint8_t[] CredentialID;
|
||||||
uint8_t[] SigBuffer;
|
uint8_t[] SigBuffer;
|
||||||
|
WebAuthnExtensionResult[] Extensions;
|
||||||
};
|
};
|
||||||
|
|
||||||
async protocol PWebAuthnTransaction {
|
async protocol PWebAuthnTransaction {
|
||||||
|
|
|
@ -115,6 +115,19 @@ PublicKeyCredential::IsUserVerifyingPlatformAuthenticatorAvailable(GlobalObject&
|
||||||
return promise.forget();
|
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 dom
|
||||||
} // namespace mozilla
|
} // namespace mozilla
|
||||||
|
|
|
@ -49,12 +49,17 @@ public:
|
||||||
static already_AddRefed<Promise>
|
static already_AddRefed<Promise>
|
||||||
IsUserVerifyingPlatformAuthenticatorAvailable(GlobalObject& aGlobal);
|
IsUserVerifyingPlatformAuthenticatorAvailable(GlobalObject& aGlobal);
|
||||||
|
|
||||||
|
void
|
||||||
|
GetClientExtensionResults(AuthenticationExtensionsClientOutputs& aResult);
|
||||||
|
|
||||||
|
void
|
||||||
|
SetClientExtensionResultAppId(bool aResult);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
CryptoBuffer mRawId;
|
CryptoBuffer mRawId;
|
||||||
JS::Heap<JSObject*> mRawIdCachedObj;
|
JS::Heap<JSObject*> mRawIdCachedObj;
|
||||||
RefPtr<AuthenticatorResponse> mResponse;
|
RefPtr<AuthenticatorResponse> mResponse;
|
||||||
// Extensions are not supported yet.
|
AuthenticationExtensionsClientOutputs mClientExtensionOutputs;
|
||||||
// <some type> mClientExtensionResults;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace dom
|
} // namespace dom
|
||||||
|
|
|
@ -127,6 +127,7 @@ U2FHIDTokenManager::Register(const nsTArray<WebAuthnScopedCredential>& aCredenti
|
||||||
}
|
}
|
||||||
|
|
||||||
ClearPromises();
|
ClearPromises();
|
||||||
|
mCurrentAppId = aApplication;
|
||||||
mTransactionId = rust_u2f_mgr_register(mU2FManager,
|
mTransactionId = rust_u2f_mgr_register(mU2FManager,
|
||||||
registerFlags,
|
registerFlags,
|
||||||
(uint64_t)aTimeoutMS,
|
(uint64_t)aTimeoutMS,
|
||||||
|
@ -164,6 +165,7 @@ RefPtr<U2FSignPromise>
|
||||||
U2FHIDTokenManager::Sign(const nsTArray<WebAuthnScopedCredential>& aCredentials,
|
U2FHIDTokenManager::Sign(const nsTArray<WebAuthnScopedCredential>& aCredentials,
|
||||||
const nsTArray<uint8_t>& aApplication,
|
const nsTArray<uint8_t>& aApplication,
|
||||||
const nsTArray<uint8_t>& aChallenge,
|
const nsTArray<uint8_t>& aChallenge,
|
||||||
|
const nsTArray<WebAuthnExtension>& aExtensions,
|
||||||
bool aRequireUserVerification,
|
bool aRequireUserVerification,
|
||||||
uint32_t aTimeoutMS)
|
uint32_t aTimeoutMS)
|
||||||
{
|
{
|
||||||
|
@ -176,15 +178,25 @@ U2FHIDTokenManager::Sign(const nsTArray<WebAuthnScopedCredential>& aCredentials,
|
||||||
signFlags |= U2F_FLAG_REQUIRE_USER_VERIFICATION;
|
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();
|
ClearPromises();
|
||||||
|
mCurrentAppId = aApplication;
|
||||||
mTransactionId = rust_u2f_mgr_sign(mU2FManager,
|
mTransactionId = rust_u2f_mgr_sign(mU2FManager,
|
||||||
signFlags,
|
signFlags,
|
||||||
(uint64_t)aTimeoutMS,
|
(uint64_t)aTimeoutMS,
|
||||||
u2f_sign_callback,
|
u2f_sign_callback,
|
||||||
aChallenge.Elements(),
|
aChallenge.Elements(),
|
||||||
aChallenge.Length(),
|
aChallenge.Length(),
|
||||||
aApplication.Elements(),
|
U2FAppIds(appIds).Get(),
|
||||||
aApplication.Length(),
|
|
||||||
U2FKeyHandles(aCredentials).Get());
|
U2FKeyHandles(aCredentials).Get());
|
||||||
if (mTransactionId == 0) {
|
if (mTransactionId == 0) {
|
||||||
return U2FSignPromise::CreateAndReject(NS_ERROR_DOM_UNKNOWN_ERR, __func__);
|
return U2FSignPromise::CreateAndReject(NS_ERROR_DOM_UNKNOWN_ERR, __func__);
|
||||||
|
@ -234,6 +246,12 @@ U2FHIDTokenManager::HandleSignResult(UniquePtr<U2FResult>&& aResult)
|
||||||
|
|
||||||
MOZ_ASSERT(!mSignPromise.IsEmpty());
|
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;
|
nsTArray<uint8_t> keyHandle;
|
||||||
if (!aResult->CopyKeyHandle(keyHandle)) {
|
if (!aResult->CopyKeyHandle(keyHandle)) {
|
||||||
mSignPromise.Reject(NS_ERROR_DOM_UNKNOWN_ERR, __func__);
|
mSignPromise.Reject(NS_ERROR_DOM_UNKNOWN_ERR, __func__);
|
||||||
|
@ -246,7 +264,14 @@ U2FHIDTokenManager::HandleSignResult(UniquePtr<U2FResult>&& aResult)
|
||||||
return;
|
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__);
|
mSignPromise.Resolve(Move(result), __func__);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -18,13 +18,32 @@
|
||||||
namespace mozilla {
|
namespace mozilla {
|
||||||
namespace dom {
|
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 {
|
class U2FKeyHandles {
|
||||||
public:
|
public:
|
||||||
explicit U2FKeyHandles(const nsTArray<WebAuthnScopedCredential>& aCredentials)
|
explicit U2FKeyHandles(const nsTArray<WebAuthnScopedCredential>& aCredentials)
|
||||||
{
|
{
|
||||||
mKeyHandles = rust_u2f_khs_new();
|
mKeyHandles = rust_u2f_khs_new();
|
||||||
|
|
||||||
for (auto cred: aCredentials) {
|
for (auto& cred: aCredentials) {
|
||||||
rust_u2f_khs_add(mKeyHandles,
|
rust_u2f_khs_add(mKeyHandles,
|
||||||
cred.id().Elements(),
|
cred.id().Elements(),
|
||||||
cred.id().Length(),
|
cred.id().Length(),
|
||||||
|
@ -66,6 +85,11 @@ public:
|
||||||
return CopyBuffer(U2F_RESBUF_ID_SIGNATURE, aBuffer);
|
return CopyBuffer(U2F_RESBUF_ID_SIGNATURE, aBuffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool CopyAppId(nsTArray<uint8_t>& aBuffer)
|
||||||
|
{
|
||||||
|
return CopyBuffer(U2F_RESBUF_ID_APPID, aBuffer);
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
bool CopyBuffer(uint8_t aResBufID, nsTArray<uint8_t>& aBuffer) {
|
bool CopyBuffer(uint8_t aResBufID, nsTArray<uint8_t>& aBuffer) {
|
||||||
if (!mResult) {
|
if (!mResult) {
|
||||||
|
@ -104,6 +128,7 @@ public:
|
||||||
Sign(const nsTArray<WebAuthnScopedCredential>& aCredentials,
|
Sign(const nsTArray<WebAuthnScopedCredential>& aCredentials,
|
||||||
const nsTArray<uint8_t>& aApplication,
|
const nsTArray<uint8_t>& aApplication,
|
||||||
const nsTArray<uint8_t>& aChallenge,
|
const nsTArray<uint8_t>& aChallenge,
|
||||||
|
const nsTArray<WebAuthnExtension>& aExtensions,
|
||||||
bool aRequireUserVerification,
|
bool aRequireUserVerification,
|
||||||
uint32_t aTimeoutMS) override;
|
uint32_t aTimeoutMS) override;
|
||||||
|
|
||||||
|
@ -123,6 +148,7 @@ private:
|
||||||
|
|
||||||
rust_u2f_manager* mU2FManager;
|
rust_u2f_manager* mU2FManager;
|
||||||
uint64_t mTransactionId;
|
uint64_t mTransactionId;
|
||||||
|
nsTArray<uint8_t> mCurrentAppId;
|
||||||
MozPromiseHolder<U2FRegisterPromise> mRegisterPromise;
|
MozPromiseHolder<U2FRegisterPromise> mRegisterPromise;
|
||||||
MozPromiseHolder<U2FSignPromise> mSignPromise;
|
MozPromiseHolder<U2FSignPromise> mSignPromise;
|
||||||
};
|
};
|
||||||
|
|
|
@ -693,6 +693,27 @@ U2FSoftTokenManager::Register(const nsTArray<WebAuthnScopedCredential>& aCredent
|
||||||
return U2FRegisterPromise::CreateAndResolve(Move(result), __func__);
|
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
|
// 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.
|
// 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,
|
U2FSoftTokenManager::Sign(const nsTArray<WebAuthnScopedCredential>& aCredentials,
|
||||||
const nsTArray<uint8_t>& aApplication,
|
const nsTArray<uint8_t>& aApplication,
|
||||||
const nsTArray<uint8_t>& aChallenge,
|
const nsTArray<uint8_t>& aChallenge,
|
||||||
|
const nsTArray<WebAuthnExtension>& aExtensions,
|
||||||
bool aRequireUserVerification,
|
bool aRequireUserVerification,
|
||||||
uint32_t aTimeoutMS)
|
uint32_t aTimeoutMS)
|
||||||
{
|
{
|
||||||
|
@ -728,18 +750,21 @@ U2FSoftTokenManager::Sign(const nsTArray<WebAuthnScopedCredential>& aCredentials
|
||||||
return U2FSignPromise::CreateAndReject(NS_ERROR_DOM_NOT_ALLOWED_ERR, __func__);
|
return U2FSignPromise::CreateAndReject(NS_ERROR_DOM_NOT_ALLOWED_ERR, __func__);
|
||||||
}
|
}
|
||||||
|
|
||||||
nsTArray<uint8_t> keyHandle;
|
nsTArray<nsTArray<uint8_t>> appIds;
|
||||||
for (auto cred: aCredentials) {
|
appIds.AppendElement(aApplication);
|
||||||
bool isRegistered = false;
|
|
||||||
nsresult rv = IsRegistered(cred.id(), aApplication, isRegistered);
|
// Process extensions.
|
||||||
if (NS_SUCCEEDED(rv) && isRegistered) {
|
for (const WebAuthnExtension& ext: aExtensions) {
|
||||||
keyHandle.Assign(cred.id());
|
if (ext.type() == WebAuthnExtension::TWebAuthnExtensionAppId) {
|
||||||
break;
|
appIds.AppendElement(ext.get_WebAuthnExtensionAppId().AppId());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fail if we didn't recognize a key id.
|
nsTArray<uint8_t> chosenAppId(aApplication);
|
||||||
if (keyHandle.IsEmpty()) {
|
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__);
|
return U2FSignPromise::CreateAndReject(NS_ERROR_DOM_NOT_ALLOWED_ERR, __func__);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -748,10 +773,10 @@ U2FSoftTokenManager::Sign(const nsTArray<WebAuthnScopedCredential>& aCredentials
|
||||||
UniquePK11SlotInfo slot(PK11_GetInternalSlot());
|
UniquePK11SlotInfo slot(PK11_GetInternalSlot());
|
||||||
MOZ_ASSERT(slot.get());
|
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,
|
MOZ_LOG(gNSSTokenLog, LogLevel::Warning,
|
||||||
("Parameter lengths are wrong! challenge=%d app=%d expected=%d",
|
("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__);
|
return U2FSignPromise::CreateAndReject(NS_ERROR_ILLEGAL_VALUE, __func__);
|
||||||
}
|
}
|
||||||
|
@ -760,8 +785,8 @@ U2FSoftTokenManager::Sign(const nsTArray<WebAuthnScopedCredential>& aCredentials
|
||||||
UniqueSECKEYPrivateKey privKey = PrivateKeyFromKeyHandle(slot, mWrappingKey,
|
UniqueSECKEYPrivateKey privKey = PrivateKeyFromKeyHandle(slot, mWrappingKey,
|
||||||
const_cast<uint8_t*>(keyHandle.Elements()),
|
const_cast<uint8_t*>(keyHandle.Elements()),
|
||||||
keyHandle.Length(),
|
keyHandle.Length(),
|
||||||
const_cast<uint8_t*>(aApplication.Elements()),
|
const_cast<uint8_t*>(chosenAppId.Elements()),
|
||||||
aApplication.Length());
|
chosenAppId.Length());
|
||||||
if (NS_WARN_IF(!privKey.get())) {
|
if (NS_WARN_IF(!privKey.get())) {
|
||||||
MOZ_LOG(gNSSTokenLog, LogLevel::Warning, ("Couldn't get the priv key!"));
|
MOZ_LOG(gNSSTokenLog, LogLevel::Warning, ("Couldn't get the priv key!"));
|
||||||
return U2FSignPromise::CreateAndReject(NS_ERROR_FAILURE, __func__);
|
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
|
// It's OK to ignore the return values here because we're writing into
|
||||||
// pre-allocated space
|
// pre-allocated space
|
||||||
signedDataBuf.AppendElements(aApplication.Elements(), aApplication.Length(),
|
signedDataBuf.AppendElements(chosenAppId.Elements(), chosenAppId.Length(),
|
||||||
mozilla::fallible);
|
mozilla::fallible);
|
||||||
signedDataBuf.AppendElement(0x01, mozilla::fallible);
|
signedDataBuf.AppendElement(0x01, mozilla::fallible);
|
||||||
signedDataBuf.AppendSECItem(counterItem);
|
signedDataBuf.AppendSECItem(counterItem);
|
||||||
|
@ -832,7 +857,15 @@ U2FSoftTokenManager::Sign(const nsTArray<WebAuthnScopedCredential>& aCredentials
|
||||||
signatureBuf.AppendSECItem(counterItem);
|
signatureBuf.AppendSECItem(counterItem);
|
||||||
signatureBuf.AppendSECItem(signatureItem);
|
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__);
|
return U2FSignPromise::CreateAndResolve(Move(result), __func__);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -34,6 +34,7 @@ public:
|
||||||
Sign(const nsTArray<WebAuthnScopedCredential>& aCredentials,
|
Sign(const nsTArray<WebAuthnScopedCredential>& aCredentials,
|
||||||
const nsTArray<uint8_t>& aApplication,
|
const nsTArray<uint8_t>& aApplication,
|
||||||
const nsTArray<uint8_t>& aChallenge,
|
const nsTArray<uint8_t>& aChallenge,
|
||||||
|
const nsTArray<WebAuthnExtension>& aExtensions,
|
||||||
bool aRequireUserVerification,
|
bool aRequireUserVerification,
|
||||||
uint32_t aTimeoutMS) override;
|
uint32_t aTimeoutMS) override;
|
||||||
|
|
||||||
|
@ -47,6 +48,12 @@ private:
|
||||||
const nsTArray<uint8_t>& aAppParam,
|
const nsTArray<uint8_t>& aAppParam,
|
||||||
bool& aResult);
|
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;
|
bool mInitialized;
|
||||||
mozilla::UniquePK11SymKey mWrappingKey;
|
mozilla::UniquePK11SymKey mWrappingKey;
|
||||||
|
|
||||||
|
|
|
@ -322,6 +322,7 @@ U2FTokenManager::Sign(PWebAuthnTransactionParent* aTransactionParent,
|
||||||
mTokenManagerImpl->Sign(aTransactionInfo.AllowList(),
|
mTokenManagerImpl->Sign(aTransactionInfo.AllowList(),
|
||||||
aTransactionInfo.RpIdHash(),
|
aTransactionInfo.RpIdHash(),
|
||||||
aTransactionInfo.ClientDataHash(),
|
aTransactionInfo.ClientDataHash(),
|
||||||
|
aTransactionInfo.Extensions(),
|
||||||
aTransactionInfo.RequireUserVerification(),
|
aTransactionInfo.RequireUserVerification(),
|
||||||
aTransactionInfo.TimeoutMS())
|
aTransactionInfo.TimeoutMS())
|
||||||
->Then(GetCurrentThreadSerialEventTarget(), __func__,
|
->Then(GetCurrentThreadSerialEventTarget(), __func__,
|
||||||
|
|
|
@ -38,6 +38,7 @@ public:
|
||||||
Sign(const nsTArray<WebAuthnScopedCredential>& aCredentials,
|
Sign(const nsTArray<WebAuthnScopedCredential>& aCredentials,
|
||||||
const nsTArray<uint8_t>& aApplication,
|
const nsTArray<uint8_t>& aApplication,
|
||||||
const nsTArray<uint8_t>& aChallenge,
|
const nsTArray<uint8_t>& aChallenge,
|
||||||
|
const nsTArray<WebAuthnExtension>& aExtensions,
|
||||||
bool aRequireUserVerification,
|
bool aRequireUserVerification,
|
||||||
uint32_t aTimeoutMS) = 0;
|
uint32_t aTimeoutMS) = 0;
|
||||||
|
|
||||||
|
|
|
@ -49,8 +49,11 @@ NS_IMPL_ISUPPORTS(WebAuthnManager, nsIDOMEventListener);
|
||||||
**********************************************************************/
|
**********************************************************************/
|
||||||
|
|
||||||
static nsresult
|
static nsresult
|
||||||
AssembleClientData(const nsAString& aOrigin, const CryptoBuffer& aChallenge,
|
AssembleClientData(const nsAString& aOrigin,
|
||||||
const nsAString& aType, /* out */ nsACString& aJsonOut)
|
const CryptoBuffer& aChallenge,
|
||||||
|
const nsAString& aType,
|
||||||
|
const AuthenticationExtensionsClientInputs& aExtensions,
|
||||||
|
/* out */ nsACString& aJsonOut)
|
||||||
{
|
{
|
||||||
MOZ_ASSERT(NS_IsMainThread());
|
MOZ_ASSERT(NS_IsMainThread());
|
||||||
|
|
||||||
|
@ -65,6 +68,7 @@ AssembleClientData(const nsAString& aOrigin, const CryptoBuffer& aChallenge,
|
||||||
clientDataObject.mChallenge.Assign(challengeBase64);
|
clientDataObject.mChallenge.Assign(challengeBase64);
|
||||||
clientDataObject.mOrigin.Assign(aOrigin);
|
clientDataObject.mOrigin.Assign(aOrigin);
|
||||||
clientDataObject.mHashAlgorithm.AssignLiteral(u"SHA-256");
|
clientDataObject.mHashAlgorithm.AssignLiteral(u"SHA-256");
|
||||||
|
clientDataObject.mClientExtensions = aExtensions;
|
||||||
|
|
||||||
nsAutoString temp;
|
nsAutoString temp;
|
||||||
if (NS_WARN_IF(!clientDataObject.ToJSON(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;
|
CryptoBuffer rpIdHash;
|
||||||
if (!rpIdHash.SetLength(SHA256_LENGTH, fallible)) {
|
if (!rpIdHash.SetLength(SHA256_LENGTH, fallible)) {
|
||||||
promise->MaybeReject(NS_ERROR_OUT_OF_MEMORY);
|
promise->MaybeReject(NS_ERROR_OUT_OF_MEMORY);
|
||||||
|
@ -345,7 +355,8 @@ WebAuthnManager::MakeCredential(const PublicKeyCredentialCreationOptions& aOptio
|
||||||
|
|
||||||
nsAutoCString clientDataJSON;
|
nsAutoCString clientDataJSON;
|
||||||
srv = AssembleClientData(origin, challenge,
|
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))) {
|
if (NS_WARN_IF(NS_FAILED(srv))) {
|
||||||
promise->MaybeReject(NS_ERROR_DOM_SECURITY_ERR);
|
promise->MaybeReject(NS_ERROR_DOM_SECURITY_ERR);
|
||||||
return promise.forget();
|
return promise.forget();
|
||||||
|
@ -537,7 +548,7 @@ WebAuthnManager::GetAssertion(const PublicKeyCredentialRequestOptions& aOptions,
|
||||||
|
|
||||||
nsAutoCString clientDataJSON;
|
nsAutoCString clientDataJSON;
|
||||||
srv = AssembleClientData(origin, challenge, NS_LITERAL_STRING("webauthn.get"),
|
srv = AssembleClientData(origin, challenge, NS_LITERAL_STRING("webauthn.get"),
|
||||||
clientDataJSON);
|
aOptions.mExtensions, clientDataJSON);
|
||||||
if (NS_WARN_IF(NS_FAILED(srv))) {
|
if (NS_WARN_IF(NS_FAILED(srv))) {
|
||||||
promise->MaybeReject(NS_ERROR_DOM_SECURITY_ERR);
|
promise->MaybeReject(NS_ERROR_DOM_SECURITY_ERR);
|
||||||
return promise.forget();
|
return promise.forget();
|
||||||
|
@ -593,14 +604,40 @@ WebAuthnManager::GetAssertion(const PublicKeyCredentialRequestOptions& aOptions,
|
||||||
bool requireUserVerification =
|
bool requireUserVerification =
|
||||||
aOptions.mUserVerification == UserVerificationRequirement::Required;
|
aOptions.mUserVerification == UserVerificationRequirement::Required;
|
||||||
|
|
||||||
// TODO: Add extension list building
|
// If extensions were specified, process any extensions supported by this
|
||||||
// If extensions was specified, process any extensions supported by this
|
|
||||||
// client platform, to produce the extension data that needs to be sent to the
|
// client platform, to produce the extension data that needs to be sent to the
|
||||||
// authenticator. If an error is encountered while processing an extension,
|
// authenticator. If an error is encountered while processing an extension,
|
||||||
// skip that extension and do not produce any extension data for it. Call the
|
// skip that extension and do not produce any extension data for it. Call the
|
||||||
// result of this processing clientExtensions.
|
// result of this processing clientExtensions.
|
||||||
nsTArray<WebAuthnExtension> extensions;
|
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,
|
WebAuthnGetAssertionInfo info(rpIdHash,
|
||||||
clientDataHash,
|
clientDataHash,
|
||||||
adjustedTimeout,
|
adjustedTimeout,
|
||||||
|
@ -807,7 +844,7 @@ WebAuthnManager::FinishGetAssertion(const uint64_t& aTransactionId,
|
||||||
}
|
}
|
||||||
|
|
||||||
CryptoBuffer rpIdHashBuf;
|
CryptoBuffer rpIdHashBuf;
|
||||||
if (!rpIdHashBuf.Assign(mTransaction.ref().mRpIdHash)) {
|
if (!rpIdHashBuf.Assign(aResult.RpIdHash())) {
|
||||||
RejectTransaction(NS_ERROR_OUT_OF_MEMORY);
|
RejectTransaction(NS_ERROR_OUT_OF_MEMORY);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -862,6 +899,14 @@ WebAuthnManager::FinishGetAssertion(const uint64_t& aTransactionId,
|
||||||
credential->SetRawId(credentialBuf);
|
credential->SetRawId(credentialBuf);
|
||||||
credential->SetResponse(assertion);
|
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);
|
mTransaction.ref().mPromise->MaybeResolve(credential);
|
||||||
ClearTransaction();
|
ClearTransaction();
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,11 +5,113 @@
|
||||||
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
#include "mozilla/dom/WebAuthnUtil.h"
|
#include "mozilla/dom/WebAuthnUtil.h"
|
||||||
|
#include "nsIEffectiveTLDService.h"
|
||||||
|
#include "nsNetUtil.h"
|
||||||
#include "pkixutil.h"
|
#include "pkixutil.h"
|
||||||
|
|
||||||
namespace mozilla {
|
namespace mozilla {
|
||||||
namespace dom {
|
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
|
nsresult
|
||||||
ReadToCryptoBuffer(pkix::Reader& aSrc, /* out */ CryptoBuffer& aDest,
|
ReadToCryptoBuffer(pkix::Reader& aSrc, /* out */ CryptoBuffer& aDest,
|
||||||
uint32_t aLen)
|
uint32_t aLen)
|
||||||
|
|
|
@ -16,6 +16,17 @@
|
||||||
|
|
||||||
namespace mozilla {
|
namespace mozilla {
|
||||||
namespace dom {
|
namespace dom {
|
||||||
|
|
||||||
|
enum class U2FOperation
|
||||||
|
{
|
||||||
|
Register,
|
||||||
|
Sign
|
||||||
|
};
|
||||||
|
|
||||||
|
bool
|
||||||
|
EvaluateAppID(nsPIDOMWindowInner* aParent, const nsString& aOrigin,
|
||||||
|
const U2FOperation& aOp, /* in/out */ nsString& aAppId);
|
||||||
|
|
||||||
nsresult
|
nsresult
|
||||||
AssembleAuthenticatorData(const CryptoBuffer& rpIdHashBuf,
|
AssembleAuthenticatorData(const CryptoBuffer& rpIdHashBuf,
|
||||||
const uint8_t flags,
|
const uint8_t flags,
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
[DEFAULT]
|
[DEFAULT]
|
||||||
support-files =
|
support-files =
|
||||||
|
head.js
|
||||||
tab_webauthn_result.html
|
tab_webauthn_result.html
|
||||||
tab_webauthn_success.html
|
tab_webauthn_success.html
|
||||||
../cbor/*
|
../cbor/*
|
||||||
|
@ -8,4 +9,5 @@ support-files =
|
||||||
skip-if = !e10s
|
skip-if = !e10s
|
||||||
|
|
||||||
[browser_abort_visibility.js]
|
[browser_abort_visibility.js]
|
||||||
|
[browser_fido_appid_extension.js]
|
||||||
[browser_webauthn_telemetry.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.hashAlgorithm, "SHA-256", "Hash algorithm is correct");
|
||||||
is(clientData.type, "webauthn.create", "Type 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)
|
return webAuthnDecodeCBORAttestation(aCredInfo.response.attestationObject)
|
||||||
.then(function(aAttestationObj) {
|
.then(function(aAttestationObj) {
|
||||||
// Make sure the RP ID hash matches what we calculate.
|
// Make sure the RP ID hash matches what we calculate.
|
||||||
|
|
|
@ -82,7 +82,7 @@ fn main() {
|
||||||
flags,
|
flags,
|
||||||
15_000,
|
15_000,
|
||||||
chall_bytes,
|
chall_bytes,
|
||||||
app_bytes,
|
vec![app_bytes],
|
||||||
vec![key_handle],
|
vec![key_handle],
|
||||||
move |rv| { tx.send(rv.unwrap()).unwrap(); },
|
move |rv| { tx.send(rv.unwrap()).unwrap(); },
|
||||||
)
|
)
|
||||||
|
|
|
@ -9,6 +9,7 @@ use std::{ptr, slice};
|
||||||
|
|
||||||
use U2FManager;
|
use U2FManager;
|
||||||
|
|
||||||
|
type U2FAppIds = Vec<::AppId>;
|
||||||
type U2FKeyHandles = Vec<::KeyHandle>;
|
type U2FKeyHandles = Vec<::KeyHandle>;
|
||||||
type U2FResult = HashMap<u8, Vec<u8>>;
|
type U2FResult = HashMap<u8, Vec<u8>>;
|
||||||
type U2FCallback = extern "C" fn(u64, *mut U2FResult);
|
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_REGISTRATION: u8 = 0;
|
||||||
const RESBUF_ID_KEYHANDLE: u8 = 1;
|
const RESBUF_ID_KEYHANDLE: u8 = 1;
|
||||||
const RESBUF_ID_SIGNATURE: u8 = 2;
|
const RESBUF_ID_SIGNATURE: u8 = 2;
|
||||||
|
const RESBUF_ID_APPID: u8 = 3;
|
||||||
|
|
||||||
// Generates a new 64-bit transaction id with collision probability 2^-32.
|
// Generates a new 64-bit transaction id with collision probability 2^-32.
|
||||||
fn new_tid() -> u64 {
|
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]
|
#[no_mangle]
|
||||||
pub unsafe extern "C" fn rust_u2f_khs_new() -> *mut U2FKeyHandles {
|
pub unsafe extern "C" fn rust_u2f_khs_new() -> *mut U2FKeyHandles {
|
||||||
Box::into_raw(Box::new(vec![]))
|
Box::into_raw(Box::new(vec![]))
|
||||||
|
@ -165,8 +188,7 @@ pub unsafe extern "C" fn rust_u2f_mgr_sign(
|
||||||
callback: U2FCallback,
|
callback: U2FCallback,
|
||||||
challenge_ptr: *const u8,
|
challenge_ptr: *const u8,
|
||||||
challenge_len: usize,
|
challenge_len: usize,
|
||||||
application_ptr: *const u8,
|
app_ids: *const U2FAppIds,
|
||||||
application_len: usize,
|
|
||||||
khs: *const U2FKeyHandles,
|
khs: *const U2FKeyHandles,
|
||||||
) -> u64 {
|
) -> u64 {
|
||||||
if mgr.is_null() || khs.is_null() {
|
if mgr.is_null() || khs.is_null() {
|
||||||
|
@ -174,13 +196,18 @@ pub unsafe extern "C" fn rust_u2f_mgr_sign(
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check buffers.
|
// 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;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
let flags = ::SignFlags::from_bits_truncate(flags);
|
let flags = ::SignFlags::from_bits_truncate(flags);
|
||||||
let challenge = from_raw(challenge_ptr, challenge_len);
|
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 key_handles = (*khs).clone();
|
||||||
|
|
||||||
let tid = new_tid();
|
let tid = new_tid();
|
||||||
|
@ -188,13 +215,14 @@ pub unsafe extern "C" fn rust_u2f_mgr_sign(
|
||||||
flags,
|
flags,
|
||||||
timeout,
|
timeout,
|
||||||
challenge,
|
challenge,
|
||||||
application,
|
app_ids,
|
||||||
key_handles,
|
key_handles,
|
||||||
move |rv| {
|
move |rv| {
|
||||||
if let Ok((key_handle, signature)) = rv {
|
if let Ok((app_id, key_handle, signature)) = rv {
|
||||||
let mut result = U2FResult::new();
|
let mut result = U2FResult::new();
|
||||||
result.insert(RESBUF_ID_KEYHANDLE, key_handle);
|
result.insert(RESBUF_ID_KEYHANDLE, key_handle);
|
||||||
result.insert(RESBUF_ID_SIGNATURE, signature);
|
result.insert(RESBUF_ID_SIGNATURE, signature);
|
||||||
|
result.insert(RESBUF_ID_APPID, app_id);
|
||||||
callback(tid, Box::into_raw(Box::new(result)));
|
callback(tid, Box::into_raw(Box::new(result)));
|
||||||
} else {
|
} else {
|
||||||
callback(tid, ptr::null_mut());
|
callback(tid, ptr::null_mut());
|
||||||
|
|
|
@ -75,6 +75,10 @@ pub struct KeyHandle {
|
||||||
pub transports: AuthenticatorTransports,
|
pub transports: AuthenticatorTransports,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub type AppId = Vec<u8>;
|
||||||
|
pub type RegisterResult = Vec<u8>;
|
||||||
|
pub type SignResult = (AppId, Vec<u8>, Vec<u8>);
|
||||||
|
|
||||||
#[cfg(fuzzing)]
|
#[cfg(fuzzing)]
|
||||||
pub use u2fprotocol::*;
|
pub use u2fprotocol::*;
|
||||||
#[cfg(fuzzing)]
|
#[cfg(fuzzing)]
|
||||||
|
|
|
@ -16,17 +16,17 @@ enum QueueAction {
|
||||||
flags: ::RegisterFlags,
|
flags: ::RegisterFlags,
|
||||||
timeout: u64,
|
timeout: u64,
|
||||||
challenge: Vec<u8>,
|
challenge: Vec<u8>,
|
||||||
application: Vec<u8>,
|
application: ::AppId,
|
||||||
key_handles: Vec<::KeyHandle>,
|
key_handles: Vec<::KeyHandle>,
|
||||||
callback: OnceCallback<Vec<u8>>,
|
callback: OnceCallback<::RegisterResult>,
|
||||||
},
|
},
|
||||||
Sign {
|
Sign {
|
||||||
flags: ::SignFlags,
|
flags: ::SignFlags,
|
||||||
timeout: u64,
|
timeout: u64,
|
||||||
challenge: Vec<u8>,
|
challenge: Vec<u8>,
|
||||||
application: Vec<u8>,
|
app_ids: Vec<::AppId>,
|
||||||
key_handles: Vec<::KeyHandle>,
|
key_handles: Vec<::KeyHandle>,
|
||||||
callback: OnceCallback<(Vec<u8>, Vec<u8>)>,
|
callback: OnceCallback<::SignResult>,
|
||||||
},
|
},
|
||||||
Cancel,
|
Cancel,
|
||||||
}
|
}
|
||||||
|
@ -68,7 +68,7 @@ impl U2FManager {
|
||||||
flags,
|
flags,
|
||||||
timeout,
|
timeout,
|
||||||
challenge,
|
challenge,
|
||||||
application,
|
app_ids,
|
||||||
key_handles,
|
key_handles,
|
||||||
callback,
|
callback,
|
||||||
}) => {
|
}) => {
|
||||||
|
@ -77,7 +77,7 @@ impl U2FManager {
|
||||||
flags,
|
flags,
|
||||||
timeout,
|
timeout,
|
||||||
challenge,
|
challenge,
|
||||||
application,
|
app_ids,
|
||||||
key_handles,
|
key_handles,
|
||||||
callback,
|
callback,
|
||||||
);
|
);
|
||||||
|
@ -109,12 +109,12 @@ impl U2FManager {
|
||||||
flags: ::RegisterFlags,
|
flags: ::RegisterFlags,
|
||||||
timeout: u64,
|
timeout: u64,
|
||||||
challenge: Vec<u8>,
|
challenge: Vec<u8>,
|
||||||
application: Vec<u8>,
|
application: ::AppId,
|
||||||
key_handles: Vec<::KeyHandle>,
|
key_handles: Vec<::KeyHandle>,
|
||||||
callback: F,
|
callback: F,
|
||||||
) -> io::Result<()>
|
) -> io::Result<()>
|
||||||
where
|
where
|
||||||
F: FnOnce(io::Result<Vec<u8>>),
|
F: FnOnce(io::Result<::RegisterResult>),
|
||||||
F: Send + 'static,
|
F: Send + 'static,
|
||||||
{
|
{
|
||||||
if challenge.len() != PARAMETER_SIZE || application.len() != PARAMETER_SIZE {
|
if challenge.len() != PARAMETER_SIZE || application.len() != PARAMETER_SIZE {
|
||||||
|
@ -150,21 +150,37 @@ impl U2FManager {
|
||||||
flags: ::SignFlags,
|
flags: ::SignFlags,
|
||||||
timeout: u64,
|
timeout: u64,
|
||||||
challenge: Vec<u8>,
|
challenge: Vec<u8>,
|
||||||
application: Vec<u8>,
|
app_ids: Vec<::AppId>,
|
||||||
key_handles: Vec<::KeyHandle>,
|
key_handles: Vec<::KeyHandle>,
|
||||||
callback: F,
|
callback: F,
|
||||||
) -> io::Result<()>
|
) -> io::Result<()>
|
||||||
where
|
where
|
||||||
F: FnOnce(io::Result<(Vec<u8>, Vec<u8>)>),
|
F: FnOnce(io::Result<::SignResult>),
|
||||||
F: Send + 'static,
|
F: Send + 'static,
|
||||||
{
|
{
|
||||||
if challenge.len() != PARAMETER_SIZE || application.len() != PARAMETER_SIZE {
|
if challenge.len() != PARAMETER_SIZE {
|
||||||
return Err(io::Error::new(
|
return Err(io::Error::new(
|
||||||
io::ErrorKind::InvalidInput,
|
io::ErrorKind::InvalidInput,
|
||||||
"Invalid parameter sizes",
|
"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 {
|
for key_handle in &key_handles {
|
||||||
if key_handle.credential.len() > 256 {
|
if key_handle.credential.len() > 256 {
|
||||||
return Err(io::Error::new(
|
return Err(io::Error::new(
|
||||||
|
@ -179,7 +195,7 @@ impl U2FManager {
|
||||||
flags,
|
flags,
|
||||||
timeout,
|
timeout,
|
||||||
challenge,
|
challenge,
|
||||||
application,
|
app_ids,
|
||||||
key_handles,
|
key_handles,
|
||||||
callback,
|
callback,
|
||||||
};
|
};
|
||||||
|
|
|
@ -14,6 +14,31 @@ fn is_valid_transport(transports: ::AuthenticatorTransports) -> bool {
|
||||||
transports.is_empty() || transports.contains(::AuthenticatorTransports::USB)
|
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)]
|
#[derive(Default)]
|
||||||
pub struct StateMachine {
|
pub struct StateMachine {
|
||||||
transaction: Option<Transaction>,
|
transaction: Option<Transaction>,
|
||||||
|
@ -29,9 +54,9 @@ impl StateMachine {
|
||||||
flags: ::RegisterFlags,
|
flags: ::RegisterFlags,
|
||||||
timeout: u64,
|
timeout: u64,
|
||||||
challenge: Vec<u8>,
|
challenge: Vec<u8>,
|
||||||
application: Vec<u8>,
|
application: ::AppId,
|
||||||
key_handles: Vec<::KeyHandle>,
|
key_handles: Vec<::KeyHandle>,
|
||||||
callback: OnceCallback<Vec<u8>>,
|
callback: OnceCallback<::RegisterResult>,
|
||||||
) {
|
) {
|
||||||
// Abort any prior register/sign calls.
|
// Abort any prior register/sign calls.
|
||||||
self.cancel();
|
self.cancel();
|
||||||
|
@ -93,9 +118,9 @@ impl StateMachine {
|
||||||
flags: ::SignFlags,
|
flags: ::SignFlags,
|
||||||
timeout: u64,
|
timeout: u64,
|
||||||
challenge: Vec<u8>,
|
challenge: Vec<u8>,
|
||||||
application: Vec<u8>,
|
app_ids: Vec<::AppId>,
|
||||||
key_handles: Vec<::KeyHandle>,
|
key_handles: Vec<::KeyHandle>,
|
||||||
callback: OnceCallback<(Vec<u8>, Vec<u8>)>,
|
callback: OnceCallback<::SignResult>,
|
||||||
) {
|
) {
|
||||||
// Abort any prior register/sign calls.
|
// Abort any prior register/sign calls.
|
||||||
self.cancel();
|
self.cancel();
|
||||||
|
@ -125,14 +150,15 @@ impl StateMachine {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Find all matching key handles.
|
// For each appId, try all key handles. If there's at least one
|
||||||
let key_handles = key_handles
|
// valid key handle for an appId, we'll use that appId below.
|
||||||
.iter()
|
let (app_id, valid_handles) =
|
||||||
.filter(|key_handle| {
|
find_valid_key_handles(&app_ids, &key_handles,
|
||||||
u2f_is_keyhandle_valid(dev, &challenge, &application, &key_handle.credential)
|
|app_id, key_handle| {
|
||||||
.unwrap_or(false) /* no match on failure */
|
u2f_is_keyhandle_valid(dev, &challenge, app_id,
|
||||||
})
|
&key_handle.credential)
|
||||||
.collect::<Vec<_>>();
|
.unwrap_or(false) /* no match on failure */
|
||||||
|
});
|
||||||
|
|
||||||
// Aggregate distinct transports from all given credentials.
|
// Aggregate distinct transports from all given credentials.
|
||||||
let transports = key_handles.iter().fold(
|
let transports = key_handles.iter().fold(
|
||||||
|
@ -149,7 +175,7 @@ impl StateMachine {
|
||||||
while alive() {
|
while alive() {
|
||||||
// If the device matches none of the given key handles
|
// If the device matches none of the given key handles
|
||||||
// then just make it blink with bogus data.
|
// then just make it blink with bogus data.
|
||||||
if key_handles.is_empty() {
|
if valid_handles.is_empty() {
|
||||||
let blank = vec![0u8; PARAMETER_SIZE];
|
let blank = vec![0u8; PARAMETER_SIZE];
|
||||||
if let Ok(_) = u2f_register(dev, &blank, &blank) {
|
if let Ok(_) = u2f_register(dev, &blank, &blank) {
|
||||||
callback.call(Err(io_err("invalid key")));
|
callback.call(Err(io_err("invalid key")));
|
||||||
|
@ -157,15 +183,17 @@ impl StateMachine {
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Otherwise, try to sign.
|
// Otherwise, try to sign.
|
||||||
for key_handle in &key_handles {
|
for key_handle in &valid_handles {
|
||||||
if let Ok(bytes) = u2f_sign(
|
if let Ok(bytes) = u2f_sign(
|
||||||
dev,
|
dev,
|
||||||
&challenge,
|
&challenge,
|
||||||
&application,
|
app_id,
|
||||||
&key_handle.credential,
|
&key_handle.credential,
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
callback.call(Ok((key_handle.credential.clone(), bytes)));
|
callback.call(Ok((app_id.clone(),
|
||||||
|
key_handle.credential.clone(),
|
||||||
|
bytes)));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,6 +14,7 @@ extern "C" {
|
||||||
const uint8_t U2F_RESBUF_ID_REGISTRATION = 0;
|
const uint8_t U2F_RESBUF_ID_REGISTRATION = 0;
|
||||||
const uint8_t U2F_RESBUF_ID_KEYHANDLE = 1;
|
const uint8_t U2F_RESBUF_ID_KEYHANDLE = 1;
|
||||||
const uint8_t U2F_RESBUF_ID_SIGNATURE = 2;
|
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_RESIDENT_KEY = 1;
|
||||||
const uint64_t U2F_FLAG_REQUIRE_USER_VERIFICATION = 2;
|
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`
|
// The `rust_u2f_mgr` opaque type is equivalent to the rust type `U2FManager`
|
||||||
struct rust_u2f_manager;
|
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`
|
// The `rust_u2f_key_handles` opaque type is equivalent to the rust type `U2FKeyHandles`
|
||||||
struct rust_u2f_key_handles;
|
struct rust_u2f_key_handles;
|
||||||
|
|
||||||
|
@ -65,13 +69,21 @@ uint64_t rust_u2f_mgr_sign(rust_u2f_manager* mgr,
|
||||||
rust_u2f_callback,
|
rust_u2f_callback,
|
||||||
const uint8_t* challenge_ptr,
|
const uint8_t* challenge_ptr,
|
||||||
size_t challenge_len,
|
size_t challenge_len,
|
||||||
const uint8_t* application_ptr,
|
const rust_u2f_app_ids* app_ids,
|
||||||
size_t application_len,
|
|
||||||
const rust_u2f_key_handles* khs);
|
const rust_u2f_key_handles* khs);
|
||||||
|
|
||||||
uint64_t rust_u2f_mgr_cancel(rust_u2f_manager* mgr);
|
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.
|
/// U2FKeyHandles functions.
|
||||||
|
|
||||||
rust_u2f_key_handles* rust_u2f_khs_new();
|
rust_u2f_key_handles* rust_u2f_khs_new();
|
||||||
|
|
|
@ -21,6 +21,8 @@ interface CredentialsContainer {
|
||||||
Promise<Credential?> create(optional CredentialCreationOptions options);
|
Promise<Credential?> create(optional CredentialCreationOptions options);
|
||||||
[Throws]
|
[Throws]
|
||||||
Promise<Credential> store(Credential credential);
|
Promise<Credential> store(Credential credential);
|
||||||
|
[Throws]
|
||||||
|
Promise<void> preventSilentAccess();
|
||||||
};
|
};
|
||||||
|
|
||||||
dictionary CredentialRequestOptions {
|
dictionary CredentialRequestOptions {
|
||||||
|
|
|
@ -13,8 +13,7 @@
|
||||||
interface PublicKeyCredential : Credential {
|
interface PublicKeyCredential : Credential {
|
||||||
[SameObject] readonly attribute ArrayBuffer rawId;
|
[SameObject] readonly attribute ArrayBuffer rawId;
|
||||||
[SameObject] readonly attribute AuthenticatorResponse response;
|
[SameObject] readonly attribute AuthenticatorResponse response;
|
||||||
// Extensions are not supported yet.
|
AuthenticationExtensionsClientOutputs getClientExtensionResults();
|
||||||
// [SameObject] readonly attribute AuthenticationExtensions clientExtensionResults; // Add in Bug 1406458
|
|
||||||
};
|
};
|
||||||
|
|
||||||
[SecureContext]
|
[SecureContext]
|
||||||
|
@ -104,10 +103,18 @@ dictionary PublicKeyCredentialRequestOptions {
|
||||||
AuthenticationExtensionsClientInputs extensions;
|
AuthenticationExtensionsClientInputs extensions;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// TODO - Use partial dictionaries when bug 1436329 is fixed.
|
||||||
dictionary AuthenticationExtensionsClientInputs {
|
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 {
|
dictionary AuthenticationExtensionsClientOutputs {
|
||||||
|
// FIDO AppID Extension (appid)
|
||||||
|
// <https://w3c.github.io/webauthn/#sctn-appid-extension>
|
||||||
|
boolean appid;
|
||||||
};
|
};
|
||||||
|
|
||||||
typedef record<DOMString, DOMString> AuthenticationExtensionsAuthenticatorInputs;
|
typedef record<DOMString, DOMString> AuthenticationExtensionsAuthenticatorInputs;
|
||||||
|
@ -144,3 +151,16 @@ typedef sequence<AAGUID> AuthenticatorSelectionList;
|
||||||
|
|
||||||
typedef BufferSource AAGUID;
|
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();
|
Abort();
|
||||||
}
|
}
|
||||||
|
|
||||||
mParseEndListener = nullptr;
|
if (mParseEndListener) {
|
||||||
|
mParseEndListener->SetIsStale();
|
||||||
|
mParseEndListener = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
MOZ_ASSERT(!mFlagSyncLooping, "we rather crash than hang");
|
MOZ_ASSERT(!mFlagSyncLooping, "we rather crash than hang");
|
||||||
mFlagSyncLooping = false;
|
mFlagSyncLooping = false;
|
||||||
|
|
|
@ -875,8 +875,13 @@ public:
|
||||||
mXHR = nullptr;
|
mXHR = nullptr;
|
||||||
return NS_OK;
|
return NS_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
explicit nsXHRParseEndListener(XMLHttpRequestMainThread* aXHR)
|
explicit nsXHRParseEndListener(XMLHttpRequestMainThread* aXHR)
|
||||||
: mXHR(aXHR) {}
|
: mXHR(aXHR) {}
|
||||||
|
|
||||||
|
void SetIsStale() {
|
||||||
|
mXHR = nullptr;
|
||||||
|
}
|
||||||
private:
|
private:
|
||||||
virtual ~nsXHRParseEndListener() {}
|
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);
|
GeneratorObject::ResumeKind resumeKind = GeneratorObject::getResumeKind(pc);
|
||||||
|
|
||||||
frame.syncStack(0);
|
frame.syncStack(0);
|
||||||
masm.checkStackAlignment();
|
masm.assertStackAlignment(sizeof(Value), 0);
|
||||||
|
|
||||||
AllocatableGeneralRegisterSet regs(GeneralRegisterSet::All());
|
AllocatableGeneralRegisterSet regs(GeneralRegisterSet::All());
|
||||||
regs.take(BaselineFrameReg);
|
regs.take(BaselineFrameReg);
|
||||||
|
@ -4790,7 +4790,7 @@ BaselineCompiler::emit_JSOP_RESUME()
|
||||||
masm.push(BaselineFrameReg);
|
masm.push(BaselineFrameReg);
|
||||||
masm.moveStackPtrTo(BaselineFrameReg);
|
masm.moveStackPtrTo(BaselineFrameReg);
|
||||||
masm.subFromStackPtr(Imm32(BaselineFrame::Size()));
|
masm.subFromStackPtr(Imm32(BaselineFrame::Size()));
|
||||||
masm.checkStackAlignment();
|
masm.assertStackAlignment(sizeof(Value), 0);
|
||||||
|
|
||||||
// Store flags and env chain.
|
// Store flags and env chain.
|
||||||
masm.store32(Imm32(BaselineFrame::HAS_INITIAL_ENV), frame.addressOfFlags());
|
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_);
|
pushCallArguments(masm, regs, argcReg, /* isJitCall = */ false, isConstructing_);
|
||||||
regs.take(scratch);
|
regs.take(scratch);
|
||||||
|
|
||||||
masm.checkStackAlignment();
|
masm.assertStackAlignment(sizeof(Value), 0);
|
||||||
|
|
||||||
// Native functions have the signature:
|
// Native functions have the signature:
|
||||||
//
|
//
|
||||||
|
|
|
@ -3833,10 +3833,12 @@ void
|
||||||
CodeGenerator::visitTypeBarrierV(LTypeBarrierV* lir)
|
CodeGenerator::visitTypeBarrierV(LTypeBarrierV* lir)
|
||||||
{
|
{
|
||||||
ValueOperand operand = ToValue(lir, LTypeBarrierV::Input);
|
ValueOperand operand = ToValue(lir, LTypeBarrierV::Input);
|
||||||
Register scratch = ToTempRegisterOrInvalid(lir->temp());
|
Register unboxScratch = ToTempRegisterOrInvalid(lir->unboxTemp());
|
||||||
|
Register objScratch = ToTempRegisterOrInvalid(lir->objTemp());
|
||||||
|
|
||||||
Label miss;
|
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());
|
bailoutFrom(&miss, lir->snapshot());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3869,10 +3871,12 @@ void
|
||||||
CodeGenerator::visitMonitorTypes(LMonitorTypes* lir)
|
CodeGenerator::visitMonitorTypes(LMonitorTypes* lir)
|
||||||
{
|
{
|
||||||
ValueOperand operand = ToValue(lir, LMonitorTypes::Input);
|
ValueOperand operand = ToValue(lir, LMonitorTypes::Input);
|
||||||
Register scratch = ToTempUnboxRegister(lir->temp());
|
Register unboxScratch = ToTempRegisterOrInvalid(lir->unboxTemp());
|
||||||
|
Register objScratch = ToTempRegisterOrInvalid(lir->objTemp());
|
||||||
|
|
||||||
Label matched, miss;
|
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());
|
bailoutFrom(&miss, lir->snapshot());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5086,7 +5090,9 @@ CodeGenerator::generateArgumentsChecks(bool assert)
|
||||||
MResumePoint* rp = mir.entryResumePoint();
|
MResumePoint* rp = mir.entryResumePoint();
|
||||||
|
|
||||||
// No registers are allocated yet, so it's safe to grab anything.
|
// 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();
|
const CompileInfo& info = gen->info();
|
||||||
|
|
||||||
|
@ -5103,7 +5109,8 @@ CodeGenerator::generateArgumentsChecks(bool assert)
|
||||||
// ... * sizeof(Value) - Scale by value size.
|
// ... * sizeof(Value) - Scale by value size.
|
||||||
// ArgToStackOffset(...) - Compute displacement within arg vector.
|
// ArgToStackOffset(...) - Compute displacement within arg vector.
|
||||||
int32_t offset = ArgToStackOffset((i - info.startArgSlot()) * sizeof(Value));
|
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()) {
|
if (miss.used()) {
|
||||||
|
@ -5124,8 +5131,8 @@ CodeGenerator::generateArgumentsChecks(bool assert)
|
||||||
Label skip;
|
Label skip;
|
||||||
Address addr(masm.getStackPointer(), ArgToStackOffset((i - info.startArgSlot()) * sizeof(Value)));
|
Address addr(masm.getStackPointer(), ArgToStackOffset((i - info.startArgSlot()) * sizeof(Value)));
|
||||||
masm.branchTestObject(Assembler::NotEqual, addr, &skip);
|
masm.branchTestObject(Assembler::NotEqual, addr, &skip);
|
||||||
Register obj = masm.extractObject(addr, temp);
|
Register obj = masm.extractObject(addr, temp1);
|
||||||
masm.guardTypeSetMightBeIncomplete(types, obj, temp, &success);
|
masm.guardTypeSetMightBeIncomplete(types, obj, temp1, &success);
|
||||||
masm.bind(&skip);
|
masm.bind(&skip);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5469,7 +5476,7 @@ CodeGenerator::emitAssertResultV(const ValueOperand input, const TemporaryTypeSe
|
||||||
if (typeset && !typeset->unknown()) {
|
if (typeset && !typeset->unknown()) {
|
||||||
// We have a result TypeSet, assert this value is in it.
|
// We have a result TypeSet, assert this value is in it.
|
||||||
Label miss, ok;
|
Label miss, ok;
|
||||||
masm.guardTypeSet(input, typeset, BarrierKind::TypeSet, temp1, &miss);
|
masm.guardTypeSet(input, typeset, BarrierKind::TypeSet, temp1, temp2, &miss);
|
||||||
masm.jump(&ok);
|
masm.jump(&ok);
|
||||||
|
|
||||||
masm.bind(&miss);
|
masm.bind(&miss);
|
||||||
|
|
|
@ -1439,6 +1439,22 @@ EmitCheckPropertyTypes(MacroAssembler& masm, const PropertyTypeCheckInfo* typeCh
|
||||||
masm.Push(obj);
|
masm.Push(obj);
|
||||||
Register scratch1 = 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;
|
bool checkTypeSet = true;
|
||||||
Label failedFastPath;
|
Label failedFastPath;
|
||||||
|
|
||||||
|
@ -1468,7 +1484,8 @@ EmitCheckPropertyTypes(MacroAssembler& masm, const PropertyTypeCheckInfo* typeCh
|
||||||
if (propTypes) {
|
if (propTypes) {
|
||||||
// guardTypeSet can read from type sets without triggering read barriers.
|
// guardTypeSet can read from type sets without triggering read barriers.
|
||||||
TypeSet::readBarrier(propTypes);
|
TypeSet::readBarrier(propTypes);
|
||||||
masm.guardTypeSet(valReg, propTypes, BarrierKind::TypeSet, scratch1, &failedFastPath);
|
masm.guardTypeSet(valReg, propTypes, BarrierKind::TypeSet, scratch1, objScratch,
|
||||||
|
&failedFastPath);
|
||||||
masm.jump(&done);
|
masm.jump(&done);
|
||||||
} else {
|
} else {
|
||||||
masm.jump(&failedFastPath);
|
masm.jump(&failedFastPath);
|
||||||
|
@ -1511,11 +1528,15 @@ EmitCheckPropertyTypes(MacroAssembler& masm, const PropertyTypeCheckInfo* typeCh
|
||||||
masm.PopRegsInMaskIgnore(save, ignore);
|
masm.PopRegsInMaskIgnore(save, ignore);
|
||||||
|
|
||||||
masm.branchIfTrueBool(scratch1, &done);
|
masm.branchIfTrueBool(scratch1, &done);
|
||||||
|
if (objScratch != InvalidReg)
|
||||||
|
masm.pop(objScratch);
|
||||||
masm.pop(obj);
|
masm.pop(obj);
|
||||||
masm.jump(failures);
|
masm.jump(failures);
|
||||||
}
|
}
|
||||||
|
|
||||||
masm.bind(&done);
|
masm.bind(&done);
|
||||||
|
if (objScratch != InvalidReg)
|
||||||
|
masm.Pop(objScratch);
|
||||||
masm.Pop(obj);
|
masm.Pop(obj);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2792,7 +2792,6 @@ LIRGenerator::visitTypeBarrier(MTypeBarrier* ins)
|
||||||
// from inside a type barrier test.
|
// from inside a type barrier test.
|
||||||
|
|
||||||
const TemporaryTypeSet* types = ins->resultTypeSet();
|
const TemporaryTypeSet* types = ins->resultTypeSet();
|
||||||
bool needTemp = !types->unknownObject() && types->getObjectCount() > 0;
|
|
||||||
|
|
||||||
MIRType inputType = ins->getOperand(0)->type();
|
MIRType inputType = ins->getOperand(0)->type();
|
||||||
MOZ_ASSERT(inputType == ins->type());
|
MOZ_ASSERT(inputType == ins->type());
|
||||||
|
@ -2807,10 +2806,13 @@ LIRGenerator::visitTypeBarrier(MTypeBarrier* ins)
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool needObjTemp = !types->unknownObject() && types->getObjectCount() > 0;
|
||||||
|
|
||||||
// Handle typebarrier with Value as input.
|
// Handle typebarrier with Value as input.
|
||||||
if (inputType == MIRType::Value) {
|
if (inputType == MIRType::Value) {
|
||||||
LDefinition tmp = needTemp ? temp() : tempToUnbox();
|
LDefinition objTemp = needObjTemp ? temp() : LDefinition::BogusTemp();
|
||||||
LTypeBarrierV* barrier = new(alloc()) LTypeBarrierV(useBox(ins->input()), tmp);
|
LTypeBarrierV* barrier = new(alloc()) LTypeBarrierV(useBox(ins->input()), tempToUnbox(),
|
||||||
|
objTemp);
|
||||||
assignSnapshot(barrier, Bailout_TypeBarrierV);
|
assignSnapshot(barrier, Bailout_TypeBarrierV);
|
||||||
add(barrier, ins);
|
add(barrier, ins);
|
||||||
redefine(ins, ins->input());
|
redefine(ins, ins->input());
|
||||||
|
@ -2829,7 +2831,7 @@ LIRGenerator::visitTypeBarrier(MTypeBarrier* ins)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (needsObjectBarrier) {
|
if (needsObjectBarrier) {
|
||||||
LDefinition tmp = needTemp ? temp() : LDefinition::BogusTemp();
|
LDefinition tmp = needObjTemp ? temp() : LDefinition::BogusTemp();
|
||||||
LTypeBarrierO* barrier = new(alloc()) LTypeBarrierO(useRegister(ins->getOperand(0)), tmp);
|
LTypeBarrierO* barrier = new(alloc()) LTypeBarrierO(useRegister(ins->getOperand(0)), tmp);
|
||||||
assignSnapshot(barrier, Bailout_TypeBarrierO);
|
assignSnapshot(barrier, Bailout_TypeBarrierO);
|
||||||
add(barrier, ins);
|
add(barrier, ins);
|
||||||
|
@ -2848,10 +2850,11 @@ LIRGenerator::visitMonitorTypes(MMonitorTypes* ins)
|
||||||
// from inside a type check.
|
// from inside a type check.
|
||||||
|
|
||||||
const TemporaryTypeSet* types = ins->typeSet();
|
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);
|
assignSnapshot(lir, Bailout_MonitorTypes);
|
||||||
add(lir, ins);
|
add(lir, ins);
|
||||||
}
|
}
|
||||||
|
@ -3188,8 +3191,7 @@ LIRGenerator::visitSpectreMaskIndex(MSpectreMaskIndex* ins)
|
||||||
MOZ_ASSERT(ins->length()->type() == MIRType::Int32);
|
MOZ_ASSERT(ins->length()->type() == MIRType::Int32);
|
||||||
MOZ_ASSERT(ins->type() == MIRType::Int32);
|
MOZ_ASSERT(ins->type() == MIRType::Int32);
|
||||||
|
|
||||||
LSpectreMaskIndex* lir =
|
auto* lir = new(alloc()) LSpectreMaskIndex(useRegister(ins->index()), useAny(ins->length()));
|
||||||
new(alloc()) LSpectreMaskIndex(useRegister(ins->index()), useAny(ins->length()));
|
|
||||||
define(lir, ins);
|
define(lir, ins);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4306,9 +4306,9 @@ SimdTypeToArrayElementType(SimdType type)
|
||||||
}
|
}
|
||||||
|
|
||||||
bool
|
bool
|
||||||
IonBuilder::prepareForSimdLoadStore(CallInfo& callInfo, Scalar::Type simdType, MInstruction** elements,
|
IonBuilder::prepareForSimdLoadStore(CallInfo& callInfo, Scalar::Type simdType,
|
||||||
MDefinition** index, Scalar::Type* arrayType,
|
MInstruction** elements, MDefinition** index,
|
||||||
BoundsCheckKind boundsCheckKind)
|
Scalar::Type* arrayType, BoundsCheckKind boundsCheckKind)
|
||||||
{
|
{
|
||||||
MDefinition* array = callInfo.getArg(0);
|
MDefinition* array = callInfo.getArg(0);
|
||||||
*index = callInfo.getArg(1);
|
*index = callInfo.getArg(1);
|
||||||
|
@ -4320,31 +4320,35 @@ IonBuilder::prepareForSimdLoadStore(CallInfo& callInfo, Scalar::Type simdType, M
|
||||||
current->add(indexAsInt32);
|
current->add(indexAsInt32);
|
||||||
*index = 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);
|
MOZ_ASSERT(Scalar::byteSize(simdType) % Scalar::byteSize(*arrayType) == 0);
|
||||||
int32_t suppSlotsNeeded = Scalar::byteSize(simdType) / Scalar::byteSize(*arrayType) - 1;
|
int32_t byteLoadSize = Scalar::byteSize(simdType) / Scalar::byteSize(*arrayType);
|
||||||
if (suppSlotsNeeded) {
|
if (byteLoadSize > 1) {
|
||||||
MConstant* suppSlots = constant(Int32Value(suppSlotsNeeded));
|
// Add the number of supplementary needed slots. Overflows are fine
|
||||||
MAdd* addedIndex = MAdd::New(alloc(), *index, suppSlots);
|
// because the bounds check code uses an unsigned comparison.
|
||||||
// We're fine even with the add overflows, as long as the generated code
|
MAdd* addedIndex = MAdd::New(alloc(), *index, constant(Int32Value(byteLoadSize - 1)));
|
||||||
// for the bounds check uses an unsigned comparison.
|
|
||||||
addedIndex->setInt32Specialization();
|
addedIndex->setInt32Specialization();
|
||||||
current->add(addedIndex);
|
current->add(addedIndex);
|
||||||
indexForBoundsCheck = addedIndex;
|
indexLoadEnd = addedIndex;
|
||||||
}
|
}
|
||||||
|
|
||||||
MInstruction* length;
|
MInstruction* length;
|
||||||
addTypedArrayLengthAndData(array, SkipBoundsCheck, index, &length, elements, boundsCheckKind);
|
addTypedArrayLengthAndData(array, SkipBoundsCheck, index, &length, elements, boundsCheckKind);
|
||||||
|
|
||||||
// It can be that the index is out of bounds, while the added index for the
|
// If the index+size addition overflows, then indexLoadEnd might be
|
||||||
// bounds check is in bounds, so we actually need two bounds checks here.
|
// 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);
|
*index = addBoundsCheck(*index, length, boundsCheckKind);
|
||||||
|
|
||||||
addBoundsCheck(indexForBoundsCheck, length, BoundsCheckKind::UnusedIndex);
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -80,8 +80,14 @@ EmitTypeCheck(MacroAssembler& masm, Assembler::Condition cond, const T& src, Typ
|
||||||
|
|
||||||
template <typename Source> void
|
template <typename Source> void
|
||||||
MacroAssembler::guardTypeSet(const Source& address, const TypeSet* types, BarrierKind kind,
|
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(kind == BarrierKind::TypeTagOnly || kind == BarrierKind::TypeSet);
|
||||||
MOZ_ASSERT(!types->unknown());
|
MOZ_ASSERT(!types->unknown());
|
||||||
|
|
||||||
|
@ -119,7 +125,7 @@ MacroAssembler::guardTypeSet(const Source& address, const TypeSet* types, Barrie
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Register tag = extractTag(address, scratch);
|
Register tag = extractTag(address, unboxScratch);
|
||||||
|
|
||||||
// Emit all typed tests.
|
// Emit all typed tests.
|
||||||
for (size_t i = 0; i < mozilla::ArrayLength(tests); i++) {
|
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.
|
// Test specific objects.
|
||||||
MOZ_ASSERT(scratch != InvalidReg);
|
MOZ_ASSERT(objScratch != InvalidReg);
|
||||||
|
MOZ_ASSERT(objScratch != unboxScratch);
|
||||||
|
|
||||||
MOZ_ASSERT(numBranches == 1);
|
MOZ_ASSERT(numBranches == 1);
|
||||||
branchTestObject(NotEqual, tag, miss);
|
branchTestObject(NotEqual, tag, miss);
|
||||||
|
|
||||||
if (kind != BarrierKind::TypeTagOnly) {
|
if (kind != BarrierKind::TypeTagOnly) {
|
||||||
Register obj = extractObject(address, scratch);
|
Register obj = extractObject(address, unboxScratch);
|
||||||
guardObjectType(obj, types, scratch, miss);
|
guardObjectType(obj, types, objScratch, miss);
|
||||||
} else {
|
} else {
|
||||||
#ifdef DEBUG
|
#ifdef DEBUG
|
||||||
Label fail;
|
Label fail;
|
||||||
Register obj = extractObject(address, scratch);
|
Register obj = extractObject(address, unboxScratch);
|
||||||
guardObjectType(obj, types, scratch, &fail);
|
guardObjectType(obj, types, objScratch, &fail);
|
||||||
jump(&matched);
|
jump(&matched);
|
||||||
|
|
||||||
bind(&fail);
|
bind(&fail);
|
||||||
|
guardTypeSetMightBeIncomplete(types, obj, objScratch, &matched);
|
||||||
if (obj == scratch)
|
|
||||||
extractObject(address, scratch);
|
|
||||||
guardTypeSetMightBeIncomplete(types, obj, scratch, &matched);
|
|
||||||
|
|
||||||
assumeUnreachable("Unexpected object type");
|
assumeUnreachable("Unexpected object type");
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
@ -209,6 +213,7 @@ void
|
||||||
MacroAssembler::guardObjectType(Register obj, const TypeSet* types,
|
MacroAssembler::guardObjectType(Register obj, const TypeSet* types,
|
||||||
Register scratch, Label* miss)
|
Register scratch, Label* miss)
|
||||||
{
|
{
|
||||||
|
MOZ_ASSERT(obj != scratch);
|
||||||
MOZ_ASSERT(!types->unknown());
|
MOZ_ASSERT(!types->unknown());
|
||||||
MOZ_ASSERT(!types->hasType(TypeSet::AnyObjectType()));
|
MOZ_ASSERT(!types->hasType(TypeSet::AnyObjectType()));
|
||||||
MOZ_ASSERT_IF(types->getObjectCount() > 0, scratch != InvalidReg);
|
MOZ_ASSERT_IF(types->getObjectCount() > 0, scratch != InvalidReg);
|
||||||
|
@ -257,8 +262,6 @@ MacroAssembler::guardObjectType(Register obj, const TypeSet* types,
|
||||||
if (hasObjectGroups) {
|
if (hasObjectGroups) {
|
||||||
comment("has object groups");
|
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);
|
loadPtr(Address(obj, JSObject::offsetOfGroup()), scratch);
|
||||||
|
|
||||||
for (unsigned i = 0; i < count; i++) {
|
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,
|
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,
|
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,
|
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>
|
template<typename S, typename T>
|
||||||
static void
|
static void
|
||||||
|
|
|
@ -1933,7 +1933,8 @@ class MacroAssembler : public MacroAssemblerSpecific
|
||||||
// Emits a test of a value against all types in a TypeSet. A scratch
|
// Emits a test of a value against all types in a TypeSet. A scratch
|
||||||
// register is required.
|
// register is required.
|
||||||
template <typename Source>
|
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);
|
void guardObjectType(Register obj, const TypeSet* types, Register scratch, Label* miss);
|
||||||
|
|
||||||
|
|
|
@ -833,6 +833,14 @@ class AssemblerMIPSShared : public AssemblerShared
|
||||||
FCSR = 31
|
FCSR = 31
|
||||||
};
|
};
|
||||||
|
|
||||||
|
enum FCSRBit {
|
||||||
|
CauseI = 12,
|
||||||
|
CauseU,
|
||||||
|
CauseO,
|
||||||
|
CauseZ,
|
||||||
|
CauseV
|
||||||
|
};
|
||||||
|
|
||||||
enum FloatFormat {
|
enum FloatFormat {
|
||||||
SingleFloat,
|
SingleFloat,
|
||||||
DoubleFloat
|
DoubleFloat
|
||||||
|
|
|
@ -1481,17 +1481,19 @@ CodeGeneratorMIPSShared::visitWasmTruncateToInt32(LWasmTruncateToInt32* lir)
|
||||||
|
|
||||||
MOZ_ASSERT(fromType == MIRType::Double || fromType == MIRType::Float32);
|
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);
|
addOutOfLineCode(ool, mir);
|
||||||
|
|
||||||
Label* oolEntry = ool->entry();
|
Label* oolEntry = ool->entry();
|
||||||
if (mir->isUnsigned()) {
|
if (mir->isUnsigned()) {
|
||||||
if (fromType == MIRType::Double)
|
if (fromType == MIRType::Double)
|
||||||
masm.wasmTruncateDoubleToUInt32(input, output, oolEntry);
|
masm.wasmTruncateDoubleToUInt32(input, output, mir->isSaturating(), oolEntry);
|
||||||
else if (fromType == MIRType::Float32)
|
else if (fromType == MIRType::Float32)
|
||||||
masm.wasmTruncateFloat32ToUInt32(input, output, oolEntry);
|
masm.wasmTruncateFloat32ToUInt32(input, output, mir->isSaturating(), oolEntry);
|
||||||
else
|
else
|
||||||
MOZ_CRASH("unexpected type");
|
MOZ_CRASH("unexpected type");
|
||||||
|
|
||||||
|
masm.bind(ool->rejoin());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1508,9 +1510,15 @@ CodeGeneratorMIPSShared::visitWasmTruncateToInt32(LWasmTruncateToInt32* lir)
|
||||||
void
|
void
|
||||||
CodeGeneratorMIPSShared::visitOutOfLineWasmTruncateCheck(OutOfLineWasmTruncateCheck* ool)
|
CodeGeneratorMIPSShared::visitOutOfLineWasmTruncateCheck(OutOfLineWasmTruncateCheck* ool)
|
||||||
{
|
{
|
||||||
masm.outOfLineWasmTruncateToIntCheck(ool->input(), ool->fromType(), ool->toType(),
|
if(ool->toType() == MIRType::Int32)
|
||||||
ool->isUnsigned(), ool->rejoin(),
|
{
|
||||||
ool->bytecodeOffset());
|
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
|
void
|
||||||
|
|
|
@ -610,20 +610,10 @@ MacroAssembler::branchFloat(DoubleCondition cond, FloatRegister lhs, FloatRegist
|
||||||
ma_bc1s(lhs, rhs, label, cond);
|
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
|
void
|
||||||
MacroAssembler::branchTruncateFloat32ToInt32(FloatRegister src, Register dest, Label* fail)
|
MacroAssembler::branchTruncateFloat32ToInt32(FloatRegister src, Register dest, Label* fail)
|
||||||
{
|
{
|
||||||
convertFloat32ToInt32(src, dest, fail);
|
MOZ_CRASH();
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
|
@ -633,25 +623,10 @@ MacroAssembler::branchDouble(DoubleCondition cond, FloatRegister lhs, FloatRegis
|
||||||
ma_bc1d(lhs, rhs, label, cond);
|
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
|
void
|
||||||
MacroAssembler::branchTruncateDoubleToInt32(FloatRegister src, Register dest, Label* fail)
|
MacroAssembler::branchTruncateDoubleToInt32(FloatRegister src, Register dest, Label* fail)
|
||||||
{
|
{
|
||||||
convertDoubleToInt32(src, dest, fail);
|
MOZ_CRASH();
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename T, typename L>
|
template <typename T, typename L>
|
||||||
|
|
|
@ -1110,8 +1110,12 @@ MacroAssemblerMIPSShared::ma_lis(FloatRegister dest, float value)
|
||||||
{
|
{
|
||||||
Imm32 imm(mozilla::BitwiseCast<uint32_t>(value));
|
Imm32 imm(mozilla::BitwiseCast<uint32_t>(value));
|
||||||
|
|
||||||
ma_li(ScratchRegister, imm);
|
if(imm.value != 0) {
|
||||||
moveToFloat32(ScratchRegister, dest);
|
ma_li(ScratchRegister, imm);
|
||||||
|
moveToFloat32(ScratchRegister, dest);
|
||||||
|
} else {
|
||||||
|
moveToFloat32(zero, dest);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
|
@ -1654,7 +1658,7 @@ MacroAssembler::wasmTruncateDoubleToInt32(FloatRegister input, Register output,
|
||||||
as_truncwd(ScratchFloat32Reg, input);
|
as_truncwd(ScratchFloat32Reg, input);
|
||||||
as_cfc1(ScratchRegister, Assembler::FCSR);
|
as_cfc1(ScratchRegister, Assembler::FCSR);
|
||||||
moveFromFloat32(ScratchFloat32Reg, output);
|
moveFromFloat32(ScratchFloat32Reg, output);
|
||||||
ma_ext(ScratchRegister, ScratchRegister, 6, 1);
|
ma_ext(ScratchRegister, ScratchRegister, Assembler::CauseV, 1);
|
||||||
ma_b(ScratchRegister, Imm32(0), oolEntry, Assembler::NotEqual);
|
ma_b(ScratchRegister, Imm32(0), oolEntry, Assembler::NotEqual);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1666,113 +1670,198 @@ MacroAssembler::wasmTruncateFloat32ToInt32(FloatRegister input, Register output,
|
||||||
as_truncws(ScratchFloat32Reg, input);
|
as_truncws(ScratchFloat32Reg, input);
|
||||||
as_cfc1(ScratchRegister, Assembler::FCSR);
|
as_cfc1(ScratchRegister, Assembler::FCSR);
|
||||||
moveFromFloat32(ScratchFloat32Reg, output);
|
moveFromFloat32(ScratchFloat32Reg, output);
|
||||||
ma_ext(ScratchRegister, ScratchRegister, 6, 1);
|
ma_ext(ScratchRegister, ScratchRegister, Assembler::CauseV, 1);
|
||||||
ma_b(ScratchRegister, Imm32(0), oolEntry, Assembler::NotEqual);
|
ma_b(ScratchRegister, Imm32(0), oolEntry, Assembler::NotEqual);
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
MacroAssembler::oolWasmTruncateCheckF32ToI32(FloatRegister input, Register, TruncFlags flags,
|
MacroAssembler::oolWasmTruncateCheckF32ToI32(FloatRegister input, Register output,
|
||||||
wasm::BytecodeOffset off, Label* rejoin)
|
TruncFlags flags, wasm::BytecodeOffset off,
|
||||||
|
Label* rejoin)
|
||||||
{
|
{
|
||||||
outOfLineWasmTruncateToIntCheck(input, MIRType::Float32, MIRType::Int32, flags & TRUNC_UNSIGNED,
|
outOfLineWasmTruncateToInt32Check(input, output, MIRType::Float32, flags, rejoin, off);
|
||||||
rejoin, off);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
MacroAssembler::oolWasmTruncateCheckF64ToI32(FloatRegister input, Register, TruncFlags flags,
|
MacroAssembler::oolWasmTruncateCheckF64ToI32(FloatRegister input, Register output,
|
||||||
wasm::BytecodeOffset off, Label* rejoin)
|
TruncFlags flags, wasm::BytecodeOffset off,
|
||||||
|
Label* rejoin)
|
||||||
{
|
{
|
||||||
outOfLineWasmTruncateToIntCheck(input, MIRType::Double, MIRType::Int32, flags & TRUNC_UNSIGNED,
|
outOfLineWasmTruncateToInt32Check(input, output, MIRType::Double, flags, rejoin, off);
|
||||||
rejoin, off);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
MacroAssembler::oolWasmTruncateCheckF32ToI64(FloatRegister input, Register64, TruncFlags flags,
|
MacroAssembler::oolWasmTruncateCheckF32ToI64(FloatRegister input, Register64 output,
|
||||||
wasm::BytecodeOffset off, Label* rejoin)
|
TruncFlags flags, wasm::BytecodeOffset off,
|
||||||
|
Label* rejoin)
|
||||||
{
|
{
|
||||||
outOfLineWasmTruncateToIntCheck(input, MIRType::Float32, MIRType::Int64, flags & TRUNC_UNSIGNED,
|
outOfLineWasmTruncateToInt64Check(input, output, MIRType::Float32, flags, rejoin, off);
|
||||||
rejoin, off);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
MacroAssembler::oolWasmTruncateCheckF64ToI64(FloatRegister input, Register64, TruncFlags flags,
|
MacroAssembler::oolWasmTruncateCheckF64ToI64(FloatRegister input, Register64 output,
|
||||||
wasm::BytecodeOffset off, Label* rejoin)
|
TruncFlags flags, wasm::BytecodeOffset off,
|
||||||
|
Label* rejoin)
|
||||||
{
|
{
|
||||||
outOfLineWasmTruncateToIntCheck(input, MIRType::Double, MIRType::Int64, flags & TRUNC_UNSIGNED,
|
outOfLineWasmTruncateToInt64Check(input, output, MIRType::Double, flags, rejoin, off);
|
||||||
rejoin, off);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
MacroAssemblerMIPSShared::outOfLineWasmTruncateToIntCheck(FloatRegister input, MIRType fromType,
|
MacroAssemblerMIPSShared::outOfLineWasmTruncateToInt32Check(FloatRegister input, Register output,
|
||||||
MIRType toType, bool isUnsigned,
|
MIRType fromType, TruncFlags flags,
|
||||||
Label* rejoin,
|
Label* rejoin,
|
||||||
wasm::BytecodeOffset trapOffset)
|
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;
|
Label inputIsNaN;
|
||||||
|
|
||||||
if (fromType == MIRType::Double)
|
if (fromType == MIRType::Double)
|
||||||
asMasm().branchDouble(Assembler::DoubleUnordered, input, input, &inputIsNaN);
|
asMasm().branchDouble(Assembler::DoubleUnordered, input, input, &inputIsNaN);
|
||||||
else if (fromType == MIRType::Float32)
|
else if (fromType == MIRType::Float32)
|
||||||
asMasm().branchFloat(Assembler::DoubleUnordered, input, input, &inputIsNaN);
|
asMasm().branchFloat(Assembler::DoubleUnordered, input, input, &inputIsNaN);
|
||||||
else
|
|
||||||
MOZ_CRASH("unexpected type in visitOutOfLineWasmTruncateCheck");
|
|
||||||
|
|
||||||
// By default test for the following inputs and bail:
|
asMasm().wasmTrap(wasm::Trap::IntegerOverflow, trapOffset);
|
||||||
// signed: ] -Inf, INTXX_MIN - 1.0 ] and [ INTXX_MAX + 1.0 : +Inf [
|
asMasm().bind(&inputIsNaN);
|
||||||
// unsigned: ] -Inf, -1.0 ] and [ UINTXX_MAX + 1.0 : +Inf [
|
asMasm().wasmTrap(wasm::Trap::InvalidConversionToInteger, trapOffset);
|
||||||
// Note: we cannot always represent those exact values. As a result
|
}
|
||||||
// this changes the actual comparison a bit.
|
|
||||||
double minValue, maxValue;
|
void
|
||||||
Assembler::DoubleCondition minCond = Assembler::DoubleLessThanOrEqual;
|
MacroAssemblerMIPSShared::outOfLineWasmTruncateToInt64Check(FloatRegister input, Register64 output_,
|
||||||
Assembler::DoubleCondition maxCond = Assembler::DoubleGreaterThanOrEqual;
|
MIRType fromType, TruncFlags flags,
|
||||||
if (toType == MIRType::Int64) {
|
Label* rejoin,
|
||||||
if (isUnsigned) {
|
wasm::BytecodeOffset trapOffset)
|
||||||
minValue = -1;
|
{
|
||||||
maxValue = double(UINT64_MAX) + 1.0;
|
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 {
|
} else {
|
||||||
// In the float32/double range there exists no value between
|
|
||||||
// INT64_MIN and INT64_MIN - 1.0. Making INT64_MIN the lower-bound.
|
// Positive overflow is already saturated to INT64_MAX, so we only have
|
||||||
minValue = double(INT64_MIN);
|
// to handle NaN and negative overflow here.
|
||||||
minCond = Assembler::DoubleLessThan;
|
|
||||||
maxValue = double(INT64_MAX) + 1.0;
|
FloatTestKind moveCondition;
|
||||||
}
|
compareFloatingPoint(fromType == MIRType::Double ? DoubleFloat : SingleFloat,
|
||||||
} else {
|
input,
|
||||||
if (isUnsigned) {
|
input,
|
||||||
minValue = -1;
|
Assembler::DoubleUnordered, &moveCondition);
|
||||||
maxValue = double(UINT32_MAX) + 1.0;
|
MOZ_ASSERT(moveCondition == TestForTrue);
|
||||||
} else {
|
|
||||||
if (fromType == MIRType::Float32) {
|
as_movt(output, zero);
|
||||||
// In the float32 range there exists no value between
|
|
||||||
// INT32_MIN and INT32_MIN - 1.0. Making INT32_MIN the lower-bound.
|
compareFloatingPoint(fromType == MIRType::Double ? DoubleFloat : SingleFloat,
|
||||||
minValue = double(INT32_MIN);
|
input,
|
||||||
minCond = Assembler::DoubleLessThan;
|
fromType == MIRType::Double ? ScratchDoubleReg : ScratchFloat32Reg,
|
||||||
} else {
|
Assembler::DoubleLessThan, &moveCondition);
|
||||||
minValue = double(INT32_MIN) - 1.0;
|
MOZ_ASSERT(moveCondition == TestForTrue);
|
||||||
}
|
|
||||||
maxValue = double(INT32_MAX) + 1.0;
|
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) {
|
if (fromType == MIRType::Double) {
|
||||||
asMasm().loadConstantDouble(minValue, ScratchDoubleReg);
|
asMasm().loadConstantDouble(validInput, ScratchDoubleReg);
|
||||||
asMasm().branchDouble(minCond, input, ScratchDoubleReg, &fail);
|
asMasm().branchDouble(Assembler::DoubleEqual, input, ScratchDoubleReg, rejoin);
|
||||||
|
|
||||||
asMasm().loadConstantDouble(maxValue, ScratchDoubleReg);
|
|
||||||
asMasm().branchDouble(maxCond, input, ScratchDoubleReg, &fail);
|
|
||||||
} else {
|
} else {
|
||||||
asMasm().loadConstantFloat32(float(minValue), ScratchFloat32Reg);
|
asMasm().loadConstantFloat32(float(validInput), ScratchFloat32Reg);
|
||||||
asMasm().branchFloat(minCond, input, ScratchFloat32Reg, &fail);
|
asMasm().branchFloat(Assembler::DoubleEqual, input, ScratchDoubleReg, rejoin);
|
||||||
|
|
||||||
asMasm().loadConstantFloat32(float(maxValue), ScratchFloat32Reg);
|
|
||||||
asMasm().branchFloat(maxCond, input, ScratchFloat32Reg, &fail);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
asMasm().jump(rejoin);
|
#endif
|
||||||
|
|
||||||
// Handle errors.
|
|
||||||
asMasm().bind(&fail);
|
|
||||||
asMasm().wasmTrap(wasm::Trap::IntegerOverflow, trapOffset);
|
asMasm().wasmTrap(wasm::Trap::IntegerOverflow, trapOffset);
|
||||||
asMasm().bind(&inputIsNaN);
|
asMasm().bind(&inputIsNaN);
|
||||||
asMasm().wasmTrap(wasm::Trap::InvalidConversionToInteger, trapOffset);
|
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 minMaxDouble(FloatRegister srcDest, FloatRegister other, bool handleNaN, bool isMax);
|
||||||
void minMaxFloat32(FloatRegister srcDest, FloatRegister other, bool handleNaN, bool isMax);
|
void minMaxFloat32(FloatRegister srcDest, FloatRegister other, bool handleNaN, bool isMax);
|
||||||
|
|
||||||
void outOfLineWasmTruncateToIntCheck(FloatRegister input, MIRType fromType,
|
void outOfLineWasmTruncateToInt32Check(FloatRegister input, Register output, MIRType fromType,
|
||||||
MIRType toType, bool isUnsigned, Label* rejoin,
|
TruncFlags flags, Label* rejoin,
|
||||||
wasm::BytecodeOffset trapOffset);
|
wasm::BytecodeOffset trapOffset);
|
||||||
|
void outOfLineWasmTruncateToInt64Check(FloatRegister input, Register64 output, MIRType fromType,
|
||||||
|
TruncFlags flags, Label* rejoin,
|
||||||
|
wasm::BytecodeOffset trapOffset);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void wasmLoadImpl(const wasm::MemoryAccessDesc& access, Register memoryBase, Register ptr,
|
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 TotalDouble = 16;
|
||||||
static const uint32_t TotalSingle = 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 AllSingleMask = (1ULL << TotalSingle) - 1;
|
||||||
|
|
||||||
static const SetType AllDoubleMask = ((1ULL << TotalDouble) - 1) << TotalSingle;
|
static const SetType AllDoubleMask = ((1ULL << TotalDouble) - 1) << TotalSingle;
|
||||||
|
@ -95,8 +95,7 @@ class FloatRegisters : public FloatRegistersMIPSShared
|
||||||
static const SetType WrapperMask = VolatileMask;
|
static const SetType WrapperMask = VolatileMask;
|
||||||
|
|
||||||
static const SetType NonAllocatableMask =
|
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;
|
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 ReturnDoubleReg = { FloatRegisters::f0, FloatRegister::Double };
|
||||||
static constexpr FloatRegister ScratchFloat32Reg = { FloatRegisters::f18, FloatRegister::Single };
|
static constexpr FloatRegister ScratchFloat32Reg = { FloatRegisters::f18, FloatRegister::Single };
|
||||||
static constexpr FloatRegister ScratchDoubleReg = { FloatRegisters::f18, FloatRegister::Double };
|
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
|
struct ScratchFloat32Scope : public AutoFloatRegisterScope
|
||||||
{
|
{
|
||||||
|
|
|
@ -619,40 +619,45 @@ void
|
||||||
CodeGeneratorMIPS::visitWasmTruncateToInt64(LWasmTruncateToInt64* lir)
|
CodeGeneratorMIPS::visitWasmTruncateToInt64(LWasmTruncateToInt64* lir)
|
||||||
{
|
{
|
||||||
FloatRegister input = ToFloatRegister(lir->input());
|
FloatRegister input = ToFloatRegister(lir->input());
|
||||||
FloatRegister scratch = input;
|
FloatRegister arg = input;
|
||||||
Register64 output = ToOutRegister64(lir);
|
Register64 output = ToOutRegister64(lir);
|
||||||
MWasmTruncateToInt64* mir = lir->mir();
|
MWasmTruncateToInt64* mir = lir->mir();
|
||||||
MIRType fromType = mir->input()->type();
|
MIRType fromType = mir->input()->type();
|
||||||
|
|
||||||
auto* ool = new(alloc()) OutOfLineWasmTruncateCheck(mir, input);
|
auto* ool = new(alloc()) OutOfLineWasmTruncateCheck(mir, input, Register64::Invalid());
|
||||||
addOutOfLineCode(ool, mir);
|
addOutOfLineCode(ool, mir);
|
||||||
|
|
||||||
if (fromType == MIRType::Double) {
|
if (fromType == MIRType::Float32) {
|
||||||
masm.branchDouble(Assembler::DoubleUnordered, input, input, ool->entry());
|
arg = ScratchDoubleReg;
|
||||||
} else if (fromType == MIRType::Float32) {
|
masm.convertFloat32ToDouble(input, arg);
|
||||||
masm.branchFloat(Assembler::DoubleUnordered, input, input, ool->entry());
|
|
||||||
scratch = ScratchDoubleReg;
|
|
||||||
masm.convertFloat32ToDouble(input, scratch);
|
|
||||||
} else {
|
|
||||||
MOZ_CRASH("unexpected type in visitOutOfLineWasmTruncateCheck");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
masm.Push(input);
|
if (!lir->mir()->isSaturating()) {
|
||||||
|
masm.Push(input);
|
||||||
|
|
||||||
masm.setupWasmABICall();
|
masm.setupWasmABICall();
|
||||||
masm.passABIArg(scratch, MoveOp::DOUBLE);
|
masm.passABIArg(arg, MoveOp::DOUBLE);
|
||||||
if (lir->mir()->isUnsigned())
|
|
||||||
masm.callWithABI(mir->bytecodeOffset(), wasm::SymbolicAddress::TruncateDoubleToUint64);
|
|
||||||
else
|
|
||||||
masm.callWithABI(mir->bytecodeOffset(), wasm::SymbolicAddress::TruncateDoubleToInt64);
|
|
||||||
|
|
||||||
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.Pop(input);
|
||||||
masm.ma_b(output.low, Imm32(0x00000000), ool->rejoin(), Assembler::NotEqual);
|
|
||||||
masm.ma_b(ool->entry());
|
|
||||||
|
|
||||||
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);
|
MOZ_ASSERT(ReturnReg64 == output);
|
||||||
}
|
}
|
||||||
|
@ -661,7 +666,7 @@ void
|
||||||
CodeGeneratorMIPS::visitInt64ToFloatingPoint(LInt64ToFloatingPoint* lir)
|
CodeGeneratorMIPS::visitInt64ToFloatingPoint(LInt64ToFloatingPoint* lir)
|
||||||
{
|
{
|
||||||
Register64 input = ToRegister64(lir->getInt64Operand(0));
|
Register64 input = ToRegister64(lir->getInt64Operand(0));
|
||||||
FloatRegister output = ToFloatRegister(lir->output());
|
mozilla::DebugOnly<FloatRegister> output = ToFloatRegister(lir->output());
|
||||||
|
|
||||||
MInt64ToFloatingPoint* mir = lir->mir();
|
MInt64ToFloatingPoint* mir = lir->mir();
|
||||||
MIRType toType = mir->type();
|
MIRType toType = mir->type();
|
||||||
|
@ -686,8 +691,8 @@ CodeGeneratorMIPS::visitInt64ToFloatingPoint(LInt64ToFloatingPoint* lir)
|
||||||
else
|
else
|
||||||
masm.callWithABI(mir->bytecodeOffset(), wasm::SymbolicAddress::Int64ToFloat32, MoveOp::FLOAT32);
|
masm.callWithABI(mir->bytecodeOffset(), wasm::SymbolicAddress::Int64ToFloat32, MoveOp::FLOAT32);
|
||||||
|
|
||||||
MOZ_ASSERT_IF(toType == MIRType::Double, output == ReturnDoubleReg);
|
MOZ_ASSERT_IF(toType == MIRType::Double, *(&output) == ReturnDoubleReg);
|
||||||
MOZ_ASSERT_IF(toType == MIRType::Float32, output == ReturnFloat32Reg);
|
MOZ_ASSERT_IF(toType == MIRType::Float32, *(&output) == ReturnFloat32Reg);
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
|
|
|
@ -996,6 +996,26 @@ MacroAssembler::branchToComputedAddress(const BaseIndex& addr)
|
||||||
as_nop();
|
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.
|
// Memory access primitives.
|
||||||
void
|
void
|
||||||
|
|
|
@ -62,20 +62,26 @@ MacroAssemblerMIPSCompat::convertInt32ToDouble(const BaseIndex& src, FloatRegist
|
||||||
void
|
void
|
||||||
MacroAssemblerMIPSCompat::convertUInt32ToDouble(Register src, FloatRegister dest)
|
MacroAssemblerMIPSCompat::convertUInt32ToDouble(Register src, FloatRegister dest)
|
||||||
{
|
{
|
||||||
// We use SecondScratchDoubleReg because MacroAssembler::loadFromTypedArray
|
Label positive, done;
|
||||||
// calls with ScratchDoubleReg as dest.
|
ma_b(src, src, &positive, NotSigned, ShortJump);
|
||||||
MOZ_ASSERT(dest != SecondScratchDoubleReg);
|
|
||||||
|
|
||||||
// Subtract INT32_MIN to get a positive number
|
const uint32_t kExponentShift = mozilla::FloatingPoint<double>::kExponentShift - 32;
|
||||||
ma_subu(ScratchRegister, src, Imm32(INT32_MIN));
|
const uint32_t kExponent = (31 + mozilla::FloatingPoint<double>::kExponentBias);
|
||||||
|
|
||||||
// Convert value
|
ma_ext(SecondScratchReg, src, 31 - kExponentShift, kExponentShift);
|
||||||
as_mtc1(ScratchRegister, dest);
|
ma_li(ScratchRegister, Imm32(kExponent << kExponentShift));
|
||||||
as_cvtdw(dest, dest);
|
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
|
void
|
||||||
|
@ -84,10 +90,19 @@ MacroAssemblerMIPSCompat::convertUInt32ToFloat32(Register src, FloatRegister des
|
||||||
Label positive, done;
|
Label positive, done;
|
||||||
ma_b(src, src, &positive, NotSigned, ShortJump);
|
ma_b(src, src, &positive, NotSigned, ShortJump);
|
||||||
|
|
||||||
// We cannot do the same as convertUInt32ToDouble because float32 doesn't
|
const uint32_t kExponentShift = mozilla::FloatingPoint<double>::kExponentShift - 32;
|
||||||
// have enough precision.
|
const uint32_t kExponent = (31 + mozilla::FloatingPoint<double>::kExponentBias);
|
||||||
convertUInt32ToDouble(src, dest);
|
|
||||||
convertDoubleToFloat32(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));
|
||||||
|
FloatRegister destDouble = dest.asDouble();
|
||||||
|
moveToDoubleHi(SecondScratchReg, destDouble);
|
||||||
|
moveToDoubleLo(ScratchRegister, destDouble);
|
||||||
|
|
||||||
|
convertDoubleToFloat32(destDouble, dest);
|
||||||
|
|
||||||
ma_b(&done, ShortJump);
|
ma_b(&done, ShortJump);
|
||||||
|
|
||||||
bind(&positive);
|
bind(&positive);
|
||||||
|
@ -111,17 +126,18 @@ MacroAssemblerMIPSCompat::convertDoubleToInt32(FloatRegister src, Register dest,
|
||||||
{
|
{
|
||||||
if (negativeZeroCheck) {
|
if (negativeZeroCheck) {
|
||||||
moveFromDoubleHi(src, dest);
|
moveFromDoubleHi(src, dest);
|
||||||
moveFromDoubleLo(src, ScratchRegister);
|
moveFromDoubleLo(src, SecondScratchReg);
|
||||||
as_movn(dest, zero, ScratchRegister);
|
ma_xor(dest, Imm32(INT32_MIN));
|
||||||
ma_b(dest, Imm32(INT32_MIN), fail, Assembler::Equal);
|
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
|
// Truncate double to int ; if result is inexact fail
|
||||||
// same number.
|
as_truncwd(ScratchFloat32Reg, src);
|
||||||
as_cvtwd(ScratchDoubleReg, src);
|
as_cfc1(ScratchRegister, Assembler::FCSR);
|
||||||
as_mfc1(dest, ScratchDoubleReg);
|
moveFromFloat32(ScratchFloat32Reg, dest);
|
||||||
as_cvtdw(ScratchDoubleReg, ScratchDoubleReg);
|
ma_ext(ScratchRegister, ScratchRegister, Assembler::CauseI, 1);
|
||||||
ma_bc1d(src, ScratchDoubleReg, fail, Assembler::DoubleNotEqualOrUnordered);
|
ma_b(ScratchRegister, Imm32(0), fail, Assembler::NotEqual);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Checks whether a float32 is representable as a 32-bit integer. If so, the
|
// 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);
|
ma_b(dest, Imm32(INT32_MIN), fail, Assembler::Equal);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Converting the floating point value to an integer and then converting it
|
as_truncws(ScratchFloat32Reg, src);
|
||||||
// back to a float32 would not work, as float to int32 conversions are
|
as_cfc1(ScratchRegister, Assembler::FCSR);
|
||||||
// clamping (e.g. float(INT32_MAX + 1) would get converted into INT32_MAX
|
moveFromFloat32(ScratchFloat32Reg, dest);
|
||||||
// and then back to float(INT32_MAX + 1)). If this ever happens, we just
|
ma_ext(ScratchRegister, ScratchRegister, Assembler::CauseI, 1);
|
||||||
// bail out.
|
ma_b(ScratchRegister, Imm32(0), fail, Assembler::NotEqual);
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
|
@ -1377,49 +1386,19 @@ MacroAssemblerMIPSCompat::storeUnalignedDouble(const wasm::MemoryAccessDesc& acc
|
||||||
append(access, store.getOffset(), framePushed);
|
append(access, store.getOffset(), framePushed);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Note: this function clobbers the input register.
|
|
||||||
void
|
void
|
||||||
MacroAssembler::clampDoubleToUint8(FloatRegister input, Register output)
|
MacroAssembler::clampDoubleToUint8(FloatRegister input, Register output)
|
||||||
{
|
{
|
||||||
MOZ_ASSERT(input != ScratchDoubleReg);
|
as_roundwd(ScratchDoubleReg, input);
|
||||||
Label positive, done;
|
ma_li(ScratchRegister, Imm32(255));
|
||||||
|
as_mfc1(output, ScratchDoubleReg);
|
||||||
// <= 0 or NaN --> 0
|
zeroDouble(ScratchDoubleReg);
|
||||||
zeroDouble(ScratchDoubleReg);
|
as_sltiu(SecondScratchReg, output, 255);
|
||||||
branchDouble(DoubleGreaterThan, input, ScratchDoubleReg, &positive);
|
as_colt(DoubleFloat, ScratchDoubleReg, input);
|
||||||
{
|
// if res > 255; res = 255;
|
||||||
move32(Imm32(0), output);
|
as_movz(output, ScratchRegister, SecondScratchReg);
|
||||||
jump(&done);
|
// if !(input > 0); res = 0;
|
||||||
}
|
as_movf(output, zero);
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// higher level tag testing code
|
// higher level tag testing code
|
||||||
|
@ -2549,25 +2528,26 @@ void
|
||||||
MacroAssembler::wasmTruncateDoubleToUInt32(FloatRegister input, Register output, bool isSaturating,
|
MacroAssembler::wasmTruncateDoubleToUInt32(FloatRegister input, Register output, bool isSaturating,
|
||||||
Label* oolEntry)
|
Label* oolEntry)
|
||||||
{
|
{
|
||||||
MOZ_ASSERT(!isSaturating, "NYI");
|
Label done;
|
||||||
|
|
||||||
loadConstantDouble(double(-1.0), ScratchDoubleReg);
|
as_truncwd(ScratchFloat32Reg, input);
|
||||||
branchDouble(Assembler::DoubleLessThanOrEqual, input, ScratchDoubleReg, oolEntry);
|
ma_li(ScratchRegister, Imm32(INT32_MAX));
|
||||||
|
moveFromFloat32(ScratchFloat32Reg, output);
|
||||||
|
|
||||||
loadConstantDouble(double(UINT32_MAX) + 1.0, ScratchDoubleReg);
|
// For numbers in -1.[ : ]INT32_MAX range do nothing more
|
||||||
branchDouble(Assembler::DoubleGreaterThanOrEqualOrUnordered, input, ScratchDoubleReg, oolEntry);
|
ma_b(output, ScratchRegister, &done, Assembler::Below, ShortJump);
|
||||||
Label done, simple;
|
|
||||||
loadConstantDouble(double(0x80000000UL), ScratchDoubleReg);
|
loadConstantDouble(double(INT32_MAX + 1ULL), ScratchDoubleReg);
|
||||||
branchDouble(Assembler::DoubleLessThan, input, ScratchDoubleReg, &simple);
|
ma_li(ScratchRegister, Imm32(INT32_MIN));
|
||||||
as_subd(ScratchDoubleReg, input, ScratchDoubleReg);
|
as_subd(ScratchDoubleReg, input, ScratchDoubleReg);
|
||||||
as_truncwd(ScratchDoubleReg, ScratchDoubleReg);
|
as_truncwd(ScratchFloat32Reg, ScratchDoubleReg);
|
||||||
moveFromFloat32(ScratchDoubleReg, output);
|
as_cfc1(SecondScratchReg, Assembler::FCSR);
|
||||||
ma_li(ScratchRegister, Imm32(0x80000000UL));
|
moveFromFloat32(ScratchFloat32Reg, output);
|
||||||
ma_or(output, ScratchRegister);
|
ma_ext(SecondScratchReg, SecondScratchReg, Assembler::CauseV, 1);
|
||||||
ma_b(&done);
|
ma_addu(output, ScratchRegister);
|
||||||
bind(&simple);
|
|
||||||
as_truncwd(ScratchDoubleReg, input);
|
ma_b(SecondScratchReg, Imm32(0), oolEntry, Assembler::NotEqual);
|
||||||
moveFromFloat32(ScratchDoubleReg, output);
|
|
||||||
bind(&done);
|
bind(&done);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2575,25 +2555,29 @@ void
|
||||||
MacroAssembler::wasmTruncateFloat32ToUInt32(FloatRegister input, Register output, bool isSaturating,
|
MacroAssembler::wasmTruncateFloat32ToUInt32(FloatRegister input, Register output, bool isSaturating,
|
||||||
Label* oolEntry)
|
Label* oolEntry)
|
||||||
{
|
{
|
||||||
MOZ_ASSERT(!isSaturating, "NYI");
|
Label done;
|
||||||
|
|
||||||
loadConstantFloat32(double(-1.0), ScratchDoubleReg);
|
as_truncws(ScratchFloat32Reg, input);
|
||||||
branchFloat(Assembler::DoubleLessThanOrEqualOrUnordered, input, ScratchDoubleReg, oolEntry);
|
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);
|
bind(&done);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1576,24 +1576,34 @@ Simulator::setFCSRRoundError(double original, double rounded)
|
||||||
{
|
{
|
||||||
bool ret = false;
|
bool ret = false;
|
||||||
|
|
||||||
|
setFCSRBit(kFCSRInexactCauseBit, false);
|
||||||
|
setFCSRBit(kFCSRUnderflowCauseBit, false);
|
||||||
|
setFCSRBit(kFCSROverflowCauseBit, false);
|
||||||
|
setFCSRBit(kFCSRInvalidOpCauseBit, false);
|
||||||
|
|
||||||
if (!std::isfinite(original) || !std::isfinite(rounded)) {
|
if (!std::isfinite(original) || !std::isfinite(rounded)) {
|
||||||
setFCSRBit(kFCSRInvalidOpFlagBit, true);
|
setFCSRBit(kFCSRInvalidOpFlagBit, true);
|
||||||
|
setFCSRBit(kFCSRInvalidOpCauseBit, true);
|
||||||
ret = true;
|
ret = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (original != rounded) {
|
if (original != rounded) {
|
||||||
setFCSRBit(kFCSRInexactFlagBit, true);
|
setFCSRBit(kFCSRInexactFlagBit, true);
|
||||||
|
setFCSRBit(kFCSRInexactCauseBit, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (rounded < DBL_MIN && rounded > -DBL_MIN && rounded != 0) {
|
if (rounded < DBL_MIN && rounded > -DBL_MIN && rounded != 0) {
|
||||||
setFCSRBit(kFCSRUnderflowFlagBit, true);
|
setFCSRBit(kFCSRUnderflowFlagBit, true);
|
||||||
|
setFCSRBit(kFCSRUnderflowCauseBit, true);
|
||||||
ret = true;
|
ret = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (rounded > INT_MAX || rounded < INT_MIN) {
|
if (rounded > INT_MAX || rounded < INT_MIN) {
|
||||||
setFCSRBit(kFCSROverflowFlagBit, true);
|
setFCSRBit(kFCSROverflowFlagBit, true);
|
||||||
|
setFCSRBit(kFCSROverflowCauseBit, true);
|
||||||
// The reference is not really clear but it seems this is required:
|
// The reference is not really clear but it seems this is required:
|
||||||
setFCSRBit(kFCSRInvalidOpFlagBit, true);
|
setFCSRBit(kFCSRInvalidOpFlagBit, true);
|
||||||
|
setFCSRBit(kFCSRInvalidOpCauseBit, true);
|
||||||
ret = true;
|
ret = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1621,15 +1631,15 @@ Simulator::get_pc() const
|
||||||
return registers_[pc];
|
return registers_[pc];
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
JS::ProfilingFrameIterator::RegisterState
|
||||||
Simulator::startInterrupt(JitActivation* activation)
|
Simulator::registerState()
|
||||||
{
|
{
|
||||||
JS::ProfilingFrameIterator::RegisterState state;
|
wasm::RegisterState state;
|
||||||
state.pc = (void*) get_pc();
|
state.pc = (void*) get_pc();
|
||||||
state.fp = (void*) getRegister(fp);
|
state.fp = (void*) getRegister(fp);
|
||||||
state.sp = (void*) getRegister(sp);
|
state.sp = (void*) getRegister(sp);
|
||||||
state.lr = (void*) getRegister(ra);
|
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
|
// 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))
|
if (!segment || !segment->isModule() || !segment->containsCodePC(pc))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
startInterrupt(activation);
|
if (!activation->startWasmInterrupt(registerState()))
|
||||||
|
return;
|
||||||
|
|
||||||
set_pc(int32_t(segment->asModule()->interruptCode()));
|
set_pc(int32_t(segment->asModule()->interruptCode()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1682,14 +1694,19 @@ Simulator::handleWasmFault(int32_t addr, unsigned numBytes)
|
||||||
const wasm::ModuleSegment* moduleSegment = segment->asModule();
|
const wasm::ModuleSegment* moduleSegment = segment->asModule();
|
||||||
|
|
||||||
wasm::Instance* instance = wasm::LookupFaultingInstance(*moduleSegment, pc, fp);
|
wasm::Instance* instance = wasm::LookupFaultingInstance(*moduleSegment, pc, fp);
|
||||||
if (!instance || !instance->memoryAccessInGuardRegion((uint8_t*)addr, numBytes))
|
if (!instance)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
MOZ_RELEASE_ASSERT(&instance->code() == &moduleSegment->code());
|
||||||
|
|
||||||
|
if (!instance->memoryAccessInGuardRegion((uint8_t*)addr, numBytes))
|
||||||
|
return false;
|
||||||
|
|
||||||
LLBit_ = false;
|
LLBit_ = false;
|
||||||
|
|
||||||
const wasm::MemoryAccess* memoryAccess = instance->code().lookupMemoryAccess(pc);
|
const wasm::MemoryAccess* memoryAccess = instance->code().lookupMemoryAccess(pc);
|
||||||
if (!memoryAccess) {
|
if (!memoryAccess) {
|
||||||
startInterrupt(act);
|
MOZ_ALWAYS_TRUE(act->startWasmInterrupt(registerState()));
|
||||||
if (!instance->code().containsCodePC(pc))
|
if (!instance->code().containsCodePC(pc))
|
||||||
MOZ_CRASH("Cannot map PC to trap handler");
|
MOZ_CRASH("Cannot map PC to trap handler");
|
||||||
set_pc(int32_t(moduleSegment->outOfBoundsCode()));
|
set_pc(int32_t(moduleSegment->outOfBoundsCode()));
|
||||||
|
@ -1713,7 +1730,6 @@ Simulator::handleWasmTrapFault()
|
||||||
JitActivation* act = cx->activation()->asJit();
|
JitActivation* act = cx->activation()->asJit();
|
||||||
|
|
||||||
void* pc = reinterpret_cast<void*>(get_pc());
|
void* pc = reinterpret_cast<void*>(get_pc());
|
||||||
uint8_t* fp = reinterpret_cast<uint8_t*>(getRegister(Register::fp));
|
|
||||||
|
|
||||||
const wasm::CodeSegment* segment = wasm::LookupCodeSegment(pc);
|
const wasm::CodeSegment* segment = wasm::LookupCodeSegment(pc);
|
||||||
if (!segment || !segment->isModule())
|
if (!segment || !segment->isModule())
|
||||||
|
@ -1725,7 +1741,7 @@ Simulator::handleWasmTrapFault()
|
||||||
if (!moduleSegment->code().lookupTrap(pc, &trap, &bytecode))
|
if (!moduleSegment->code().lookupTrap(pc, &trap, &bytecode))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
act->startWasmTrap(trap, bytecode.offset, pc, fp);
|
act->startWasmTrap(trap, bytecode.offset, registerState());
|
||||||
set_pc(int32_t(moduleSegment->trapCode()));
|
set_pc(int32_t(moduleSegment->trapCode()));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
|
@ -34,6 +34,7 @@
|
||||||
#include "mozilla/Atomics.h"
|
#include "mozilla/Atomics.h"
|
||||||
|
|
||||||
#include "jit/IonTypes.h"
|
#include "jit/IonTypes.h"
|
||||||
|
#include "js/ProfilingFrameIterator.h"
|
||||||
#include "threading/Thread.h"
|
#include "threading/Thread.h"
|
||||||
#include "vm/MutexIDs.h"
|
#include "vm/MutexIDs.h"
|
||||||
#include "wasm/WasmCode.h"
|
#include "wasm/WasmCode.h"
|
||||||
|
@ -77,6 +78,12 @@ const uint32_t kFCSROverflowFlagBit = 4;
|
||||||
const uint32_t kFCSRDivideByZeroFlagBit = 5;
|
const uint32_t kFCSRDivideByZeroFlagBit = 5;
|
||||||
const uint32_t kFCSRInvalidOpFlagBit = 6;
|
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 kFCSRInexactFlagMask = 1 << kFCSRInexactFlagBit;
|
||||||
const uint32_t kFCSRUnderflowFlagMask = 1 << kFCSRUnderflowFlagBit;
|
const uint32_t kFCSRUnderflowFlagMask = 1 << kFCSRUnderflowFlagBit;
|
||||||
const uint32_t kFCSROverflowFlagMask = 1 << kFCSROverflowFlagBit;
|
const uint32_t kFCSROverflowFlagMask = 1 << kFCSROverflowFlagBit;
|
||||||
|
@ -299,7 +306,7 @@ class Simulator {
|
||||||
|
|
||||||
// Handle a wasm interrupt triggered by an async signal handler.
|
// Handle a wasm interrupt triggered by an async signal handler.
|
||||||
void handleWasmInterrupt();
|
void handleWasmInterrupt();
|
||||||
void startInterrupt(JitActivation* act);
|
JS::ProfilingFrameIterator::RegisterState registerState();
|
||||||
|
|
||||||
// Handle any wasm faults, returning true if the fault was handled.
|
// Handle any wasm faults, returning true if the fault was handled.
|
||||||
bool handleWasmFault(int32_t addr, unsigned numBytes);
|
bool handleWasmFault(int32_t addr, unsigned numBytes);
|
||||||
|
|
|
@ -41,7 +41,7 @@ class FloatRegisters : public FloatRegistersMIPSShared
|
||||||
static Encoding FromName(const char* name);
|
static Encoding FromName(const char* name);
|
||||||
|
|
||||||
static const uint32_t Total = 32 * NumTypes;
|
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.
|
// When saving all registers we only need to do is save double registers.
|
||||||
static const uint32_t TotalPhys = 32;
|
static const uint32_t TotalPhys = 32;
|
||||||
|
|
||||||
|
@ -79,10 +79,7 @@ class FloatRegisters : public FloatRegistersMIPSShared
|
||||||
static const SetType WrapperMask = VolatileMask;
|
static const SetType WrapperMask = VolatileMask;
|
||||||
|
|
||||||
static const SetType NonAllocatableMask =
|
static const SetType NonAllocatableMask =
|
||||||
( // f21 and f23 are MIPS scratch float registers.
|
(1U << FloatRegisters::f23) * Spread;
|
||||||
(1U << FloatRegisters::f21) |
|
|
||||||
(1U << FloatRegisters::f23)
|
|
||||||
) * Spread;
|
|
||||||
|
|
||||||
static const SetType AllocatableMask = AllMask & ~NonAllocatableMask;
|
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 ReturnDoubleReg = { FloatRegisters::f0, FloatRegisters::Double };
|
||||||
static constexpr FloatRegister ScratchFloat32Reg = { FloatRegisters::f23, FloatRegisters::Single };
|
static constexpr FloatRegister ScratchFloat32Reg = { FloatRegisters::f23, FloatRegisters::Single };
|
||||||
static constexpr FloatRegister ScratchDoubleReg = { FloatRegisters::f23, FloatRegisters::Double };
|
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
|
struct ScratchFloat32Scope : public AutoFloatRegisterScope
|
||||||
{
|
{
|
||||||
|
|
|
@ -594,18 +594,35 @@ void
|
||||||
CodeGeneratorMIPS64::visitWasmTruncateToInt64(LWasmTruncateToInt64* lir)
|
CodeGeneratorMIPS64::visitWasmTruncateToInt64(LWasmTruncateToInt64* lir)
|
||||||
{
|
{
|
||||||
FloatRegister input = ToFloatRegister(lir->input());
|
FloatRegister input = ToFloatRegister(lir->input());
|
||||||
Register output = ToRegister(lir->output());
|
Register64 output = ToOutRegister64(lir);
|
||||||
|
|
||||||
MWasmTruncateToInt64* mir = lir->mir();
|
MWasmTruncateToInt64* mir = lir->mir();
|
||||||
MIRType fromType = mir->input()->type();
|
MIRType fromType = mir->input()->type();
|
||||||
|
|
||||||
MOZ_ASSERT(fromType == MIRType::Double || fromType == MIRType::Float32);
|
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);
|
addOutOfLineCode(ool, mir);
|
||||||
|
|
||||||
masm.wasmTruncateToI64(input, output, fromType, mir->isUnsigned(),
|
Label* oolEntry = ool->entry();
|
||||||
ool->entry(), ool->rejoin());
|
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
|
void
|
||||||
|
|
|
@ -744,6 +744,30 @@ MacroAssembler::branchToComputedAddress(const BaseIndex& addr)
|
||||||
as_nop();
|
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.
|
// Memory access primitives.
|
||||||
void
|
void
|
||||||
|
|
|
@ -58,20 +58,8 @@ MacroAssemblerMIPS64Compat::convertInt32ToDouble(const BaseIndex& src, FloatRegi
|
||||||
void
|
void
|
||||||
MacroAssemblerMIPS64Compat::convertUInt32ToDouble(Register src, FloatRegister dest)
|
MacroAssemblerMIPS64Compat::convertUInt32ToDouble(Register src, FloatRegister dest)
|
||||||
{
|
{
|
||||||
// We use SecondScratchDoubleReg because MacroAssembler::loadFromTypedArray
|
ma_dext(ScratchRegister, src, Imm32(0), Imm32(32));
|
||||||
// calls with ScratchDoubleReg as dest.
|
asMasm().convertInt64ToDouble(Register64(ScratchRegister), 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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
|
@ -101,19 +89,8 @@ MacroAssemblerMIPS64Compat::convertUInt64ToDouble(Register src, FloatRegister de
|
||||||
void
|
void
|
||||||
MacroAssemblerMIPS64Compat::convertUInt32ToFloat32(Register src, FloatRegister dest)
|
MacroAssemblerMIPS64Compat::convertUInt32ToFloat32(Register src, FloatRegister dest)
|
||||||
{
|
{
|
||||||
Label positive, done;
|
ma_dext(ScratchRegister, src, Imm32(0), Imm32(32));
|
||||||
ma_b(src, src, &positive, NotSigned, ShortJump);
|
asMasm().convertInt64ToFloat32(Register64(ScratchRegister), dest);
|
||||||
|
|
||||||
// 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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
|
@ -135,12 +112,12 @@ MacroAssemblerMIPS64Compat::convertDoubleToInt32(FloatRegister src, Register des
|
||||||
ma_b(dest, Imm32(1), fail, Assembler::Equal);
|
ma_b(dest, Imm32(1), fail, Assembler::Equal);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convert double to int, then convert back and check if we have the
|
// Truncate double to int ; if result is inexact fail
|
||||||
// same number.
|
as_truncwd(ScratchFloat32Reg, src);
|
||||||
as_cvtwd(ScratchDoubleReg, src);
|
as_cfc1(ScratchRegister, Assembler::FCSR);
|
||||||
as_mfc1(dest, ScratchDoubleReg);
|
moveFromFloat32(ScratchFloat32Reg, dest);
|
||||||
as_cvtdw(ScratchDoubleReg, ScratchDoubleReg);
|
ma_ext(ScratchRegister, ScratchRegister, Assembler::CauseI, 1);
|
||||||
ma_bc1d(src, ScratchDoubleReg, fail, Assembler::DoubleNotEqualOrUnordered);
|
ma_b(ScratchRegister, Imm32(0), fail, Assembler::NotEqual);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Checks whether a float32 is representable as a 32-bit integer. If so, the
|
// 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);
|
ma_b(dest, Imm32(INT32_MIN), fail, Assembler::Equal);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Converting the floating point value to an integer and then converting it
|
as_truncws(ScratchFloat32Reg, src);
|
||||||
// back to a float32 would not work, as float to int32 conversions are
|
as_cfc1(ScratchRegister, Assembler::FCSR);
|
||||||
// clamping (e.g. float(INT32_MAX + 1) would get converted into INT32_MAX
|
moveFromFloat32(ScratchFloat32Reg, dest);
|
||||||
// and then back to float(INT32_MAX + 1)). If this ever happens, we just
|
ma_ext(ScratchRegister, ScratchRegister, Assembler::CauseI, 1);
|
||||||
// bail out.
|
ma_b(ScratchRegister, Imm32(0), fail, Assembler::NotEqual);
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
|
@ -855,8 +825,13 @@ MacroAssemblerMIPS64::ma_lid(FloatRegister dest, double value)
|
||||||
{
|
{
|
||||||
ImmWord imm(mozilla::BitwiseCast<uint64_t>(value));
|
ImmWord imm(mozilla::BitwiseCast<uint64_t>(value));
|
||||||
|
|
||||||
ma_li(ScratchRegister, imm);
|
if(imm.value != 0){
|
||||||
moveToDouble(ScratchRegister, dest);
|
ma_li(ScratchRegister, imm);
|
||||||
|
moveToDouble(ScratchRegister, dest);
|
||||||
|
} else {
|
||||||
|
moveToDouble(zero, dest);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
|
@ -1363,49 +1338,19 @@ MacroAssemblerMIPS64Compat::storeUnalignedDouble(const wasm::MemoryAccessDesc& a
|
||||||
append(access, store.getOffset(), asMasm().framePushed());
|
append(access, store.getOffset(), asMasm().framePushed());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Note: this function clobbers the input register.
|
|
||||||
void
|
void
|
||||||
MacroAssembler::clampDoubleToUint8(FloatRegister input, Register output)
|
MacroAssembler::clampDoubleToUint8(FloatRegister input, Register output)
|
||||||
{
|
{
|
||||||
MOZ_ASSERT(input != ScratchDoubleReg);
|
as_roundwd(ScratchDoubleReg, input);
|
||||||
Label positive, done;
|
ma_li(ScratchRegister, Imm32(255));
|
||||||
|
as_mfc1(output, ScratchDoubleReg);
|
||||||
// <= 0 or NaN --> 0
|
zeroDouble(ScratchDoubleReg);
|
||||||
zeroDouble(ScratchDoubleReg);
|
as_sltiu(SecondScratchReg, output, 255);
|
||||||
branchDouble(DoubleGreaterThan, input, ScratchDoubleReg, &positive);
|
as_colt(DoubleFloat, ScratchDoubleReg, input);
|
||||||
{
|
// if res > 255; res = 255;
|
||||||
move32(Imm32(0), output);
|
as_movz(output, ScratchRegister, SecondScratchReg);
|
||||||
jump(&done);
|
// if !(input > 0); res = 0;
|
||||||
}
|
as_movf(output, zero);
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
|
@ -2441,33 +2386,22 @@ void
|
||||||
MacroAssembler::wasmTruncateDoubleToUInt32(FloatRegister input, Register output, bool isSaturating,
|
MacroAssembler::wasmTruncateDoubleToUInt32(FloatRegister input, Register output, bool isSaturating,
|
||||||
Label* oolEntry)
|
Label* oolEntry)
|
||||||
{
|
{
|
||||||
MOZ_ASSERT(!isSaturating, "NYI");
|
|
||||||
|
|
||||||
as_truncld(ScratchDoubleReg, input);
|
as_truncld(ScratchDoubleReg, input);
|
||||||
moveFromDoubleHi(ScratchDoubleReg, output);
|
moveFromDouble(ScratchDoubleReg, output);
|
||||||
as_cfc1(ScratchRegister, Assembler::FCSR);
|
ma_dsrl(ScratchRegister, output, Imm32(32));
|
||||||
ma_ext(ScratchRegister, ScratchRegister, 6, 1);
|
as_sll(output, output, 0);
|
||||||
ma_or(ScratchRegister, output);
|
|
||||||
moveFromFloat32(ScratchDoubleReg, output);
|
|
||||||
ma_b(ScratchRegister, Imm32(0), oolEntry, Assembler::NotEqual);
|
ma_b(ScratchRegister, Imm32(0), oolEntry, Assembler::NotEqual);
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
MacroAssembler::wasmTruncateFloat32ToUInt32(FloatRegister input, Register output, bool isSaturating,
|
MacroAssembler::wasmTruncateFloat32ToUInt32(FloatRegister input, Register output, bool isSaturating,
|
||||||
Label* oolEntry)
|
Label* oolEntry)
|
||||||
{
|
{
|
||||||
MOZ_ASSERT(!isSaturating, "NYI");
|
|
||||||
|
|
||||||
as_truncls(ScratchDoubleReg, input);
|
as_truncls(ScratchDoubleReg, input);
|
||||||
moveFromDoubleHi(ScratchDoubleReg, output);
|
moveFromDouble(ScratchDoubleReg, output);
|
||||||
as_cfc1(ScratchRegister, Assembler::FCSR);
|
ma_dsrl(ScratchRegister, output, Imm32(32));
|
||||||
ma_ext(ScratchRegister, ScratchRegister, 6, 1);
|
as_sll(output, output, 0);
|
||||||
ma_or(ScratchRegister, output);
|
|
||||||
moveFromFloat32(ScratchDoubleReg, output);
|
|
||||||
ma_b(ScratchRegister, Imm32(0), oolEntry, Assembler::NotEqual);
|
ma_b(ScratchRegister, Imm32(0), oolEntry, Assembler::NotEqual);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
|
@ -2501,113 +2435,117 @@ MacroAssembler::wasmUnalignedStoreI64(const wasm::MemoryAccessDesc& access, Regi
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
MacroAssembler::wasmTruncateDoubleToInt64(FloatRegister input, Register64 output, bool,
|
MacroAssembler::wasmTruncateDoubleToInt64(FloatRegister input, Register64 output,
|
||||||
Label* oolEntry, Label* oolRejoin,
|
bool isSaturating, Label* oolEntry,
|
||||||
FloatRegister tempDouble)
|
Label* oolRejoin, FloatRegister tempDouble)
|
||||||
{
|
{
|
||||||
MOZ_ASSERT(tempDouble.isInvalid());
|
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
|
void
|
||||||
MacroAssembler::wasmTruncateDoubleToUInt64(FloatRegister input, Register64 output, bool,
|
MacroAssembler::wasmTruncateDoubleToUInt64(FloatRegister input, Register64 output_,
|
||||||
Label* oolEntry, Label* oolRejoin,
|
bool isSaturating, Label* oolEntry,
|
||||||
FloatRegister tempDouble)
|
Label* oolRejoin, FloatRegister tempDouble)
|
||||||
{
|
{
|
||||||
MOZ_ASSERT(tempDouble.isInvalid());
|
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
|
void
|
||||||
MacroAssembler::wasmTruncateFloat32ToInt64(FloatRegister input, Register64 output, bool,
|
MacroAssembler::wasmTruncateFloat32ToInt64(FloatRegister input, Register64 output,
|
||||||
Label* oolEntry, Label* oolRejoin,
|
bool isSaturating, Label* oolEntry,
|
||||||
FloatRegister tempFloat)
|
Label* oolRejoin, FloatRegister tempFloat)
|
||||||
{
|
{
|
||||||
MOZ_ASSERT(tempFloat.isInvalid());
|
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
|
void
|
||||||
MacroAssembler::wasmTruncateFloat32ToUInt64(FloatRegister input, Register64 output, bool,
|
MacroAssembler::wasmTruncateFloat32ToUInt64(FloatRegister input, Register64 output_,
|
||||||
Label* oolEntry, Label* oolRejoin,
|
bool isSaturating, Label* oolEntry,
|
||||||
FloatRegister tempFloat)
|
Label* oolRejoin, FloatRegister tempFloat)
|
||||||
{
|
{
|
||||||
MOZ_ASSERT(tempFloat.isInvalid());
|
MOZ_ASSERT(tempFloat.isInvalid());
|
||||||
wasmTruncateToI64(input, output.reg, MIRType::Float32, true, oolEntry, oolRejoin);
|
Register output = output_.reg;
|
||||||
}
|
|
||||||
|
|
||||||
void
|
Label done;
|
||||||
MacroAssemblerMIPS64Compat::wasmTruncateToI64(FloatRegister input, Register output, MIRType fromType,
|
|
||||||
bool isUnsigned, Label* oolEntry, Label* oolRejoin)
|
|
||||||
{
|
|
||||||
if (isUnsigned) {
|
|
||||||
Label isLarge, done;
|
|
||||||
|
|
||||||
if (fromType == MIRType::Double) {
|
as_truncls(ScratchDoubleReg, input);
|
||||||
asMasm().loadConstantDouble(double(INT64_MAX), ScratchDoubleReg);
|
// ma_li INT64_MAX
|
||||||
asMasm().ma_bc1d(ScratchDoubleReg, input, &isLarge,
|
ma_li(SecondScratchReg, Imm32(-1));
|
||||||
Assembler::DoubleLessThanOrEqual, ShortJump);
|
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);
|
loadConstantFloat32(float(INT64_MAX + 1ULL), ScratchFloat32Reg);
|
||||||
} else {
|
// ma_li INT64_MIN
|
||||||
asMasm().loadConstantFloat32(float(INT64_MAX), ScratchFloat32Reg);
|
ma_daddu(SecondScratchReg, Imm32(1));
|
||||||
asMasm().ma_bc1s(ScratchFloat32Reg, input, &isLarge,
|
as_subs(ScratchFloat32Reg, input, ScratchFloat32Reg);
|
||||||
Assembler::DoubleLessThanOrEqual, ShortJump);
|
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.
|
ma_b(ScratchRegister, Imm32(0), oolEntry, Assembler::NotEqual);
|
||||||
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);
|
|
||||||
|
|
||||||
asMasm().ma_b(&done, ShortJump);
|
bind(&done);
|
||||||
|
|
||||||
// The input is greater than double(INT64_MAX).
|
if (isSaturating)
|
||||||
asMasm().bind(&isLarge);
|
bind(oolRejoin);
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
|
|
|
@ -733,8 +733,6 @@ class MacroAssemblerMIPS64Compat : public MacroAssemblerMIPS64
|
||||||
|
|
||||||
void convertUInt64ToDouble(Register src, FloatRegister dest);
|
void convertUInt64ToDouble(Register src, FloatRegister dest);
|
||||||
|
|
||||||
void wasmTruncateToI64(FloatRegister input, Register output, MIRType fromType,
|
|
||||||
bool isUnsigned, Label* oolEntry, Label* oolRejoin);
|
|
||||||
|
|
||||||
void breakpoint();
|
void breakpoint();
|
||||||
|
|
||||||
|
|
|
@ -1573,23 +1573,34 @@ Simulator::setFCSRRoundError(double original, double rounded)
|
||||||
{
|
{
|
||||||
bool ret = false;
|
bool ret = false;
|
||||||
|
|
||||||
|
setFCSRBit(kFCSRInexactCauseBit, false);
|
||||||
|
setFCSRBit(kFCSRUnderflowCauseBit, false);
|
||||||
|
setFCSRBit(kFCSROverflowCauseBit, false);
|
||||||
|
setFCSRBit(kFCSRInvalidOpCauseBit, false);
|
||||||
|
|
||||||
if (!std::isfinite(original) || !std::isfinite(rounded)) {
|
if (!std::isfinite(original) || !std::isfinite(rounded)) {
|
||||||
setFCSRBit(kFCSRInvalidOpFlagBit, true);
|
setFCSRBit(kFCSRInvalidOpFlagBit, true);
|
||||||
|
setFCSRBit(kFCSRInvalidOpCauseBit, true);
|
||||||
ret = true;
|
ret = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (original != rounded)
|
if (original != rounded) {
|
||||||
setFCSRBit(kFCSRInexactFlagBit, true);
|
setFCSRBit(kFCSRInexactFlagBit, true);
|
||||||
|
setFCSRBit(kFCSRInexactCauseBit, true);
|
||||||
|
}
|
||||||
|
|
||||||
if (rounded < DBL_MIN && rounded > -DBL_MIN && rounded != 0) {
|
if (rounded < DBL_MIN && rounded > -DBL_MIN && rounded != 0) {
|
||||||
setFCSRBit(kFCSRUnderflowFlagBit, true);
|
setFCSRBit(kFCSRUnderflowFlagBit, true);
|
||||||
|
setFCSRBit(kFCSRUnderflowCauseBit, true);
|
||||||
ret = true;
|
ret = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (rounded > INT_MAX || rounded < INT_MIN) {
|
if (rounded > INT_MAX || rounded < INT_MIN) {
|
||||||
setFCSRBit(kFCSROverflowFlagBit, true);
|
setFCSRBit(kFCSROverflowFlagBit, true);
|
||||||
|
setFCSRBit(kFCSROverflowCauseBit, true);
|
||||||
// The reference is not really clear but it seems this is required:
|
// The reference is not really clear but it seems this is required:
|
||||||
setFCSRBit(kFCSRInvalidOpFlagBit, true);
|
setFCSRBit(kFCSRInvalidOpFlagBit, true);
|
||||||
|
setFCSRBit(kFCSRInvalidOpCauseBit, true);
|
||||||
ret = true;
|
ret = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1617,15 +1628,15 @@ Simulator::get_pc() const
|
||||||
return registers_[pc];
|
return registers_[pc];
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
JS::ProfilingFrameIterator::RegisterState
|
||||||
Simulator::startInterrupt(JitActivation* activation)
|
Simulator::registerState()
|
||||||
{
|
{
|
||||||
JS::ProfilingFrameIterator::RegisterState state;
|
wasm::RegisterState state;
|
||||||
state.pc = (void*) get_pc();
|
state.pc = (void*) get_pc();
|
||||||
state.fp = (void*) getRegister(fp);
|
state.fp = (void*) getRegister(fp);
|
||||||
state.sp = (void*) getRegister(sp);
|
state.sp = (void*) getRegister(sp);
|
||||||
state.lr = (void*) getRegister(ra);
|
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
|
// The signal handler only redirects the PC to the interrupt stub when the PC is
|
||||||
|
@ -1651,7 +1662,9 @@ Simulator::handleWasmInterrupt()
|
||||||
if (!fp)
|
if (!fp)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
startInterrupt(activation);
|
if (!activation->startWasmInterrupt(registerState()))
|
||||||
|
return;
|
||||||
|
|
||||||
set_pc(int64_t(segment->asModule()->interruptCode()));
|
set_pc(int64_t(segment->asModule()->interruptCode()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1681,14 +1694,19 @@ Simulator::handleWasmFault(uint64_t addr, unsigned numBytes)
|
||||||
const wasm::ModuleSegment* moduleSegment = segment->asModule();
|
const wasm::ModuleSegment* moduleSegment = segment->asModule();
|
||||||
|
|
||||||
wasm::Instance* instance = wasm::LookupFaultingInstance(*moduleSegment, pc, fp);
|
wasm::Instance* instance = wasm::LookupFaultingInstance(*moduleSegment, pc, fp);
|
||||||
if (!instance || !instance->memoryAccessInGuardRegion((uint8_t*)addr, numBytes))
|
if (!instance)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
MOZ_RELEASE_ASSERT(&instance->code() == &moduleSegment->code());
|
||||||
|
|
||||||
|
if (!instance->memoryAccessInGuardRegion((uint8_t*)addr, numBytes))
|
||||||
|
return false;
|
||||||
|
|
||||||
LLBit_ = false;
|
LLBit_ = false;
|
||||||
|
|
||||||
const wasm::MemoryAccess* memoryAccess = instance->code().lookupMemoryAccess(pc);
|
const wasm::MemoryAccess* memoryAccess = instance->code().lookupMemoryAccess(pc);
|
||||||
if (!memoryAccess) {
|
if (!memoryAccess) {
|
||||||
startInterrupt(act);
|
MOZ_ALWAYS_TRUE(act->startWasmInterrupt(registerState()));
|
||||||
if (!instance->code().containsCodePC(pc))
|
if (!instance->code().containsCodePC(pc))
|
||||||
MOZ_CRASH("Cannot map PC to trap handler");
|
MOZ_CRASH("Cannot map PC to trap handler");
|
||||||
set_pc(int64_t(moduleSegment->outOfBoundsCode()));
|
set_pc(int64_t(moduleSegment->outOfBoundsCode()));
|
||||||
|
@ -1712,7 +1730,6 @@ Simulator::handleWasmTrapFault()
|
||||||
JitActivation* act = cx->activation()->asJit();
|
JitActivation* act = cx->activation()->asJit();
|
||||||
|
|
||||||
void* pc = reinterpret_cast<void*>(get_pc());
|
void* pc = reinterpret_cast<void*>(get_pc());
|
||||||
uint8_t* fp = reinterpret_cast<uint8_t*>(getRegister(Register::fp));
|
|
||||||
|
|
||||||
const wasm::CodeSegment* segment = wasm::LookupCodeSegment(pc);
|
const wasm::CodeSegment* segment = wasm::LookupCodeSegment(pc);
|
||||||
if (!segment || !segment->isModule())
|
if (!segment || !segment->isModule())
|
||||||
|
@ -1724,7 +1741,7 @@ Simulator::handleWasmTrapFault()
|
||||||
if (!moduleSegment->code().lookupTrap(pc, &trap, &bytecode))
|
if (!moduleSegment->code().lookupTrap(pc, &trap, &bytecode))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
act->startWasmTrap(trap, bytecode.offset, pc, fp);
|
act->startWasmTrap(trap, bytecode.offset, registerState());
|
||||||
set_pc(int64_t(moduleSegment->trapCode()));
|
set_pc(int64_t(moduleSegment->trapCode()));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -4007,9 +4024,6 @@ Simulator::instructionDecode(SimInstruction* instr)
|
||||||
void
|
void
|
||||||
Simulator::branchDelayInstructionDecode(SimInstruction* instr)
|
Simulator::branchDelayInstructionDecode(SimInstruction* instr)
|
||||||
{
|
{
|
||||||
if (single_stepping_)
|
|
||||||
single_step_callback_(single_step_callback_arg_, this, (void*)instr);
|
|
||||||
|
|
||||||
if (instr->instructionBits() == NopInst) {
|
if (instr->instructionBits() == NopInst) {
|
||||||
// Short-cut generic nop instructions. They are always valid and they
|
// Short-cut generic nop instructions. They are always valid and they
|
||||||
// never change the simulator state.
|
// never change the simulator state.
|
||||||
|
|
|
@ -35,6 +35,7 @@
|
||||||
#include "mozilla/Atomics.h"
|
#include "mozilla/Atomics.h"
|
||||||
|
|
||||||
#include "jit/IonTypes.h"
|
#include "jit/IonTypes.h"
|
||||||
|
#include "js/ProfilingFrameIterator.h"
|
||||||
#include "threading/Thread.h"
|
#include "threading/Thread.h"
|
||||||
#include "vm/MutexIDs.h"
|
#include "vm/MutexIDs.h"
|
||||||
|
|
||||||
|
@ -82,6 +83,12 @@ const uint32_t kFCSROverflowFlagBit = 4;
|
||||||
const uint32_t kFCSRDivideByZeroFlagBit = 5;
|
const uint32_t kFCSRDivideByZeroFlagBit = 5;
|
||||||
const uint32_t kFCSRInvalidOpFlagBit = 6;
|
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 kFCSRInexactFlagMask = 1 << kFCSRInexactFlagBit;
|
||||||
const uint32_t kFCSRUnderflowFlagMask = 1 << kFCSRUnderflowFlagBit;
|
const uint32_t kFCSRUnderflowFlagMask = 1 << kFCSRUnderflowFlagBit;
|
||||||
const uint32_t kFCSROverflowFlagMask = 1 << kFCSROverflowFlagBit;
|
const uint32_t kFCSROverflowFlagMask = 1 << kFCSROverflowFlagBit;
|
||||||
|
@ -314,7 +321,7 @@ class Simulator {
|
||||||
|
|
||||||
// Handle a wasm interrupt triggered by an async signal handler.
|
// Handle a wasm interrupt triggered by an async signal handler.
|
||||||
void handleWasmInterrupt();
|
void handleWasmInterrupt();
|
||||||
void startInterrupt(JitActivation* act);
|
JS::ProfilingFrameIterator::RegisterState registerState();
|
||||||
|
|
||||||
// Handle any wasm faults, returning true if the fault was handled.
|
// Handle any wasm faults, returning true if the fault was handled.
|
||||||
bool handleWasmFault(uint64_t addr, unsigned numBytes);
|
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.
|
// 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:
|
public:
|
||||||
LIR_HEADER(TypeBarrierV)
|
LIR_HEADER(TypeBarrierV)
|
||||||
|
|
||||||
LTypeBarrierV(const LBoxAllocation& input, const LDefinition& temp) {
|
LTypeBarrierV(const LBoxAllocation& input, const LDefinition& unboxTemp,
|
||||||
|
const LDefinition& objTemp) {
|
||||||
setBoxOperand(Input, input);
|
setBoxOperand(Input, input);
|
||||||
setTemp(0, temp);
|
setTemp(0, unboxTemp);
|
||||||
|
setTemp(1, objTemp);
|
||||||
}
|
}
|
||||||
|
|
||||||
static const size_t Input = 0;
|
static const size_t Input = 0;
|
||||||
|
@ -7803,9 +7805,12 @@ class LTypeBarrierV : public LInstructionHelper<0, BOX_PIECES, 1>
|
||||||
const MTypeBarrier* mir() const {
|
const MTypeBarrier* mir() const {
|
||||||
return mir_->toTypeBarrier();
|
return mir_->toTypeBarrier();
|
||||||
}
|
}
|
||||||
const LDefinition* temp() {
|
const LDefinition* unboxTemp() {
|
||||||
return getTemp(0);
|
return getTemp(0);
|
||||||
}
|
}
|
||||||
|
const LDefinition* objTemp() {
|
||||||
|
return getTemp(1);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Guard that a object is in a TypeSet.
|
// 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.
|
// 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:
|
public:
|
||||||
LIR_HEADER(MonitorTypes)
|
LIR_HEADER(MonitorTypes)
|
||||||
|
|
||||||
LMonitorTypes(const LBoxAllocation& input, const LDefinition& temp) {
|
LMonitorTypes(const LBoxAllocation& input, const LDefinition& unboxTemp,
|
||||||
|
const LDefinition& objTemp) {
|
||||||
setBoxOperand(Input, input);
|
setBoxOperand(Input, input);
|
||||||
setTemp(0, temp);
|
setTemp(0, unboxTemp);
|
||||||
|
setTemp(1, objTemp);
|
||||||
}
|
}
|
||||||
|
|
||||||
static const size_t Input = 0;
|
static const size_t Input = 0;
|
||||||
|
@ -7845,9 +7852,12 @@ class LMonitorTypes : public LInstructionHelper<0, BOX_PIECES, 1>
|
||||||
const MMonitorTypes* mir() const {
|
const MMonitorTypes* mir() const {
|
||||||
return mir_->toMonitorTypes();
|
return mir_->toMonitorTypes();
|
||||||
}
|
}
|
||||||
const LDefinition* temp() {
|
const LDefinition* unboxTemp() {
|
||||||
return getTemp(0);
|
return getTemp(0);
|
||||||
}
|
}
|
||||||
|
const LDefinition* objTemp() {
|
||||||
|
return getTemp(1);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Generational write barrier used when writing an object to another object.
|
// Generational write barrier used when writing an object to another object.
|
||||||
|
|
|
@ -1427,6 +1427,14 @@ static const LiveRegisterSet AllRegsExceptPCSP(
|
||||||
(uint32_t(1) << Registers::pc))),
|
(uint32_t(1) << Registers::pc))),
|
||||||
FloatRegisterSet(FloatRegisters::AllDoubleMask));
|
FloatRegisterSet(FloatRegisters::AllDoubleMask));
|
||||||
static_assert(!SupportsSimd, "high lanes of SIMD registers need to be saved too.");
|
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
|
#else
|
||||||
static const LiveRegisterSet AllRegsExceptSP(
|
static const LiveRegisterSet AllRegsExceptSP(
|
||||||
GeneralRegisterSet(Registers::AllMask & ~(uint32_t(1) << Registers::StackPointer)),
|
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)
|
#elif defined(JS_CODEGEN_MIPS32) || defined(JS_CODEGEN_MIPS64)
|
||||||
// Reserve space to store resumePC and HeapReg.
|
// Reserve space to store resumePC and HeapReg.
|
||||||
masm.subFromStackPtr(Imm32(2 * sizeof(intptr_t)));
|
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);
|
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.moveStackPtrTo(s0);
|
||||||
|
masm.as_cfc1(s1, Assembler::FCSR);
|
||||||
|
|
||||||
// Align the stack.
|
// Align the stack.
|
||||||
masm.ma_and(StackPointer, StackPointer, Imm32(~(ABIStackAlignment - 1)));
|
masm.ma_and(StackPointer, StackPointer, Imm32(~(ABIStackAlignment - 1)));
|
||||||
|
|
||||||
|
@ -1509,19 +1519,18 @@ GenerateInterruptExit(MacroAssembler& masm, Label* throwLabel, Offsets* offsets)
|
||||||
masm.assertStackAlignment(ABIStackAlignment);
|
masm.assertStackAlignment(ABIStackAlignment);
|
||||||
masm.call(SymbolicAddress::HandleExecutionInterrupt);
|
masm.call(SymbolicAddress::HandleExecutionInterrupt);
|
||||||
|
|
||||||
# ifdef USES_O32_ABI
|
|
||||||
masm.addToStackPtr(Imm32(4 * sizeof(intptr_t)));
|
|
||||||
# endif
|
|
||||||
|
|
||||||
masm.branchTestPtr(Assembler::Zero, ReturnReg, ReturnReg, throwLabel);
|
masm.branchTestPtr(Assembler::Zero, ReturnReg, ReturnReg, throwLabel);
|
||||||
|
|
||||||
// This will restore stack to the address before the call.
|
// This will restore stack to the address before the call.
|
||||||
masm.moveToStackPtr(s0);
|
masm.moveToStackPtr(s0);
|
||||||
|
|
||||||
|
// Restore FCSR.
|
||||||
|
masm.as_ctc1(s1, Assembler::FCSR);
|
||||||
|
|
||||||
// Store resumePC into the reserved space.
|
// Store resumePC into the reserved space.
|
||||||
masm.storePtr(ReturnReg, Address(s0, masm.framePushed()));
|
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
|
// Pop resumePC into PC. Clobber HeapReg to make the jump and restore it
|
||||||
// during jump delay slot.
|
// during jump delay slot.
|
||||||
|
|
|
@ -137,7 +137,7 @@ class ReftestRunner(MozbuildObject):
|
||||||
args.printDeviceInfo = False
|
args.printDeviceInfo = False
|
||||||
|
|
||||||
from mozrunner.devices.android_device import grant_runtime_permissions, get_adb_path
|
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:
|
if not args.adb_path:
|
||||||
args.adb_path = get_adb_path(self)
|
args.adb_path = get_adb_path(self)
|
||||||
|
|
|
@ -860,6 +860,10 @@ public abstract class GeckoApp extends GeckoActivity
|
||||||
public void onFocusRequest(final GeckoSession session) {
|
public void onFocusRequest(final GeckoSession session) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override // GeckoSession.ContentListener
|
||||||
|
public void onCloseRequest(final GeckoSession session) {
|
||||||
|
}
|
||||||
|
|
||||||
@Override // GeckoSession.ContentListener
|
@Override // GeckoSession.ContentListener
|
||||||
public void onFullScreen(final GeckoSession session, final boolean fullScreen) {
|
public void onFullScreen(final GeckoSession session, final boolean fullScreen) {
|
||||||
if (fullScreen) {
|
if (fullScreen) {
|
||||||
|
|
|
@ -649,6 +649,13 @@ public class CustomTabsActivity extends AppCompatActivity
|
||||||
return true;
|
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 */
|
/* GeckoSession.ProgressListener */
|
||||||
@Override
|
@Override
|
||||||
public void onPageStart(GeckoSession session, String url) {
|
public void onPageStart(GeckoSession session, String url) {
|
||||||
|
@ -686,6 +693,11 @@ public class CustomTabsActivity extends AppCompatActivity
|
||||||
startActivity(intent);
|
startActivity(intent);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCloseRequest(GeckoSession session) {
|
||||||
|
// Ignore
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onFullScreen(GeckoSession session, boolean fullScreen) {
|
public void onFullScreen(GeckoSession session, boolean fullScreen) {
|
||||||
ActivityUtils.setFullScreen(this, fullScreen);
|
ActivityUtils.setFullScreen(this, fullScreen);
|
||||||
|
|
|
@ -356,6 +356,11 @@ public class WebAppActivity extends AppCompatActivity
|
||||||
startActivity(intent);
|
startActivity(intent);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override // GeckoSession.ContentListener
|
||||||
|
public void onCloseRequest(GeckoSession session) {
|
||||||
|
// Ignore
|
||||||
|
}
|
||||||
|
|
||||||
@Override // GeckoSession.ContentListener
|
@Override // GeckoSession.ContentListener
|
||||||
public void onContextMenu(GeckoSession session, int screenX, int screenY,
|
public void onContextMenu(GeckoSession session, int screenX, int screenY,
|
||||||
String uri, String elementSrc) {
|
String uri, String elementSrc) {
|
||||||
|
@ -422,6 +427,13 @@ public class WebAppActivity extends AppCompatActivity
|
||||||
return true;
|
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() {
|
private void updateFullScreen() {
|
||||||
boolean fullScreen = mIsFullScreenContent || mIsFullScreenMode;
|
boolean fullScreen = mIsFullScreenContent || mIsFullScreenMode;
|
||||||
if (ActivityUtils.isFullScreen(this) == fullScreen) {
|
if (ActivityUtils.isFullScreen(this) == fullScreen) {
|
||||||
|
|
|
@ -24,6 +24,7 @@ class GeckoViewContent extends GeckoViewContentModule {
|
||||||
|
|
||||||
addEventListener("DOMTitleChanged", this, false);
|
addEventListener("DOMTitleChanged", this, false);
|
||||||
addEventListener("DOMWindowFocus", this, false);
|
addEventListener("DOMWindowFocus", this, false);
|
||||||
|
addEventListener("DOMWindowClose", this, false);
|
||||||
addEventListener("MozDOMFullscreen:Entered", this, false);
|
addEventListener("MozDOMFullscreen:Entered", this, false);
|
||||||
addEventListener("MozDOMFullscreen:Exit", this, false);
|
addEventListener("MozDOMFullscreen:Exit", this, false);
|
||||||
addEventListener("MozDOMFullscreen:Exited", this, false);
|
addEventListener("MozDOMFullscreen:Exited", this, false);
|
||||||
|
@ -43,6 +44,7 @@ class GeckoViewContent extends GeckoViewContentModule {
|
||||||
|
|
||||||
removeEventListener("DOMTitleChanged", this);
|
removeEventListener("DOMTitleChanged", this);
|
||||||
removeEventListener("DOMWindowFocus", this);
|
removeEventListener("DOMWindowFocus", this);
|
||||||
|
removeEventListener("DOMWindowClose", this);
|
||||||
removeEventListener("MozDOMFullscreen:Entered", this);
|
removeEventListener("MozDOMFullscreen:Entered", this);
|
||||||
removeEventListener("MozDOMFullscreen:Exit", this);
|
removeEventListener("MozDOMFullscreen:Exit", this);
|
||||||
removeEventListener("MozDOMFullscreen:Exited", this);
|
removeEventListener("MozDOMFullscreen:Exited", this);
|
||||||
|
@ -179,6 +181,16 @@ class GeckoViewContent extends GeckoViewContentModule {
|
||||||
type: "GeckoView:DOMWindowFocus"
|
type: "GeckoView:DOMWindowFocus"
|
||||||
});
|
});
|
||||||
break;
|
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",
|
ChromeUtils.defineModuleGetter(this, "EventDispatcher",
|
||||||
"resource://gre/modules/Messaging.jsm");
|
"resource://gre/modules/Messaging.jsm");
|
||||||
|
ChromeUtils.defineModuleGetter(this, "Services",
|
||||||
|
"resource://gre/modules/Services.jsm");
|
||||||
XPCOMUtils.defineLazyGetter(this, "WindowEventDispatcher",
|
XPCOMUtils.defineLazyGetter(this, "WindowEventDispatcher",
|
||||||
() => EventDispatcher.for(window));
|
() => EventDispatcher.for(window));
|
||||||
|
|
||||||
|
@ -23,8 +25,8 @@ XPCOMUtils.defineLazyGetter(this, "dump", () =>
|
||||||
// and remove by calling
|
// and remove by calling
|
||||||
// remove(<type name>)
|
// remove(<type name>)
|
||||||
var ModuleManager = {
|
var ModuleManager = {
|
||||||
init: function() {
|
init: function(aBrowser) {
|
||||||
this.browser = document.getElementById("content");
|
this.browser = aBrowser;
|
||||||
this.modules = {};
|
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() {
|
function startup() {
|
||||||
ModuleManager.init();
|
const browser = createBrowser();
|
||||||
|
ModuleManager.init(browser);
|
||||||
|
|
||||||
// GeckoViewNavigation needs to go first because nsIDOMBrowserWindow must set up
|
// GeckoViewNavigation needs to go first because nsIDOMBrowserWindow must set up
|
||||||
// before the first remote browser. Bug 1365364.
|
// before the first remote browser. Bug 1365364.
|
||||||
|
@ -69,5 +87,5 @@ function startup() {
|
||||||
|
|
||||||
// Move focus to the content window at the end of startup,
|
// Move focus to the content window at the end of startup,
|
||||||
// so things like text selection can work properly.
|
// 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
|
- 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/. -->
|
- 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"
|
<window id="main-window"
|
||||||
onload="startup();"
|
onload="startup();"
|
||||||
windowtype="navigator:geckoview"
|
windowtype="navigator:geckoview"
|
||||||
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
|
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"/>
|
<script type="application/javascript" src="chrome://geckoview/content/geckoview.js"/>
|
||||||
</window>
|
</window>
|
||||||
|
|
|
@ -17,10 +17,28 @@ GeckoViewStartup.prototype = {
|
||||||
|
|
||||||
QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver]),
|
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 ---------- */
|
/* ---------- nsIObserver ---------- */
|
||||||
observe: function(aSubject, aTopic, aData) {
|
observe: function(aSubject, aTopic, aData) {
|
||||||
switch (aTopic) {
|
switch (aTopic) {
|
||||||
case "app-startup": {
|
case "app-startup": {
|
||||||
|
this.setResourceSubstitutions();
|
||||||
|
|
||||||
// Parent and content process.
|
// Parent and content process.
|
||||||
Services.obs.addObserver(this, "chrome-document-global-created");
|
Services.obs.addObserver(this, "chrome-document-global-created");
|
||||||
Services.obs.addObserver(this, "content-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}"
|
versionName "${mozconfig.substs.MOZ_APP_VERSION}-${mozconfig.substs.MOZ_UPDATE_CHANNEL}"
|
||||||
consumerProguardFiles 'proguard-rules.txt'
|
consumerProguardFiles 'proguard-rules.txt'
|
||||||
|
|
||||||
|
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
|
||||||
|
|
||||||
// TODO: ensure these fields always agree with mobile/android/geckoview/BuildConfig.java.in,
|
// 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.
|
// either by diffing the processed files or by generating the output from a single source.
|
||||||
buildConfigField 'String', "GRE_MILESTONE", "\"${mozconfig.substs.GRE_MILESTONE}\""
|
buildConfigField 'String', "GRE_MILESTONE", "\"${mozconfig.substs.GRE_MILESTONE}\""
|
||||||
|
@ -152,6 +154,11 @@ dependencies {
|
||||||
testImplementation 'junit:junit:4.12'
|
testImplementation 'junit:junit:4.12'
|
||||||
testImplementation 'org.robolectric:robolectric:3.5.1'
|
testImplementation 'org.robolectric:robolectric:3.5.1'
|
||||||
testImplementation 'org.mockito:mockito-core:1.10.19'
|
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"
|
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;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return name();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final NativeQueue sNativeQueue =
|
private static final NativeQueue sNativeQueue =
|
||||||
|
@ -495,13 +500,17 @@ public class GeckoThread extends Thread {
|
||||||
|
|
||||||
@WrapForJNI(calledFrom = "gecko")
|
@WrapForJNI(calledFrom = "gecko")
|
||||||
private static void setState(final State newState) {
|
private static void setState(final State newState) {
|
||||||
sNativeQueue.setState(newState);
|
checkAndSetState(null, newState);
|
||||||
}
|
}
|
||||||
|
|
||||||
@WrapForJNI(calledFrom = "gecko")
|
@WrapForJNI(calledFrom = "gecko")
|
||||||
private static boolean checkAndSetState(final State expectedState,
|
private static boolean checkAndSetState(final State expectedState,
|
||||||
final State newState) {
|
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")
|
@WrapForJNI(stubName = "SpeculativeConnect")
|
||||||
|
|
|
@ -9,6 +9,7 @@ package org.mozilla.geckoview;
|
||||||
import java.net.URLConnection;
|
import java.net.URLConnection;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
import org.mozilla.gecko.annotation.WrapForJNI;
|
import org.mozilla.gecko.annotation.WrapForJNI;
|
||||||
import org.mozilla.gecko.EventDispatcher;
|
import org.mozilla.gecko.EventDispatcher;
|
||||||
|
@ -73,6 +74,9 @@ public class GeckoSession extends LayerSession
|
||||||
|
|
||||||
private final TextInputController mTextInput = new TextInputController(this, mNativeQueue);
|
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 =
|
private final GeckoSessionHandler<ContentListener> mContentHandler =
|
||||||
new GeckoSessionHandler<ContentListener>(
|
new GeckoSessionHandler<ContentListener>(
|
||||||
"GeckoViewContent", this,
|
"GeckoViewContent", this,
|
||||||
|
@ -80,6 +84,7 @@ public class GeckoSession extends LayerSession
|
||||||
"GeckoView:ContextMenu",
|
"GeckoView:ContextMenu",
|
||||||
"GeckoView:DOMTitleChanged",
|
"GeckoView:DOMTitleChanged",
|
||||||
"GeckoView:DOMWindowFocus",
|
"GeckoView:DOMWindowFocus",
|
||||||
|
"GeckoView:DOMWindowClose",
|
||||||
"GeckoView:FullScreenEnter",
|
"GeckoView:FullScreenEnter",
|
||||||
"GeckoView:FullScreenExit"
|
"GeckoView:FullScreenExit"
|
||||||
}
|
}
|
||||||
|
@ -101,6 +106,8 @@ public class GeckoSession extends LayerSession
|
||||||
message.getString("title"));
|
message.getString("title"));
|
||||||
} else if ("GeckoView:DOMWindowFocus".equals(event)) {
|
} else if ("GeckoView:DOMWindowFocus".equals(event)) {
|
||||||
listener.onFocusRequest(GeckoSession.this);
|
listener.onFocusRequest(GeckoSession.this);
|
||||||
|
} else if ("GeckoView:DOMWindowClose".equals(event)) {
|
||||||
|
listener.onCloseRequest(GeckoSession.this);
|
||||||
} else if ("GeckoView:FullScreenEnter".equals(event)) {
|
} else if ("GeckoView:FullScreenEnter".equals(event)) {
|
||||||
listener.onFullScreen(GeckoSession.this, true);
|
listener.onFullScreen(GeckoSession.this, true);
|
||||||
} else if ("GeckoView:FullScreenExit".equals(event)) {
|
} else if ("GeckoView:FullScreenExit".equals(event)) {
|
||||||
|
@ -114,7 +121,8 @@ public class GeckoSession extends LayerSession
|
||||||
"GeckoViewNavigation", this,
|
"GeckoViewNavigation", this,
|
||||||
new String[]{
|
new String[]{
|
||||||
"GeckoView:LocationChange",
|
"GeckoView:LocationChange",
|
||||||
"GeckoView:OnLoadUri"
|
"GeckoView:OnLoadUri",
|
||||||
|
"GeckoView:OnNewSession"
|
||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
@Override
|
@Override
|
||||||
|
@ -137,6 +145,19 @@ public class GeckoSession extends LayerSession
|
||||||
final boolean result =
|
final boolean result =
|
||||||
listener.onLoadUri(GeckoSession.this, uri, where);
|
listener.onLoadUri(GeckoSession.this, uri, where);
|
||||||
callback.sendSuccess(result);
|
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,
|
public static native void open(Window instance, Compositor compositor,
|
||||||
EventDispatcher dispatcher,
|
EventDispatcher dispatcher,
|
||||||
GeckoBundle settings, String chromeUri,
|
GeckoBundle settings, String chromeUri,
|
||||||
int screenId, boolean privateMode);
|
int screenId, boolean privateMode, String id);
|
||||||
|
|
||||||
@Override // JNIObject
|
@Override // JNIObject
|
||||||
protected void disposeNative() {
|
protected void disposeNative() {
|
||||||
|
@ -434,7 +455,7 @@ public class GeckoSession extends LayerSession
|
||||||
private GeckoSessionSettings mSettings;
|
private GeckoSessionSettings mSettings;
|
||||||
|
|
||||||
public GeckoSession() {
|
public GeckoSession() {
|
||||||
this(/* settings */ null);
|
this(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public GeckoSession(final GeckoSessionSettings settings) {
|
public GeckoSession(final GeckoSessionSettings settings) {
|
||||||
|
@ -447,13 +468,15 @@ public class GeckoSession extends LayerSession
|
||||||
mListener.registerListeners();
|
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()) {
|
if (isOpen()) {
|
||||||
throw new IllegalStateException("Session is open");
|
throw new IllegalStateException("Session is open");
|
||||||
}
|
}
|
||||||
|
|
||||||
mWindow = window;
|
mWindow = window;
|
||||||
mSettings = new GeckoSessionSettings(settings, this);
|
mSettings = new GeckoSessionSettings(settings, this);
|
||||||
|
mId = id;
|
||||||
|
|
||||||
if (mWindow != null) {
|
if (mWindow != null) {
|
||||||
if (GeckoThread.isStateAtLeast(GeckoThread.State.PROFILE_READY)) {
|
if (GeckoThread.isStateAtLeast(GeckoThread.State.PROFILE_READY)) {
|
||||||
|
@ -471,7 +494,7 @@ public class GeckoSession extends LayerSession
|
||||||
}
|
}
|
||||||
|
|
||||||
/* package */ void transferFrom(final GeckoSession session) {
|
/* package */ void transferFrom(final GeckoSession session) {
|
||||||
transferFrom(session.mWindow, session.mSettings);
|
transferFrom(session.mWindow, session.mSettings, session.mId);
|
||||||
session.mWindow = null;
|
session.mWindow = null;
|
||||||
session.onWindowChanged();
|
session.onWindowChanged();
|
||||||
}
|
}
|
||||||
|
@ -485,6 +508,7 @@ public class GeckoSession extends LayerSession
|
||||||
public void writeToParcel(Parcel out, int flags) {
|
public void writeToParcel(Parcel out, int flags) {
|
||||||
out.writeStrongInterface(mWindow);
|
out.writeStrongInterface(mWindow);
|
||||||
out.writeParcelable(mSettings, flags);
|
out.writeParcelable(mSettings, flags);
|
||||||
|
out.writeString(mId);
|
||||||
}
|
}
|
||||||
|
|
||||||
// AIDL code may call readFromParcel even though it's not part of Parcelable.
|
// 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 Window window = (ifce instanceof Window) ? (Window) ifce : null;
|
||||||
final GeckoSessionSettings settings =
|
final GeckoSessionSettings settings =
|
||||||
source.readParcelable(getClass().getClassLoader());
|
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>() {
|
public static final Creator<GeckoSession> CREATOR = new Creator<GeckoSession>() {
|
||||||
|
@ -546,6 +571,10 @@ public class GeckoSession extends LayerSession
|
||||||
return mWindow != null;
|
return mWindow != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* package */ boolean isReady() {
|
||||||
|
return mNativeQueue.isReady();
|
||||||
|
}
|
||||||
|
|
||||||
public void openWindow(final Context appContext) {
|
public void openWindow(final Context appContext) {
|
||||||
ThreadUtils.assertOnUiThread();
|
ThreadUtils.assertOnUiThread();
|
||||||
|
|
||||||
|
@ -567,7 +596,7 @@ public class GeckoSession extends LayerSession
|
||||||
|
|
||||||
if (GeckoThread.isStateAtLeast(GeckoThread.State.PROFILE_READY)) {
|
if (GeckoThread.isStateAtLeast(GeckoThread.State.PROFILE_READY)) {
|
||||||
Window.open(mWindow, mCompositor, mEventDispatcher,
|
Window.open(mWindow, mCompositor, mEventDispatcher,
|
||||||
mSettings.asBundle(), chromeUri, screenId, isPrivate);
|
mSettings.asBundle(), chromeUri, screenId, isPrivate, mId);
|
||||||
} else {
|
} else {
|
||||||
GeckoThread.queueNativeCallUntil(
|
GeckoThread.queueNativeCallUntil(
|
||||||
GeckoThread.State.PROFILE_READY,
|
GeckoThread.State.PROFILE_READY,
|
||||||
|
@ -577,7 +606,7 @@ public class GeckoSession extends LayerSession
|
||||||
EventDispatcher.class, mEventDispatcher,
|
EventDispatcher.class, mEventDispatcher,
|
||||||
GeckoBundle.class, mSettings.asBundle(),
|
GeckoBundle.class, mSettings.asBundle(),
|
||||||
String.class, chromeUri,
|
String.class, chromeUri,
|
||||||
screenId, isPrivate);
|
screenId, isPrivate, mId);
|
||||||
}
|
}
|
||||||
|
|
||||||
onWindowChanged();
|
onWindowChanged();
|
||||||
|
@ -762,7 +791,7 @@ public class GeckoSession extends LayerSession
|
||||||
/**
|
/**
|
||||||
* Set the tracking protection callback handler.
|
* Set the tracking protection callback handler.
|
||||||
* This will replace the current handler.
|
* This will replace the current handler.
|
||||||
* @param listener An implementation of TrackingProtectionDelegate.
|
* @param delegate An implementation of TrackingProtectionDelegate.
|
||||||
*/
|
*/
|
||||||
public void setTrackingProtectionDelegate(TrackingProtectionDelegate delegate) {
|
public void setTrackingProtectionDelegate(TrackingProtectionDelegate delegate) {
|
||||||
mTrackingProtectionHandler.setListener(delegate, this);
|
mTrackingProtectionHandler.setListener(delegate, this);
|
||||||
|
@ -1278,6 +1307,12 @@ public class GeckoSession extends LayerSession
|
||||||
*/
|
*/
|
||||||
void onFocusRequest(GeckoSession session);
|
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
|
* 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
|
* 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);
|
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 {
|
public interface NavigationListener {
|
||||||
/**
|
/**
|
||||||
* A view has started loading content from the network.
|
* 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 uri The URI to be loaded.
|
||||||
* @param where The target window.
|
* @param where The target window.
|
||||||
*
|
*
|
||||||
* @return True if the URI loading has been handled, false if Gecko
|
* @return Whether or not the load was handled. Returning false will allow Gecko
|
||||||
* should handle the loading.
|
* to continue the load as normal.
|
||||||
*/
|
*/
|
||||||
boolean onLoadUri(GeckoSession session, String uri, TargetWindow where);
|
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 session The GeckoSession that initiated the callback.
|
||||||
* @param uri The URI of the blocked element.
|
* @param uri The URI of the blocked element.
|
||||||
* @param categories The tracker categories 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.
|
* flags.
|
||||||
*/
|
*/
|
||||||
void onTrackerBlocked(GeckoSession session, String uri, int categories);
|
void onTrackerBlocked(GeckoSession session, String uri, int categories);
|
||||||
|
@ -1886,7 +1943,7 @@ public class GeckoSession extends LayerSession
|
||||||
/**
|
/**
|
||||||
* Enable tracking protection.
|
* Enable tracking protection.
|
||||||
* @param categories The categories of trackers that should be blocked.
|
* @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.
|
* flags.
|
||||||
**/
|
**/
|
||||||
public void enableTrackingProtection(int categories) {
|
public void enableTrackingProtection(int categories) {
|
||||||
|
|
|
@ -89,6 +89,8 @@ import android.util.Log;
|
||||||
|
|
||||||
if (mListener != null) {
|
if (mListener != null) {
|
||||||
handleMessage(mListener, event, message, callback);
|
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.GeckoSession;
|
||||||
import org.mozilla.geckoview.GeckoSessionSettings;
|
import org.mozilla.geckoview.GeckoSessionSettings;
|
||||||
import org.mozilla.geckoview.GeckoSession.PermissionDelegate.MediaSource;
|
import org.mozilla.geckoview.GeckoSession.PermissionDelegate.MediaSource;
|
||||||
|
import org.mozilla.geckoview.GeckoSession.Response;
|
||||||
import org.mozilla.geckoview.GeckoSession.TrackingProtectionDelegate;
|
import org.mozilla.geckoview.GeckoSession.TrackingProtectionDelegate;
|
||||||
import org.mozilla.geckoview.GeckoView;
|
import org.mozilla.geckoview.GeckoView;
|
||||||
|
|
||||||
|
@ -87,8 +88,8 @@ public class GeckoViewActivity extends Activity {
|
||||||
permission.androidPermissionRequestCode = REQUEST_PERMISSIONS;
|
permission.androidPermissionRequestCode = REQUEST_PERMISSIONS;
|
||||||
mGeckoSession.setPermissionDelegate(permission);
|
mGeckoSession.setPermissionDelegate(permission);
|
||||||
|
|
||||||
mGeckoView.getSettings().setBoolean(GeckoSessionSettings.USE_MULTIPROCESS,
|
mGeckoSession.getSettings().setBoolean(GeckoSessionSettings.USE_MULTIPROCESS,
|
||||||
useMultiprocess);
|
useMultiprocess);
|
||||||
|
|
||||||
mGeckoSession.enableTrackingProtection(
|
mGeckoSession.enableTrackingProtection(
|
||||||
TrackingProtectionDelegate.CATEGORY_AD |
|
TrackingProtectionDelegate.CATEGORY_AD |
|
||||||
|
@ -173,11 +174,6 @@ public class GeckoViewActivity extends Activity {
|
||||||
Log.i(LOGTAG, "Content title changed to " + title);
|
Log.i(LOGTAG, "Content title changed to " + title);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onFocusRequest(GeckoSession session) {
|
|
||||||
Log.i(LOGTAG, "Content requesting focus");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onFullScreen(final GeckoSession session, final boolean fullScreen) {
|
public void onFullScreen(final GeckoSession session, final boolean fullScreen) {
|
||||||
getWindow().setFlags(fullScreen ? WindowManager.LayoutParams.FLAG_FULLSCREEN : 0,
|
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
|
@Override
|
||||||
public void onContextMenu(GeckoSession session, int screenX, int screenY,
|
public void onContextMenu(GeckoSession session, int screenX, int screenY,
|
||||||
String uri, String elementSrc) {
|
String uri, String elementSrc) {
|
||||||
|
@ -346,13 +354,13 @@ public class GeckoViewActivity extends Activity {
|
||||||
@Override
|
@Override
|
||||||
public boolean onLoadUri(final GeckoSession session, final String uri,
|
public boolean onLoadUri(final GeckoSession session, final String uri,
|
||||||
final TargetWindow where) {
|
final TargetWindow where) {
|
||||||
Log.d(LOGTAG, "onLoadUri=" + uri +
|
Log.d(LOGTAG, "onLoadUri=" + uri + " where=" + where);
|
||||||
" where=" + where);
|
return false;
|
||||||
if (where != TargetWindow.NEW) {
|
}
|
||||||
return false;
|
|
||||||
}
|
@Override
|
||||||
session.loadUri(uri);
|
public void onNewSession(final GeckoSession session, final String uri, Response<GeckoSession> response) {
|
||||||
return true;
|
response.respond(null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -43,10 +43,11 @@ ext.configureVariantWithGeckoBinaries = { variant ->
|
||||||
def distDir = "${topobjdir}/dist/${omnijar_dir}"
|
def distDir = "${topobjdir}/dist/${omnijar_dir}"
|
||||||
|
|
||||||
def syncOmnijarFromDistDir = task("syncOmnijarFromDistDirFor${variant.name.capitalize()}", type: Sync) {
|
def syncOmnijarFromDistDir = task("syncOmnijarFromDistDirFor${variant.name.capitalize()}", type: Sync) {
|
||||||
doFirst {
|
onlyIf {
|
||||||
if (source.empty) {
|
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")
|
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) {
|
def syncLibsFromDistDir = task("syncLibsFromDistDirFor${variant.name.capitalize()}", type: Sync) {
|
||||||
doFirst {
|
onlyIf {
|
||||||
if (source.empty) {
|
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")
|
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) {
|
def syncAssetsFromDistDir = task("syncAssetsFromDistDirFor${variant.name.capitalize()}", type: Sync) {
|
||||||
doFirst {
|
onlyIf {
|
||||||
if (source.empty) {
|
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")
|
into("${project.buildDir}/moz.build/src/${variant.name}/assets")
|
||||||
|
|