Merge inbound to mozilla-central. a=merge

This commit is contained in:
Ciure Andrei 2018-02-22 23:55:25 +02:00
Родитель 68ba2c72a2 1e98fe5231
Коммит 852a0c8890
133 изменённых файлов: 4698 добавлений и 920 удалений

Просмотреть файл

@ -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();
}); });

Просмотреть файл

@ -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>

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 3.0 KiB

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 4.9 KiB

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 2.0 KiB

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 2.8 KiB

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 4.5 KiB

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 6.9 KiB

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 6.3 KiB

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 10 KiB

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 9.0 KiB

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 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")

Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше