зеркало из https://github.com/mozilla/gecko-dev.git
Merge mozilla-central to autoland r=merge a=merge on a CLOSED TREE
This commit is contained in:
Коммит
b102ef421c
|
@ -68,7 +68,3 @@ groupbox description {
|
|||
menulist label {
|
||||
font-weight: unset;
|
||||
}
|
||||
|
||||
.dialog-button-box {
|
||||
padding: 0;
|
||||
}
|
||||
|
|
|
@ -28,11 +28,6 @@
|
|||
padding: 0;
|
||||
}
|
||||
|
||||
.prefwindow[type="child"] > .prefpane {
|
||||
-moz-box-flex: 1;
|
||||
overflow: -moz-hidden-unscrollable;
|
||||
}
|
||||
|
||||
.prefpane > groupbox + groupbox {
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
|
|
@ -40,7 +40,11 @@ elif CONFIG['OS_TARGET'] in ('FreeBSD', 'OpenBSD', 'NetBSD'):
|
|||
SOURCES += ['/nsprpub/pr/src/md/unix/%s.c' % CONFIG['OS_TARGET'].lower()]
|
||||
elif CONFIG['OS_TARGET'] == 'Darwin':
|
||||
OS_LIBS += ['-framework CoreServices']
|
||||
if CONFIG['HOST_MAJOR_VERSION'] >= '15':
|
||||
if not CONFIG['HOST_MAJOR_VERSION']:
|
||||
DEFINES.update(
|
||||
HAS_CONNECTX=True,
|
||||
)
|
||||
elif CONFIG['HOST_MAJOR_VERSION'] >= '15':
|
||||
DEFINES.update(
|
||||
HAS_CONNECTX=True,
|
||||
)
|
||||
|
|
|
@ -348,9 +348,9 @@ public:
|
|||
virtual bool IsFrozen() const override;
|
||||
void SyncStateFromParentWindow();
|
||||
|
||||
mozilla::Maybe<mozilla::dom::ClientInfo> GetClientInfo() const;
|
||||
mozilla::Maybe<mozilla::dom::ClientInfo> GetClientInfo() const override;
|
||||
mozilla::Maybe<mozilla::dom::ClientState> GetClientState() const;
|
||||
mozilla::Maybe<mozilla::dom::ServiceWorkerDescriptor> GetController() const;
|
||||
mozilla::Maybe<mozilla::dom::ServiceWorkerDescriptor> GetController() const override;
|
||||
|
||||
void NoteCalledRegisterForServiceWorkerScope(const nsACString& aScope);
|
||||
|
||||
|
|
|
@ -9,6 +9,10 @@
|
|||
#include "nsThreadUtils.h"
|
||||
#include "nsHostObjectProtocolHandler.h"
|
||||
|
||||
using mozilla::Maybe;
|
||||
using mozilla::dom::ClientInfo;
|
||||
using mozilla::dom::ServiceWorkerDescriptor;
|
||||
|
||||
nsIGlobalObject::~nsIGlobalObject()
|
||||
{
|
||||
UnlinkHostObjectURIs();
|
||||
|
@ -112,3 +116,19 @@ nsIGlobalObject::TraverseHostObjectURIs(nsCycleCollectionTraversalCallback &aCb)
|
|||
nsHostObjectProtocolHandler::Traverse(mHostObjectURIs[index], aCb);
|
||||
}
|
||||
}
|
||||
|
||||
Maybe<ClientInfo>
|
||||
nsIGlobalObject::GetClientInfo() const
|
||||
{
|
||||
// By default globals do not expose themselves as a client. Only real
|
||||
// window and worker globals are currently considered clients.
|
||||
return Maybe<ClientInfo>();
|
||||
}
|
||||
|
||||
Maybe<ServiceWorkerDescriptor>
|
||||
nsIGlobalObject::GetController() const
|
||||
{
|
||||
// By default globals do not have a service worker controller. Only real
|
||||
// window and worker globals can currently be controlled as a client.
|
||||
return Maybe<ServiceWorkerDescriptor>();
|
||||
}
|
||||
|
|
|
@ -7,7 +7,10 @@
|
|||
#ifndef nsIGlobalObject_h__
|
||||
#define nsIGlobalObject_h__
|
||||
|
||||
#include "mozilla/Maybe.h"
|
||||
#include "mozilla/dom/ClientInfo.h"
|
||||
#include "mozilla/dom/DispatcherTrait.h"
|
||||
#include "mozilla/dom/ServiceWorkerDescriptor.h"
|
||||
#include "nsISupports.h"
|
||||
#include "nsStringFwd.h"
|
||||
#include "nsTArray.h"
|
||||
|
@ -76,6 +79,12 @@ public:
|
|||
|
||||
virtual bool IsInSyncOperation() { return false; }
|
||||
|
||||
virtual mozilla::Maybe<mozilla::dom::ClientInfo>
|
||||
GetClientInfo() const;
|
||||
|
||||
virtual mozilla::Maybe<mozilla::dom::ServiceWorkerDescriptor>
|
||||
GetController() const;
|
||||
|
||||
protected:
|
||||
virtual ~nsIGlobalObject();
|
||||
|
||||
|
|
|
@ -1087,25 +1087,37 @@ FetchDriver::OnDataAvailable(nsIRequest* aRequest,
|
|||
}
|
||||
}
|
||||
|
||||
uint32_t aRead;
|
||||
// Needs to be initialized to 0 because in some cases nsStringInputStream may
|
||||
// not write to aRead.
|
||||
uint32_t aRead = 0;
|
||||
MOZ_ASSERT(mResponse);
|
||||
MOZ_ASSERT(mPipeOutputStream);
|
||||
|
||||
// From "Main Fetch" step 19: SRI-part2.
|
||||
// Note: Avoid checking the hidden opaque body.
|
||||
nsresult rv;
|
||||
if (mResponse->Type() != ResponseType::Opaque &&
|
||||
ShouldCheckSRI(mRequest, mResponse)) {
|
||||
MOZ_ASSERT(mSRIDataVerifier);
|
||||
|
||||
SRIVerifierAndOutputHolder holder(mSRIDataVerifier, mPipeOutputStream);
|
||||
nsresult rv = aInputStream->ReadSegments(CopySegmentToStreamAndSRI,
|
||||
&holder, aCount, &aRead);
|
||||
return rv;
|
||||
rv = aInputStream->ReadSegments(CopySegmentToStreamAndSRI,
|
||||
&holder, aCount, &aRead);
|
||||
} else {
|
||||
rv = aInputStream->ReadSegments(NS_CopySegmentToStream,
|
||||
mPipeOutputStream,
|
||||
aCount, &aRead);
|
||||
}
|
||||
|
||||
nsresult rv = aInputStream->ReadSegments(NS_CopySegmentToStream,
|
||||
mPipeOutputStream,
|
||||
aCount, &aRead);
|
||||
// If no data was read, it's possible the output stream is closed but the
|
||||
// ReadSegments call followed its contract of returning NS_OK despite write
|
||||
// errors. Unfortunately, nsIOutputStream has an ill-conceived contract when
|
||||
// taken together with ReadSegments' contract, because the pipe will just
|
||||
// NS_OK if we try and invoke its Write* functions ourselves with a 0 count.
|
||||
// So we must just assume the pipe is broken.
|
||||
if (aRead == 0 && aCount != 0) {
|
||||
return NS_BASE_STREAM_CLOSED;
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
|
|
|
@ -34,7 +34,10 @@ public:
|
|||
{
|
||||
if (!mWasNotified) {
|
||||
mWasNotified = true;
|
||||
mReader->CloseAndRelease(NS_ERROR_DOM_INVALID_STATE_ERR);
|
||||
// The WorkerPrivate does have a context available, and we could pass it
|
||||
// here to trigger cancellation of the reader, but the author of this
|
||||
// comment chickened out.
|
||||
mReader->CloseAndRelease(nullptr, NS_ERROR_DOM_INVALID_STATE_ERR);
|
||||
}
|
||||
|
||||
return true;
|
||||
|
@ -124,11 +127,17 @@ FetchStreamReader::FetchStreamReader(nsIGlobalObject* aGlobal)
|
|||
|
||||
FetchStreamReader::~FetchStreamReader()
|
||||
{
|
||||
CloseAndRelease(NS_BASE_STREAM_CLOSED);
|
||||
CloseAndRelease(nullptr, NS_BASE_STREAM_CLOSED);
|
||||
}
|
||||
|
||||
// If a context is provided, an attempt will be made to cancel the reader. The
|
||||
// only situation where we don't expect to have a context is when closure is
|
||||
// being triggered from the destructor or the WorkerHolder is notifying. If
|
||||
// we're at the destructor, it's far too late to cancel anything. And if the
|
||||
// WorkerHolder is being notified, the global is going away, so there's also
|
||||
// no need to do further JS work.
|
||||
void
|
||||
FetchStreamReader::CloseAndRelease(nsresult aStatus)
|
||||
FetchStreamReader::CloseAndRelease(JSContext* aCx, nsresult aStatus)
|
||||
{
|
||||
NS_ASSERT_OWNINGTHREAD(FetchStreamReader);
|
||||
|
||||
|
@ -139,6 +148,22 @@ FetchStreamReader::CloseAndRelease(nsresult aStatus)
|
|||
|
||||
RefPtr<FetchStreamReader> kungFuDeathGrip = this;
|
||||
|
||||
if (aCx) {
|
||||
MOZ_ASSERT(mReader);
|
||||
|
||||
RefPtr<DOMException> error = DOMException::Create(aStatus);
|
||||
|
||||
JS::Rooted<JS::Value> errorValue(aCx);
|
||||
if (ToJSValue(aCx, error, &errorValue)) {
|
||||
JS::Rooted<JSObject*> reader(aCx, mReader);
|
||||
// It's currently safe to cancel an already closed reader because, per the
|
||||
// comments in ReadableStream::cancel() conveying the spec, step 2 of
|
||||
// 3.4.3 that specified ReadableStreamCancel is: If stream.[[state]] is
|
||||
// "closed", return a new promise resolved with undefined.
|
||||
JS::ReadableStreamReaderCancel(aCx, reader, errorValue);
|
||||
}
|
||||
}
|
||||
|
||||
mStreamClosed = true;
|
||||
|
||||
mGlobal = nullptr;
|
||||
|
@ -166,7 +191,7 @@ FetchStreamReader::StartConsuming(JSContext* aCx,
|
|||
JS::ReadableStreamReaderMode::Default));
|
||||
if (!reader) {
|
||||
aRv.StealExceptionFromJSContext(aCx);
|
||||
CloseAndRelease(NS_ERROR_DOM_INVALID_STATE_ERR);
|
||||
CloseAndRelease(aCx, NS_ERROR_DOM_INVALID_STATE_ERR);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -206,14 +231,14 @@ FetchStreamReader::OnOutputStreamReady(nsIAsyncOutputStream* aStream)
|
|||
reader));
|
||||
if (NS_WARN_IF(!promise)) {
|
||||
// Let's close the stream.
|
||||
CloseAndRelease(NS_ERROR_DOM_INVALID_STATE_ERR);
|
||||
CloseAndRelease(aes.cx(), NS_ERROR_DOM_INVALID_STATE_ERR);
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
RefPtr<Promise> domPromise = Promise::CreateFromExisting(mGlobal, promise);
|
||||
if (NS_WARN_IF(!domPromise)) {
|
||||
// Let's close the stream.
|
||||
CloseAndRelease(NS_ERROR_DOM_INVALID_STATE_ERR);
|
||||
CloseAndRelease(aes.cx(), NS_ERROR_DOM_INVALID_STATE_ERR);
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
|
@ -240,13 +265,13 @@ FetchStreamReader::ResolvedCallback(JSContext* aCx,
|
|||
FetchReadableStreamReadDataDone valueDone;
|
||||
if (!valueDone.Init(aCx, aValue)) {
|
||||
JS_ClearPendingException(aCx);
|
||||
CloseAndRelease(NS_ERROR_DOM_INVALID_STATE_ERR);
|
||||
CloseAndRelease(aCx, NS_ERROR_DOM_INVALID_STATE_ERR);
|
||||
return;
|
||||
}
|
||||
|
||||
if (valueDone.mDone) {
|
||||
// Stream is completed.
|
||||
CloseAndRelease(NS_BASE_STREAM_CLOSED);
|
||||
CloseAndRelease(aCx, NS_BASE_STREAM_CLOSED);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -254,7 +279,7 @@ FetchStreamReader::ResolvedCallback(JSContext* aCx,
|
|||
new FetchReadableStreamReadDataArray);
|
||||
if (!value->Init(aCx, aValue) || !value->mValue.WasPassed()) {
|
||||
JS_ClearPendingException(aCx);
|
||||
CloseAndRelease(NS_ERROR_DOM_INVALID_STATE_ERR);
|
||||
CloseAndRelease(aCx, NS_ERROR_DOM_INVALID_STATE_ERR);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -274,7 +299,12 @@ FetchStreamReader::ResolvedCallback(JSContext* aCx,
|
|||
mBufferOffset = 0;
|
||||
mBufferRemaining = len;
|
||||
|
||||
WriteBuffer();
|
||||
nsresult rv = WriteBuffer();
|
||||
if (NS_FAILED(rv)) {
|
||||
// DOMException only understands errors from domerr.msg, so we normalize to
|
||||
// identifying an abort if the write fails.
|
||||
CloseAndRelease(aCx, NS_ERROR_DOM_ABORT_ERR);
|
||||
}
|
||||
}
|
||||
|
||||
nsresult
|
||||
|
@ -296,7 +326,6 @@ FetchStreamReader::WriteBuffer()
|
|||
}
|
||||
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
CloseAndRelease(rv);
|
||||
return rv;
|
||||
}
|
||||
|
||||
|
@ -312,7 +341,6 @@ FetchStreamReader::WriteBuffer()
|
|||
|
||||
nsresult rv = mPipeOut->AsyncWait(this, 0, 0, mOwningEventTarget);
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
CloseAndRelease(rv);
|
||||
return rv;
|
||||
}
|
||||
|
||||
|
@ -324,7 +352,7 @@ FetchStreamReader::RejectedCallback(JSContext* aCx,
|
|||
JS::Handle<JS::Value> aValue)
|
||||
{
|
||||
ReportErrorToConsole(aCx, aValue);
|
||||
CloseAndRelease(NS_ERROR_FAILURE);
|
||||
CloseAndRelease(aCx, NS_ERROR_FAILURE);
|
||||
}
|
||||
|
||||
void
|
||||
|
|
|
@ -40,8 +40,12 @@ public:
|
|||
void
|
||||
RejectedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) override;
|
||||
|
||||
// Idempotently close the output stream and null out all state. If aCx is
|
||||
// provided, the reader will also be canceled. aStatus must be a DOM error
|
||||
// as understood by DOMException because it will be provided as the
|
||||
// cancellation reason.
|
||||
void
|
||||
CloseAndRelease(nsresult aStatus);
|
||||
CloseAndRelease(JSContext* aCx, nsresult aStatus);
|
||||
|
||||
void
|
||||
StartConsuming(JSContext* aCx,
|
||||
|
|
|
@ -1380,7 +1380,7 @@ DeleteFilesRunnable::Open()
|
|||
quotaManager->OpenDirectory(mFileManager->Type(),
|
||||
mFileManager->Group(),
|
||||
mFileManager->Origin(),
|
||||
Client::IDB,
|
||||
quota::Client::IDB,
|
||||
/* aExclusive */ false,
|
||||
this);
|
||||
|
||||
|
|
|
@ -1286,199 +1286,179 @@ ScriptLoader::ProcessScriptElement(nsIScriptElement* aElement)
|
|||
}
|
||||
|
||||
// Step 15. and later in the HTML5 spec
|
||||
nsresult rv = NS_OK;
|
||||
RefPtr<ScriptLoadRequest> request;
|
||||
mozilla::net::ReferrerPolicy ourRefPolicy = mDocument->GetReferrerPolicy();
|
||||
if (aElement->GetScriptExternal()) {
|
||||
// external script
|
||||
nsCOMPtr<nsIURI> scriptURI = aElement->GetScriptURI();
|
||||
if (!scriptURI) {
|
||||
// Asynchronously report the failure to create a URI object
|
||||
return ProcessExternalScript(aElement, scriptKind, type, scriptContent);
|
||||
}
|
||||
|
||||
return ProcessInlineScript(aElement, scriptKind);
|
||||
}
|
||||
|
||||
bool
|
||||
ScriptLoader::ProcessExternalScript(nsIScriptElement* aElement,
|
||||
ScriptKind aScriptKind,
|
||||
nsAutoString aTypeAttr,
|
||||
nsIContent* aScriptContent)
|
||||
{
|
||||
nsCOMPtr<nsIURI> scriptURI = aElement->GetScriptURI();
|
||||
if (!scriptURI) {
|
||||
// Asynchronously report the failure to create a URI object
|
||||
NS_DispatchToCurrentThread(
|
||||
NewRunnableMethod("nsIScriptElement::FireErrorEvent",
|
||||
aElement,
|
||||
&nsIScriptElement::FireErrorEvent));
|
||||
return false;
|
||||
}
|
||||
|
||||
RefPtr<ScriptLoadRequest> request = LookupPreloadRequest(aElement, aScriptKind);
|
||||
|
||||
if (request && NS_FAILED(CheckContentPolicy(mDocument, aElement, request->mURI,
|
||||
aTypeAttr, false))) {
|
||||
// Probably plans have changed; even though the preload was allowed seems
|
||||
// like the actual load is not; let's cancel the preload request.
|
||||
request->Cancel();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (request) {
|
||||
// Use the preload request.
|
||||
|
||||
// It's possible these attributes changed since we started the preload so
|
||||
// update them here.
|
||||
request->SetScriptMode(aElement->GetScriptDeferred(),
|
||||
aElement->GetScriptAsync());
|
||||
} else {
|
||||
// No usable preload found.
|
||||
|
||||
SRIMetadata sriMetadata;
|
||||
{
|
||||
nsAutoString integrity;
|
||||
aScriptContent->AsElement()->GetAttr(kNameSpaceID_None,
|
||||
nsGkAtoms::integrity,
|
||||
integrity);
|
||||
GetSRIMetadata(integrity, &sriMetadata);
|
||||
}
|
||||
|
||||
nsCOMPtr<nsIPrincipal> principal = aElement->GetScriptURITriggeringPrincipal();
|
||||
if (!principal) {
|
||||
principal = aScriptContent->NodePrincipal();
|
||||
}
|
||||
|
||||
CORSMode ourCORSMode = aElement->GetCORSMode();
|
||||
mozilla::net::ReferrerPolicy ourRefPolicy = mDocument->GetReferrerPolicy();
|
||||
request = CreateLoadRequest(aScriptKind, scriptURI, aElement,
|
||||
ourCORSMode, sriMetadata, ourRefPolicy);
|
||||
request->mTriggeringPrincipal = Move(principal);
|
||||
request->mIsInline = false;
|
||||
request->SetScriptMode(aElement->GetScriptDeferred(),
|
||||
aElement->GetScriptAsync());
|
||||
// keep request->mScriptFromHead to false so we don't treat non preloaded
|
||||
// scripts as blockers for full page load. See bug 792438.
|
||||
|
||||
nsresult rv = StartLoad(request);
|
||||
if (NS_FAILED(rv)) {
|
||||
ReportErrorToConsole(request, rv);
|
||||
|
||||
// Asynchronously report the load failure
|
||||
NS_DispatchToCurrentThread(
|
||||
NewRunnableMethod("nsIScriptElement::FireErrorEvent",
|
||||
aElement,
|
||||
&nsIScriptElement::FireErrorEvent));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Double-check that the preload matches what we're asked to load now.
|
||||
CORSMode ourCORSMode = aElement->GetCORSMode();
|
||||
nsTArray<PreloadInfo>::index_type i =
|
||||
mPreloads.IndexOf(scriptURI.get(), 0, PreloadURIComparator());
|
||||
if (i != nsTArray<PreloadInfo>::NoIndex) {
|
||||
// preloaded
|
||||
// note that a script-inserted script can steal a preload!
|
||||
request = mPreloads[i].mRequest;
|
||||
request->mElement = aElement;
|
||||
nsString preloadCharset(mPreloads[i].mCharset);
|
||||
mPreloads.RemoveElementAt(i);
|
||||
// Should still be in loading stage of script.
|
||||
NS_ASSERTION(!request->InCompilingStage(),
|
||||
"Request should not yet be in compiling stage.");
|
||||
|
||||
// Double-check that the charset the preload used is the same as
|
||||
// the charset we have now.
|
||||
nsAutoString elementCharset;
|
||||
aElement->GetScriptCharset(elementCharset);
|
||||
if (elementCharset.Equals(preloadCharset) &&
|
||||
ourCORSMode == request->mCORSMode &&
|
||||
ourRefPolicy == request->mReferrerPolicy &&
|
||||
scriptKind == request->mKind) {
|
||||
rv = CheckContentPolicy(mDocument, aElement, request->mURI, type, false);
|
||||
if (NS_FAILED(rv)) {
|
||||
// probably plans have changed; even though the preload was allowed seems
|
||||
// like the actual load is not; let's cancel the preload request.
|
||||
request->Cancel();
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
// Drop the preload
|
||||
request = nullptr;
|
||||
}
|
||||
}
|
||||
if (request->IsAsyncScript()) {
|
||||
AddAsyncRequest(request);
|
||||
if (request->IsReadyToRun()) {
|
||||
// The script is available already. Run it ASAP when the event
|
||||
// loop gets a chance to spin.
|
||||
|
||||
if (request) {
|
||||
// Use a preload request.
|
||||
|
||||
// It's possible these attributes changed since we started the preload so
|
||||
// update them here.
|
||||
request->SetScriptMode(aElement->GetScriptDeferred(),
|
||||
aElement->GetScriptAsync());
|
||||
} else {
|
||||
// No usable preload found.
|
||||
|
||||
SRIMetadata sriMetadata;
|
||||
{
|
||||
nsAutoString integrity;
|
||||
scriptContent->AsElement()->GetAttr(kNameSpaceID_None,
|
||||
nsGkAtoms::integrity,
|
||||
integrity);
|
||||
if (!integrity.IsEmpty()) {
|
||||
MOZ_LOG(SRILogHelper::GetSriLog(), mozilla::LogLevel::Debug,
|
||||
("ScriptLoader::ProcessScriptElement, integrity=%s",
|
||||
NS_ConvertUTF16toUTF8(integrity).get()));
|
||||
nsAutoCString sourceUri;
|
||||
if (mDocument->GetDocumentURI()) {
|
||||
mDocument->GetDocumentURI()->GetAsciiSpec(sourceUri);
|
||||
}
|
||||
SRICheck::IntegrityMetadata(integrity, sourceUri, mReporter,
|
||||
&sriMetadata);
|
||||
}
|
||||
}
|
||||
|
||||
nsCOMPtr<nsIPrincipal> principal = aElement->GetScriptURITriggeringPrincipal();
|
||||
if (!principal) {
|
||||
principal = scriptContent->NodePrincipal();
|
||||
}
|
||||
|
||||
request = CreateLoadRequest(scriptKind, scriptURI, aElement, ourCORSMode,
|
||||
sriMetadata, ourRefPolicy);
|
||||
request->mTriggeringPrincipal = Move(principal);
|
||||
request->mIsInline = false;
|
||||
request->SetScriptMode(aElement->GetScriptDeferred(),
|
||||
aElement->GetScriptAsync());
|
||||
// keep request->mScriptFromHead to false so we don't treat non preloaded
|
||||
// scripts as blockers for full page load. See bug 792438.
|
||||
|
||||
rv = StartLoad(request);
|
||||
if (NS_FAILED(rv)) {
|
||||
ReportErrorToConsole(request, rv);
|
||||
|
||||
// Asynchronously report the load failure
|
||||
NS_DispatchToCurrentThread(
|
||||
NewRunnableMethod("nsIScriptElement::FireErrorEvent",
|
||||
aElement,
|
||||
&nsIScriptElement::FireErrorEvent));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Should still be in loading stage of script.
|
||||
NS_ASSERTION(!request->InCompilingStage(),
|
||||
"Request should not yet be in compiling stage.");
|
||||
|
||||
if (request->IsAsyncScript()) {
|
||||
AddAsyncRequest(request);
|
||||
if (request->IsReadyToRun()) {
|
||||
// The script is available already. Run it ASAP when the event
|
||||
// loop gets a chance to spin.
|
||||
|
||||
// KVKV TODO: Instead of processing immediately, try off-thread-parsing
|
||||
// it and only schedule a pending ProcessRequest if that fails.
|
||||
ProcessPendingRequestsAsync();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
if (!aElement->GetParserCreated()) {
|
||||
// Violate the HTML5 spec in order to make LABjs and the "order" plug-in
|
||||
// for RequireJS work with their Gecko-sniffed code path. See
|
||||
// http://lists.w3.org/Archives/Public/public-html/2010Oct/0088.html
|
||||
request->mIsNonAsyncScriptInserted = true;
|
||||
mNonAsyncExternalScriptInsertedRequests.AppendElement(request);
|
||||
if (request->IsReadyToRun()) {
|
||||
// The script is available already. Run it ASAP when the event
|
||||
// loop gets a chance to spin.
|
||||
ProcessPendingRequestsAsync();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
// we now have a parser-inserted request that may or may not be still
|
||||
// loading
|
||||
if (request->IsDeferredScript()) {
|
||||
// We don't want to run this yet.
|
||||
// If we come here, the script is a parser-created script and it has
|
||||
// the defer attribute but not the async attribute. Since a
|
||||
// a parser-inserted script is being run, we came here by the parser
|
||||
// running the script, which means the parser is still alive and the
|
||||
// parse is ongoing.
|
||||
NS_ASSERTION(mDocument->GetCurrentContentSink() ||
|
||||
aElement->GetParserCreated() == FROM_PARSER_XSLT,
|
||||
"Non-XSLT Defer script on a document without an active parser; bug 592366.");
|
||||
AddDeferRequest(request);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (aElement->GetParserCreated() == FROM_PARSER_XSLT) {
|
||||
// Need to maintain order for XSLT-inserted scripts
|
||||
NS_ASSERTION(!mParserBlockingRequest,
|
||||
"Parser-blocking scripts and XSLT scripts in the same doc!");
|
||||
request->mIsXSLT = true;
|
||||
mXSLTRequests.AppendElement(request);
|
||||
if (request->IsReadyToRun()) {
|
||||
// The script is available already. Run it ASAP when the event
|
||||
// loop gets a chance to spin.
|
||||
ProcessPendingRequestsAsync();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
if (request->IsReadyToRun() && ReadyToExecuteParserBlockingScripts()) {
|
||||
// The request has already been loaded and there are no pending style
|
||||
// sheets. If the script comes from the network stream, cheat for
|
||||
// performance reasons and avoid a trip through the event loop.
|
||||
if (aElement->GetParserCreated() == FROM_PARSER_NETWORK) {
|
||||
return ProcessRequest(request) == NS_ERROR_HTMLPARSER_BLOCK;
|
||||
}
|
||||
// Otherwise, we've got a document.written script, make a trip through
|
||||
// the event loop to hide the preload effects from the scripts on the
|
||||
// Web page.
|
||||
NS_ASSERTION(!mParserBlockingRequest,
|
||||
"There can be only one parser-blocking script at a time");
|
||||
NS_ASSERTION(mXSLTRequests.isEmpty(),
|
||||
"Parser-blocking scripts and XSLT scripts in the same doc!");
|
||||
mParserBlockingRequest = request;
|
||||
// KVKV TODO: Instead of processing immediately, try off-thread-parsing
|
||||
// it and only schedule a pending ProcessRequest if that fails.
|
||||
ProcessPendingRequestsAsync();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
if (!aElement->GetParserCreated()) {
|
||||
// Violate the HTML5 spec in order to make LABjs and the "order" plug-in
|
||||
// for RequireJS work with their Gecko-sniffed code path. See
|
||||
// http://lists.w3.org/Archives/Public/public-html/2010Oct/0088.html
|
||||
request->mIsNonAsyncScriptInserted = true;
|
||||
mNonAsyncExternalScriptInsertedRequests.AppendElement(request);
|
||||
if (request->IsReadyToRun()) {
|
||||
// The script is available already. Run it ASAP when the event
|
||||
// loop gets a chance to spin.
|
||||
ProcessPendingRequestsAsync();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
// we now have a parser-inserted request that may or may not be still
|
||||
// loading
|
||||
if (request->IsDeferredScript()) {
|
||||
// We don't want to run this yet.
|
||||
// If we come here, the script is a parser-created script and it has
|
||||
// the defer attribute but not the async attribute. Since a
|
||||
// a parser-inserted script is being run, we came here by the parser
|
||||
// running the script, which means the parser is still alive and the
|
||||
// parse is ongoing.
|
||||
NS_ASSERTION(mDocument->GetCurrentContentSink() ||
|
||||
aElement->GetParserCreated() == FROM_PARSER_XSLT,
|
||||
"Non-XSLT Defer script on a document without an active parser; bug 592366.");
|
||||
AddDeferRequest(request);
|
||||
return false;
|
||||
}
|
||||
|
||||
// The script hasn't loaded yet or there's a style sheet blocking it.
|
||||
// The script will be run when it loads or the style sheet loads.
|
||||
if (aElement->GetParserCreated() == FROM_PARSER_XSLT) {
|
||||
// Need to maintain order for XSLT-inserted scripts
|
||||
NS_ASSERTION(!mParserBlockingRequest,
|
||||
"Parser-blocking scripts and XSLT scripts in the same doc!");
|
||||
request->mIsXSLT = true;
|
||||
mXSLTRequests.AppendElement(request);
|
||||
if (request->IsReadyToRun()) {
|
||||
// The script is available already. Run it ASAP when the event
|
||||
// loop gets a chance to spin.
|
||||
ProcessPendingRequestsAsync();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
if (request->IsReadyToRun() && ReadyToExecuteParserBlockingScripts()) {
|
||||
// The request has already been loaded and there are no pending style
|
||||
// sheets. If the script comes from the network stream, cheat for
|
||||
// performance reasons and avoid a trip through the event loop.
|
||||
if (aElement->GetParserCreated() == FROM_PARSER_NETWORK) {
|
||||
return ProcessRequest(request) == NS_ERROR_HTMLPARSER_BLOCK;
|
||||
}
|
||||
// Otherwise, we've got a document.written script, make a trip through
|
||||
// the event loop to hide the preload effects from the scripts on the
|
||||
// Web page.
|
||||
NS_ASSERTION(!mParserBlockingRequest,
|
||||
"There can be only one parser-blocking script at a time");
|
||||
NS_ASSERTION(mXSLTRequests.isEmpty(),
|
||||
"Parser-blocking scripts and XSLT scripts in the same doc!");
|
||||
mParserBlockingRequest = request;
|
||||
ProcessPendingRequestsAsync();
|
||||
return true;
|
||||
}
|
||||
|
||||
// inline script
|
||||
// The script hasn't loaded yet or there's a style sheet blocking it.
|
||||
// The script will be run when it loads or the style sheet loads.
|
||||
NS_ASSERTION(!mParserBlockingRequest,
|
||||
"There can be only one parser-blocking script at a time");
|
||||
NS_ASSERTION(mXSLTRequests.isEmpty(),
|
||||
"Parser-blocking scripts and XSLT scripts in the same doc!");
|
||||
mParserBlockingRequest = request;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
ScriptLoader::ProcessInlineScript(nsIScriptElement* aElement,
|
||||
ScriptKind aScriptKind)
|
||||
{
|
||||
// Is this document sandboxed without 'allow-scripts'?
|
||||
if (mDocument->HasScriptsBlockedBySandbox()) {
|
||||
return false;
|
||||
|
@ -1491,14 +1471,15 @@ ScriptLoader::ProcessScriptElement(nsIScriptElement* aElement)
|
|||
|
||||
// Inline classic scripts ignore their CORS mode and are always CORS_NONE.
|
||||
CORSMode corsMode = CORS_NONE;
|
||||
if (scriptKind == ScriptKind::eModule) {
|
||||
if (aScriptKind == ScriptKind::eModule) {
|
||||
corsMode = aElement->GetCORSMode();
|
||||
}
|
||||
|
||||
request = CreateLoadRequest(scriptKind, mDocument->GetDocumentURI(), aElement,
|
||||
corsMode,
|
||||
SRIMetadata(), // SRI doesn't apply
|
||||
ourRefPolicy);
|
||||
RefPtr<ScriptLoadRequest> request =
|
||||
CreateLoadRequest(aScriptKind, mDocument->GetDocumentURI(), aElement,
|
||||
corsMode,
|
||||
SRIMetadata(), // SRI doesn't apply
|
||||
mDocument->GetReferrerPolicy());
|
||||
request->mIsInline = true;
|
||||
request->mTriggeringPrincipal = mDocument->NodePrincipal();
|
||||
request->mLineNo = aElement->GetScriptLineNumber();
|
||||
|
@ -1574,6 +1555,60 @@ ScriptLoader::ProcessScriptElement(nsIScriptElement* aElement)
|
|||
return ProcessRequest(request) == NS_ERROR_HTMLPARSER_BLOCK;
|
||||
}
|
||||
|
||||
ScriptLoadRequest*
|
||||
ScriptLoader::LookupPreloadRequest(nsIScriptElement* aElement,
|
||||
ScriptKind aScriptKind)
|
||||
{
|
||||
nsTArray<PreloadInfo>::index_type i =
|
||||
mPreloads.IndexOf(aElement->GetScriptURI(), 0, PreloadURIComparator());
|
||||
if (i == nsTArray<PreloadInfo>::NoIndex) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Found preloaded request. Note that a script-inserted script can steal a
|
||||
// preload!
|
||||
RefPtr<ScriptLoadRequest> request = mPreloads[i].mRequest;
|
||||
request->mElement = aElement;
|
||||
nsString preloadCharset(mPreloads[i].mCharset);
|
||||
mPreloads.RemoveElementAt(i);
|
||||
|
||||
// Double-check that the charset the preload used is the same as the charset
|
||||
// we have now.
|
||||
nsAutoString elementCharset;
|
||||
aElement->GetScriptCharset(elementCharset);
|
||||
if (!elementCharset.Equals(preloadCharset) ||
|
||||
aElement->GetCORSMode() != request->mCORSMode ||
|
||||
mDocument->GetReferrerPolicy() != request->mReferrerPolicy ||
|
||||
aScriptKind != request->mKind) {
|
||||
// Drop the preload.
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return request;
|
||||
}
|
||||
|
||||
void
|
||||
ScriptLoader::GetSRIMetadata(const nsAString& aIntegrityAttr,
|
||||
SRIMetadata *aMetadataOut)
|
||||
{
|
||||
MOZ_ASSERT(aMetadataOut->IsEmpty());
|
||||
|
||||
if (aIntegrityAttr.IsEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
MOZ_LOG(SRILogHelper::GetSriLog(), mozilla::LogLevel::Debug,
|
||||
("ScriptLoader::GetSRIMetadata, integrity=%s",
|
||||
NS_ConvertUTF16toUTF8(aIntegrityAttr).get()));
|
||||
|
||||
nsAutoCString sourceUri;
|
||||
if (mDocument->GetDocumentURI()) {
|
||||
mDocument->GetDocumentURI()->GetAsciiSpec(sourceUri);
|
||||
}
|
||||
SRICheck::IntegrityMetadata(aIntegrityAttr, sourceUri, mReporter,
|
||||
aMetadataOut);
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
class NotifyOffThreadScriptLoadCompletedRunnable : public Runnable
|
||||
|
@ -3129,16 +3164,7 @@ ScriptLoader::PreloadURI(nsIURI* aURI, const nsAString& aCharset,
|
|||
}
|
||||
|
||||
SRIMetadata sriMetadata;
|
||||
if (!aIntegrity.IsEmpty()) {
|
||||
MOZ_LOG(SRILogHelper::GetSriLog(), mozilla::LogLevel::Debug,
|
||||
("ScriptLoader::PreloadURI, integrity=%s",
|
||||
NS_ConvertUTF16toUTF8(aIntegrity).get()));
|
||||
nsAutoCString sourceUri;
|
||||
if (mDocument->GetDocumentURI()) {
|
||||
mDocument->GetDocumentURI()->GetAsciiSpec(sourceUri);
|
||||
}
|
||||
SRICheck::IntegrityMetadata(aIntegrity, sourceUri, mReporter, &sriMetadata);
|
||||
}
|
||||
GetSRIMetadata(aIntegrity, &sriMetadata);
|
||||
|
||||
RefPtr<ScriptLoadRequest> request =
|
||||
CreateLoadRequest(ScriptKind::eClassic, aURI, nullptr,
|
||||
|
|
|
@ -357,6 +357,20 @@ private:
|
|||
void ContinueParserAsync(ScriptLoadRequest* aParserBlockingRequest);
|
||||
|
||||
|
||||
bool ProcessExternalScript(nsIScriptElement* aElement,
|
||||
ScriptKind aScriptKind,
|
||||
nsAutoString aTypeAttr,
|
||||
nsIContent* aScriptContent);
|
||||
|
||||
bool ProcessInlineScript(nsIScriptElement* aElement,
|
||||
ScriptKind aScriptKind);
|
||||
|
||||
ScriptLoadRequest* LookupPreloadRequest(nsIScriptElement* aElement,
|
||||
ScriptKind aScriptKind);
|
||||
|
||||
void GetSRIMetadata(const nsAString& aIntegrityAttr,
|
||||
SRIMetadata *aMetadataOut);
|
||||
|
||||
/**
|
||||
* Helper function to check the content policy for a given request.
|
||||
*/
|
||||
|
|
|
@ -35,15 +35,20 @@ public:
|
|||
// WebIDL
|
||||
already_AddRefed<SVGAnimatedLength> TextLength();
|
||||
already_AddRefed<SVGAnimatedEnumeration> LengthAdjust();
|
||||
int32_t GetNumberOfChars();
|
||||
float GetComputedTextLength();
|
||||
MOZ_CAN_RUN_SCRIPT int32_t GetNumberOfChars();
|
||||
MOZ_CAN_RUN_SCRIPT float GetComputedTextLength();
|
||||
MOZ_CAN_RUN_SCRIPT
|
||||
void SelectSubString(uint32_t charnum, uint32_t nchars, ErrorResult& rv);
|
||||
MOZ_CAN_RUN_SCRIPT
|
||||
float GetSubStringLength(uint32_t charnum, uint32_t nchars, ErrorResult& rv);
|
||||
MOZ_CAN_RUN_SCRIPT
|
||||
already_AddRefed<nsISVGPoint> GetStartPositionOfChar(uint32_t charnum, ErrorResult& rv);
|
||||
MOZ_CAN_RUN_SCRIPT
|
||||
already_AddRefed<nsISVGPoint> GetEndPositionOfChar(uint32_t charnum, ErrorResult& rv);
|
||||
MOZ_CAN_RUN_SCRIPT
|
||||
already_AddRefed<SVGIRect> GetExtentOfChar(uint32_t charnum, ErrorResult& rv);
|
||||
float GetRotationOfChar(uint32_t charnum, ErrorResult& rv);
|
||||
int32_t GetCharNumAtPosition(nsISVGPoint& point);
|
||||
MOZ_CAN_RUN_SCRIPT float GetRotationOfChar(uint32_t charnum, ErrorResult& rv);
|
||||
MOZ_CAN_RUN_SCRIPT int32_t GetCharNumAtPosition(nsISVGPoint& point);
|
||||
|
||||
protected:
|
||||
|
||||
|
@ -51,9 +56,9 @@ protected:
|
|||
: SVGTextContentElementBase(aNodeInfo)
|
||||
{}
|
||||
|
||||
SVGTextFrame* GetSVGTextFrame();
|
||||
SVGTextFrame* GetSVGTextFrameForNonLayoutDependentQuery();
|
||||
mozilla::Maybe<int32_t> GetNonLayoutDependentNumberOfChars();
|
||||
MOZ_CAN_RUN_SCRIPT SVGTextFrame* GetSVGTextFrame();
|
||||
MOZ_CAN_RUN_SCRIPT SVGTextFrame* GetSVGTextFrameForNonLayoutDependentQuery();
|
||||
MOZ_CAN_RUN_SCRIPT mozilla::Maybe<int32_t> GetNonLayoutDependentNumberOfChars();
|
||||
|
||||
enum { LENGTHADJUST };
|
||||
virtual nsSVGEnum* EnumAttributes() = 0;
|
||||
|
|
|
@ -37,6 +37,7 @@ public:
|
|||
already_AddRefed<SVGAnimatedTransformList> Transform();
|
||||
nsSVGElement* GetNearestViewportElement();
|
||||
nsSVGElement* GetFarthestViewportElement();
|
||||
MOZ_CAN_RUN_SCRIPT
|
||||
already_AddRefed<SVGIRect> GetBBox(const SVGBoundingBoxOptions& aOptions,
|
||||
ErrorResult& rv);
|
||||
already_AddRefed<SVGMatrix> GetCTM();
|
||||
|
|
|
@ -49,6 +49,7 @@
|
|||
#include "mozilla/dom/BindingUtils.h"
|
||||
#include "mozilla/dom/ClientManager.h"
|
||||
#include "mozilla/dom/ClientSource.h"
|
||||
#include "mozilla/dom/ClientState.h"
|
||||
#include "mozilla/dom/Console.h"
|
||||
#include "mozilla/dom/DocGroup.h"
|
||||
#include "mozilla/dom/ErrorEvent.h"
|
||||
|
@ -5333,6 +5334,24 @@ WorkerPrivate::GetClientInfo() const
|
|||
return mClientSource->Info();
|
||||
}
|
||||
|
||||
const ClientState
|
||||
WorkerPrivate::GetClientState() const
|
||||
{
|
||||
AssertIsOnWorkerThread();
|
||||
MOZ_DIAGNOSTIC_ASSERT(mClientSource);
|
||||
ClientState state;
|
||||
mClientSource->SnapshotState(&state);
|
||||
return Move(state);
|
||||
}
|
||||
|
||||
const Maybe<ServiceWorkerDescriptor>
|
||||
WorkerPrivate::GetController() const
|
||||
{
|
||||
AssertIsOnWorkerThread();
|
||||
MOZ_DIAGNOSTIC_ASSERT(mClientSource);
|
||||
return mClientSource->GetController();
|
||||
}
|
||||
|
||||
void
|
||||
WorkerPrivate::Control(const ServiceWorkerDescriptor& aServiceWorker)
|
||||
{
|
||||
|
|
|
@ -1494,6 +1494,12 @@ public:
|
|||
const ClientInfo&
|
||||
GetClientInfo() const;
|
||||
|
||||
const ClientState
|
||||
GetClientState() const;
|
||||
|
||||
const Maybe<ServiceWorkerDescriptor>
|
||||
GetController() const;
|
||||
|
||||
void
|
||||
Control(const ServiceWorkerDescriptor& aServiceWorker);
|
||||
|
||||
|
|
|
@ -515,6 +515,28 @@ WorkerGlobalScope::AbstractMainThreadFor(TaskCategory aCategory)
|
|||
MOZ_CRASH("AbstractMainThreadFor not supported for workers.");
|
||||
}
|
||||
|
||||
Maybe<ClientInfo>
|
||||
WorkerGlobalScope::GetClientInfo() const
|
||||
{
|
||||
Maybe<ClientInfo> info;
|
||||
info.emplace(mWorkerPrivate->GetClientInfo());
|
||||
return Move(info);
|
||||
}
|
||||
|
||||
Maybe<ClientState>
|
||||
WorkerGlobalScope::GetClientState() const
|
||||
{
|
||||
Maybe<ClientState> state;
|
||||
state.emplace(mWorkerPrivate->GetClientState());
|
||||
return Move(state);
|
||||
}
|
||||
|
||||
Maybe<ServiceWorkerDescriptor>
|
||||
WorkerGlobalScope::GetController() const
|
||||
{
|
||||
return mWorkerPrivate->GetController();
|
||||
}
|
||||
|
||||
DedicatedWorkerGlobalScope::DedicatedWorkerGlobalScope(WorkerPrivate* aWorkerPrivate,
|
||||
const nsString& aName)
|
||||
: WorkerGlobalScope(aWorkerPrivate)
|
||||
|
|
|
@ -19,7 +19,9 @@ namespace dom {
|
|||
|
||||
class AnyCallback;
|
||||
struct ChannelPixelLayout;
|
||||
class ClientInfo;
|
||||
class Clients;
|
||||
class ClientState;
|
||||
class Console;
|
||||
class Crypto;
|
||||
class Function;
|
||||
|
@ -221,6 +223,15 @@ public:
|
|||
|
||||
AbstractThread*
|
||||
AbstractMainThreadFor(TaskCategory aCategory) override;
|
||||
|
||||
Maybe<ClientInfo>
|
||||
GetClientInfo() const override;
|
||||
|
||||
Maybe<ClientState>
|
||||
GetClientState() const;
|
||||
|
||||
Maybe<ServiceWorkerDescriptor>
|
||||
GetController() const override;
|
||||
};
|
||||
|
||||
class DedicatedWorkerGlobalScope final : public WorkerGlobalScope
|
||||
|
|
|
@ -125,6 +125,7 @@ MOCHITEST_CHROME_MANIFESTS += [
|
|||
|
||||
BROWSER_CHROME_MANIFESTS += [
|
||||
'test/serviceworkers/browser.ini',
|
||||
'test/serviceworkers/isolated/multi-e10s-update/browser.ini',
|
||||
]
|
||||
|
||||
XPCSHELL_TESTS_MANIFESTS += ['test/xpcshell/xpcshell.ini']
|
||||
|
|
|
@ -4,19 +4,20 @@ support-files =
|
|||
browser_cached_force_refresh.html
|
||||
download/window.html
|
||||
download/worker.js
|
||||
download_canceled/page_download_canceled.html
|
||||
download_canceled/server-stream-download.sjs
|
||||
download_canceled/sw_download_canceled.js
|
||||
fetch.js
|
||||
file_multie10s_update.html
|
||||
file_userContextId_openWindow.js
|
||||
force_refresh_browser_worker.js
|
||||
empty.html
|
||||
empty.js
|
||||
server_multie10s_update.sjs
|
||||
utils.js
|
||||
|
||||
[browser_devtools_serviceworker_interception.js]
|
||||
[browser_force_refresh.js]
|
||||
[browser_download.js]
|
||||
[browser_multie10s_update.js]
|
||||
skip-if = !e10s || os != "win" # Bug 1404914
|
||||
[browser_download_canceled.js]
|
||||
[browser_storage_permission.js]
|
||||
[browser_unregister_with_containers.js]
|
||||
[browser_userContextId_openWindow.js]
|
||||
|
|
|
@ -0,0 +1,166 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
/*
|
||||
* Test cancellation of a download in order to test edge-cases related to
|
||||
* channel diversion. Channel diversion occurs in cases of file (and PSM cert)
|
||||
* downloads where we realize in the child that we really want to consume the
|
||||
* channel data in the parent. For data "sourced" by the parent, like network
|
||||
* data, data streaming to the child is suspended and the parent waits for the
|
||||
* child to send back the data it already received, then the channel is resumed.
|
||||
* For data generated by the child, such as (the current, to be mooted by
|
||||
* parent-intercept) child-side intercept, the data (currently) stream is
|
||||
* continually pumped up to the parent.
|
||||
*
|
||||
* In particular, we want to reproduce the circumstances of Bug 1418795 where
|
||||
* the child-side input-stream pump attempts to send data to the parent process
|
||||
* but the parent has canceled the channel and so the IPC Actor has been torn
|
||||
* down. Diversion begins once the nsURILoader receives the OnStartRequest
|
||||
* notification with the headers, so there are two ways to produce
|
||||
*/
|
||||
|
||||
Cu.import('resource://gre/modules/Services.jsm');
|
||||
const { Downloads } = Cu.import("resource://gre/modules/Downloads.jsm", {});
|
||||
|
||||
/**
|
||||
* Clear the downloads list so other tests don't see our byproducts.
|
||||
*/
|
||||
async function clearDownloads() {
|
||||
const downloads = await Downloads.getList(Downloads.ALL);
|
||||
downloads.removeFinished();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a Promise that will be resolved once the download dialog shows up and
|
||||
* we have clicked the given button.
|
||||
*
|
||||
* Derived from browser/components/downloads/test/browser/head.js's
|
||||
* self-contained promiseAlertDialogOpen helper, but modified to work on the
|
||||
* download dialog instead of commonDialog.xul.
|
||||
*/
|
||||
function promiseClickDownloadDialogButton(buttonAction) {
|
||||
return new Promise(resolve => {
|
||||
Services.ww.registerNotification(function onOpen(win, topic, data) {
|
||||
if (topic === "domwindowopened" && win instanceof Ci.nsIDOMWindow) {
|
||||
// The test listens for the "load" event which guarantees that the alert
|
||||
// class has already been added (it is added when "DOMContentLoaded" is
|
||||
// fired).
|
||||
win.addEventListener("load", function() {
|
||||
info(`found window of type: ${win.document.documentURI}`);
|
||||
if (win.document.documentURI ===
|
||||
"chrome://mozapps/content/downloads/unknownContentType.xul") {
|
||||
Services.ww.unregisterNotification(onOpen);
|
||||
|
||||
// nsHelperAppDlg.js currently uses an eval-based setTimeout(0) to
|
||||
// invoke its postShowCallback that results in a misleading error to
|
||||
// the console if we close the dialog before it gets a chance to
|
||||
// run. Just a setTimeout is not sufficient because it appears we
|
||||
// get our "load" listener before the document's, so we use
|
||||
// executeSoon to defer until after its load handler runs, then
|
||||
// use setTimeout(0) to end up after its eval.
|
||||
executeSoon(function() {
|
||||
setTimeout(function() {
|
||||
const button = win.document.documentElement.getButton(buttonAction);
|
||||
button.disabled = false;
|
||||
info(`clicking ${buttonAction} button`);
|
||||
button.click();
|
||||
resolve();
|
||||
}, 0);
|
||||
});
|
||||
}
|
||||
}, {once: true});
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async function performCanceledDownload(tab, path) {
|
||||
// Start waiting for the download dialog before triggering the download.
|
||||
info("watching for download popup");
|
||||
const cancelDownload = promiseClickDownloadDialogButton("cancel");
|
||||
|
||||
// Trigger the download.
|
||||
info(`triggering download of "${path}"`);
|
||||
await ContentTask.spawn(
|
||||
tab.linkedBrowser,
|
||||
path,
|
||||
function(path) {
|
||||
// Put a Promise in place that we can wait on for stream closure.
|
||||
content.wrappedJSObject.trackStreamClosure(path);
|
||||
// Create the link and trigger the download.
|
||||
const link = content.document.createElement('a');
|
||||
link.href = path;
|
||||
link.download = path;
|
||||
content.document.body.appendChild(link);
|
||||
link.click();
|
||||
});
|
||||
|
||||
// Wait for the cancelation to have been triggered.
|
||||
info("waiting for download popup");
|
||||
await cancelDownload;
|
||||
ok(true, "canceled download");
|
||||
|
||||
// Wait for confirmation that the stream stopped.
|
||||
info(`wait for the ${path} stream to close.`);
|
||||
const why = await ContentTask.spawn(
|
||||
tab.linkedBrowser,
|
||||
path,
|
||||
function(path) {
|
||||
return content.wrappedJSObject.streamClosed[path].promise;
|
||||
});
|
||||
is(why.why, "canceled", "Ensure the stream canceled instead of timing out.");
|
||||
// Note that for the "sw-stream-download" case, we end up with a bogus
|
||||
// reason of "'close' may only be called on a stream in the 'readable' state."
|
||||
// Since we aren't actually invoking close(), I'm assuming this is an
|
||||
// implementation bug that will be corrected in the web platform tests.
|
||||
info(`Cancellation reason: ${why.message} after ${why.ticks} ticks`);
|
||||
}
|
||||
|
||||
const gTestRoot = getRootDirectory(gTestPath)
|
||||
.replace("chrome://mochitests/content/", "http://mochi.test:8888/");
|
||||
|
||||
|
||||
const PAGE_URL = `${gTestRoot}download_canceled/page_download_canceled.html`;
|
||||
|
||||
add_task(async function interruptedDownloads() {
|
||||
await SpecialPowers.pushPrefEnv({'set': [
|
||||
['dom.serviceWorkers.enabled', true],
|
||||
['dom.serviceWorkers.exemptFromPerDomainMax', true],
|
||||
['dom.serviceWorkers.testing.enabled', true],
|
||||
["javascript.options.streams", true],
|
||||
["dom.streams.enabled", true],
|
||||
]});
|
||||
|
||||
// Open the tab
|
||||
const tab = await BrowserTestUtils.openNewForegroundTab({
|
||||
gBrowser,
|
||||
opening: PAGE_URL
|
||||
});
|
||||
|
||||
// Wait for it to become controlled. Check that it was a promise that
|
||||
// resolved as expected rather than undefined by checking the return value.
|
||||
const controlled = await ContentTask.spawn(
|
||||
tab.linkedBrowser,
|
||||
null,
|
||||
function() {
|
||||
// This is a promise set up by the page during load, and we are post-load.
|
||||
return content.wrappedJSObject.controlled;
|
||||
});
|
||||
is(controlled, "controlled", "page became controlled");
|
||||
|
||||
// Download a pass-through fetch stream.
|
||||
await performCanceledDownload(tab, "sw-passthrough-download");
|
||||
|
||||
// Download a SW-generated stream
|
||||
await performCanceledDownload(tab, "sw-stream-download");
|
||||
|
||||
// Cleanup
|
||||
await ContentTask.spawn(
|
||||
tab.linkedBrowser,
|
||||
null,
|
||||
function() {
|
||||
return content.wrappedJSObject.registration.unregister();
|
||||
});
|
||||
await BrowserTestUtils.removeTab(tab);
|
||||
await clearDownloads();
|
||||
});
|
|
@ -36,15 +36,21 @@ function test() {
|
|||
function() {
|
||||
var url = gTestRoot + 'browser_base_force_refresh.html';
|
||||
var tab = BrowserTestUtils.addTab(gBrowser);
|
||||
var tabBrowser = gBrowser.getBrowserForTab(tab);
|
||||
gBrowser.selectedTab = tab;
|
||||
|
||||
tab.linkedBrowser.messageManager.loadFrameScript("data:,(" + encodeURIComponent(frameScript) + ")()", true);
|
||||
gBrowser.loadURI(url);
|
||||
|
||||
function done() {
|
||||
async function done() {
|
||||
tab.linkedBrowser.messageManager.removeMessageListener("test:event", eventHandler);
|
||||
|
||||
gBrowser.removeTab(tab);
|
||||
await ContentTask.spawn(tabBrowser, null, async function() {
|
||||
const swr = await content.navigator.serviceWorker.getRegistration();
|
||||
await swr.unregister();
|
||||
});
|
||||
|
||||
await BrowserTestUtils.removeTab(tab);
|
||||
executeSoon(finish);
|
||||
}
|
||||
|
||||
|
|
|
@ -1,80 +0,0 @@
|
|||
"use strict";
|
||||
|
||||
const { classes: Cc, interfaces: Ci, results: Cr } = Components;
|
||||
|
||||
// Testing if 2 child processes are correctly managed when they both try to do
|
||||
// an SW update.
|
||||
|
||||
const BASE_URI = "http://mochi.test:8888/browser/dom/workers/test/serviceworkers/";
|
||||
|
||||
add_task(async function test_update() {
|
||||
info("Setting the prefs to having multi-e10s enabled");
|
||||
await SpecialPowers.pushPrefEnv({"set": [
|
||||
["dom.ipc.processCount", 4],
|
||||
["dom.serviceWorkers.enabled", true],
|
||||
["dom.serviceWorkers.testing.enabled", true],
|
||||
]});
|
||||
|
||||
let url = BASE_URI + "file_multie10s_update.html";
|
||||
|
||||
info("Creating the first tab...");
|
||||
let tab1 = BrowserTestUtils.addTab(gBrowser, url);
|
||||
let browser1 = gBrowser.getBrowserForTab(tab1);
|
||||
await BrowserTestUtils.browserLoaded(browser1);
|
||||
|
||||
info("Creating the second tab...");
|
||||
let tab2 = BrowserTestUtils.addTab(gBrowser, url);
|
||||
let browser2 = gBrowser.getBrowserForTab(tab2);
|
||||
await BrowserTestUtils.browserLoaded(browser2);
|
||||
|
||||
let sw = BASE_URI + "server_multie10s_update.sjs";
|
||||
|
||||
info("Let's start the test...");
|
||||
let status = await ContentTask.spawn(browser1, sw, function(url) {
|
||||
// Registration of the SW
|
||||
return content.navigator.serviceWorker.register(url)
|
||||
|
||||
// Activation
|
||||
.then(function(r) {
|
||||
return new content.window.Promise(resolve => {
|
||||
let worker = r.installing;
|
||||
worker.addEventListener('statechange', () => {
|
||||
if (worker.state === 'installed') {
|
||||
resolve(true);
|
||||
}
|
||||
});
|
||||
});
|
||||
})
|
||||
|
||||
// Waiting for the result.
|
||||
.then(() => {
|
||||
return new content.window.Promise(resolve => {
|
||||
let results = [];
|
||||
let bc = new content.window.BroadcastChannel('result');
|
||||
bc.onmessage = function(e) {
|
||||
results.push(e.data);
|
||||
if (results.length != 2) {
|
||||
return;
|
||||
}
|
||||
|
||||
resolve(results[0] + results[1]);
|
||||
}
|
||||
|
||||
// Let's inform the tabs.
|
||||
bc = new content.window.BroadcastChannel('start');
|
||||
bc.postMessage('go');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
if (status == 0) {
|
||||
ok(false, "both succeeded. This is wrong.");
|
||||
} else if (status == 1) {
|
||||
ok(true, "one succeded, one failed. This is good.");
|
||||
} else {
|
||||
ok(false, "both failed. This is definitely wrong.");
|
||||
}
|
||||
|
||||
await BrowserTestUtils.removeTab(tab1);
|
||||
await BrowserTestUtils.removeTab(tab2);
|
||||
});
|
|
@ -0,0 +1,58 @@
|
|||
<!--
|
||||
Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/
|
||||
-->
|
||||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<script src="../utils.js"></script>
|
||||
<script type="text/javascript">
|
||||
function wait_until_controlled() {
|
||||
return new Promise(function(resolve) {
|
||||
if (navigator.serviceWorker.controller) {
|
||||
return resolve('controlled');
|
||||
}
|
||||
navigator.serviceWorker.addEventListener('controllerchange', function onController() {
|
||||
if (navigator.serviceWorker.controller) {
|
||||
navigator.serviceWorker.removeEventListener('controllerchange', onController);
|
||||
return resolve('controlled');
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
addEventListener('load', async function(event) {
|
||||
window.controlled = wait_until_controlled();
|
||||
window.registration =
|
||||
await navigator.serviceWorker.register('sw_download_canceled.js');
|
||||
let sw = registration.installing || registration.waiting ||
|
||||
registration.active;
|
||||
await waitForState(sw, 'activated');
|
||||
sw.postMessage('claim');
|
||||
});
|
||||
|
||||
// Place to hold promises for stream closures reported by the SW.
|
||||
window.streamClosed = {};
|
||||
|
||||
// The ServiceWorker will postMessage to this BroadcastChannel when the streams
|
||||
// are closed. (Alternately, the SW could have used the clients API to post at
|
||||
// us, but the mechanism by which that operates would be different when this
|
||||
// test is uplifted, and it's desirable to avoid timing changes.)
|
||||
//
|
||||
// The browser test will use this promise to wait on stream shutdown.
|
||||
window.swStreamChannel = new BroadcastChannel("stream-closed");
|
||||
function trackStreamClosure(path) {
|
||||
let resolve;
|
||||
const promise = new Promise(r => { resolve = r });
|
||||
window.streamClosed[path] = { promise, resolve };
|
||||
}
|
||||
window.swStreamChannel.onmessage = ({ data }) => {
|
||||
window.streamClosed[data.what].resolve(data);
|
||||
};
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,123 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
|
||||
|
||||
Cu.import("resource://gre/modules/Timer.jsm");
|
||||
Cu.import("resource://gre/modules/NetUtil.jsm");
|
||||
|
||||
// stolen from file_blocked_script.sjs
|
||||
function setGlobalState(data, key)
|
||||
{
|
||||
x = { data: data, QueryInterface: function(iid) { return this } };
|
||||
x.wrappedJSObject = x;
|
||||
setObjectState(key, x);
|
||||
}
|
||||
|
||||
function getGlobalState(key)
|
||||
{
|
||||
var data;
|
||||
getObjectState(key, function(x) {
|
||||
data = x && x.wrappedJSObject.data;
|
||||
});
|
||||
return data;
|
||||
}
|
||||
|
||||
/*
|
||||
* We want to let the sw_download_canceled.js service worker know when the
|
||||
* stream was canceled. To this end, we let it issue a monitor request which we
|
||||
* fulfill when the stream has been canceled. In order to coordinate between
|
||||
* multiple requests, we use the getObjectState/setObjectState mechanism that
|
||||
* httpd.js exposes to let data be shared and/or persist between requests. We
|
||||
* handle both possible orderings of the requests because we currently don't
|
||||
* try and impose an ordering between the two requests as issued by the SW, and
|
||||
* file_blocked_script.sjs encourages us to do this, but we probably could order
|
||||
* them.
|
||||
*/
|
||||
const MONITOR_KEY = "stream-monitor";
|
||||
function completeMonitorResponse(response, data) {
|
||||
response.write(JSON.stringify(data));
|
||||
response.finish();
|
||||
}
|
||||
function handleMonitorRequest(request, response) {
|
||||
response.setHeader("Content-Type", "application/json");
|
||||
response.setStatusLine(request.httpVersion, 200, "Found");
|
||||
|
||||
response.processAsync();
|
||||
// Necessary to cause the headers to be flushed; that or touching the
|
||||
// bodyOutputStream getter.
|
||||
response.write("");
|
||||
dump("server-stream-download.js: monitor headers issued\n");
|
||||
|
||||
const alreadyCompleted = getGlobalState(MONITOR_KEY);
|
||||
if (alreadyCompleted) {
|
||||
completeMonitorResponse(response, alreadyCompleted);
|
||||
setGlobalState(null, MONITOR_KEY);
|
||||
} else {
|
||||
setGlobalState(response, MONITOR_KEY);
|
||||
}
|
||||
}
|
||||
|
||||
const MAX_TICK_COUNT = 3000;
|
||||
const TICK_INTERVAL = 2;
|
||||
function handleStreamRequest(request, response) {
|
||||
const name = "server-stream-download";
|
||||
|
||||
// Create some payload to send.
|
||||
let strChunk =
|
||||
'Static routes are the future of ServiceWorkers! So say we all!\n';
|
||||
while (strChunk.length < 1024) {
|
||||
strChunk += strChunk;
|
||||
}
|
||||
|
||||
response.setHeader("Content-Disposition", `attachment; filename="${name}"`);
|
||||
response.setHeader("Content-Type", `application/octet-stream; name="${name}"`);
|
||||
response.setHeader("Content-Length", `${strChunk.length * MAX_TICK_COUNT}`);
|
||||
response.setStatusLine(request.httpVersion, 200, "Found");
|
||||
|
||||
response.processAsync();
|
||||
response.write(strChunk);
|
||||
dump("server-stream-download.js: stream headers + first payload issued\n");
|
||||
|
||||
let count = 0;
|
||||
let intervalId;
|
||||
function closeStream(why, message) {
|
||||
dump("server-stream-download.js: closing stream: " + why + "\n");
|
||||
clearInterval(intervalId);
|
||||
response.finish();
|
||||
|
||||
const data = { why, message };
|
||||
const monitorResponse = getGlobalState(MONITOR_KEY);
|
||||
if (monitorResponse) {
|
||||
completeMonitorResponse(monitorResponse, data);
|
||||
setGlobalState(null, MONITOR_KEY);
|
||||
} else {
|
||||
setGlobalState(data, MONITOR_KEY);
|
||||
}
|
||||
}
|
||||
function tick() {
|
||||
try {
|
||||
// bound worst-case behavior.
|
||||
if (count++ > MAX_TICK_COUNT) {
|
||||
closeStream("timeout", "timeout");
|
||||
return;
|
||||
}
|
||||
response.write(strChunk);
|
||||
} catch(e) {
|
||||
closeStream("canceled", e.message);
|
||||
}
|
||||
}
|
||||
intervalId = setInterval(tick, TICK_INTERVAL);
|
||||
}
|
||||
|
||||
Components.utils.importGlobalProperties(["URLSearchParams"]);
|
||||
function handleRequest(request, response) {
|
||||
dump("server-stream-download.js: processing request for " + request.path +
|
||||
"?" + request.queryString + "\n");
|
||||
const query = new URLSearchParams(request.queryString);
|
||||
if (query.has("monitor")) {
|
||||
handleMonitorRequest(request, response);
|
||||
} else {
|
||||
handleStreamRequest(request, response);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,136 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
// This file is derived from :bkelly's https://glitch.com/edit/#!/html-sw-stream
|
||||
|
||||
addEventListener("install", evt => {
|
||||
evt.waitUntil(self.skipWaiting());
|
||||
});
|
||||
|
||||
// Create a BroadcastChannel to notify when we have closed our streams.
|
||||
const channel = new BroadcastChannel("stream-closed");
|
||||
|
||||
const MAX_TICK_COUNT = 3000;
|
||||
const TICK_INTERVAL = 4;
|
||||
/**
|
||||
* Generate a continuous stream of data at a sufficiently high frequency that a
|
||||
* there"s a good chance of racing channel cancellation.
|
||||
*/
|
||||
function handleStream(evt, filename) {
|
||||
// Create some payload to send.
|
||||
const encoder = new TextEncoder();
|
||||
let strChunk =
|
||||
"Static routes are the future of ServiceWorkers! So say we all!\n";
|
||||
while (strChunk.length < 1024) {
|
||||
strChunk += strChunk;
|
||||
}
|
||||
const dataChunk = encoder.encode(strChunk);
|
||||
|
||||
evt.waitUntil(new Promise(resolve => {
|
||||
let body = new ReadableStream({
|
||||
start: controller => {
|
||||
const closeStream = (why) => {
|
||||
console.log("closing stream: " + JSON.stringify(why) + "\n");
|
||||
clearInterval(intervalId);
|
||||
resolve();
|
||||
// In event of error, the controller will automatically have closed.
|
||||
if (why.why != "canceled") {
|
||||
try {
|
||||
controller.close();
|
||||
} catch(ex) {
|
||||
// If we thought we should cancel but experienced a problem,
|
||||
// that's a different kind of failure and we need to report it.
|
||||
// (If we didn't catch the exception here, we'd end up erroneously
|
||||
// in the tick() method's canceled handler.)
|
||||
channel.postMessage({
|
||||
what: filename,
|
||||
why: "close-failure",
|
||||
message: ex.message,
|
||||
ticks: why.ticks
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
// Post prior to performing any attempt to close...
|
||||
channel.postMessage(why);
|
||||
};
|
||||
|
||||
controller.enqueue(dataChunk);
|
||||
let count = 0;
|
||||
let intervalId;
|
||||
function tick() {
|
||||
try {
|
||||
// bound worst-case behavior.
|
||||
if (count++ > MAX_TICK_COUNT) {
|
||||
closeStream({
|
||||
what: filename, why: "timeout", message: "timeout", ticks: count
|
||||
});
|
||||
return;
|
||||
}
|
||||
controller.enqueue(dataChunk);
|
||||
} catch(e) {
|
||||
closeStream({
|
||||
what: filename, why: "canceled", message: e.message, ticks: count
|
||||
});
|
||||
}
|
||||
}
|
||||
// Alternately, streams' pull mechanism could be used here, but this
|
||||
// test doesn't so much want to saturate the stream as to make sure the
|
||||
// data is at least flowing a little bit. (Also, the author had some
|
||||
// concern about slowing down the test by overwhelming the event loop
|
||||
// and concern that we might not have sufficent back-pressure plumbed
|
||||
// through and an infinite pipe might make bad things happen.)
|
||||
intervalId = setInterval(tick, TICK_INTERVAL);
|
||||
tick();
|
||||
},
|
||||
});
|
||||
evt.respondWith(new Response(body, {
|
||||
headers: {
|
||||
"Content-Disposition": `attachment; filename="${filename}"`,
|
||||
"Content-Type": "application/octet-stream"
|
||||
}
|
||||
}));
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Use an .sjs to generate a similar stream of data to the above, passing the
|
||||
* response through directly. Because we're handing off the response but also
|
||||
* want to be able to report when cancellation occurs, we create a second,
|
||||
* overlapping long-poll style fetch that will not finish resolving until the
|
||||
* .sjs experiences closure of its socket and terminates the payload stream.
|
||||
*/
|
||||
function handlePassThrough(evt, filename) {
|
||||
evt.waitUntil((async () => {
|
||||
console.log("issuing monitor fetch request");
|
||||
const response = await fetch("server-stream-download.sjs?monitor");
|
||||
console.log("monitor headers received, awaiting body");
|
||||
const data = await response.json();
|
||||
console.log("passthrough monitor fetch completed, notifying.");
|
||||
channel.postMessage({
|
||||
what: filename,
|
||||
why: data.why,
|
||||
message: data.message
|
||||
});
|
||||
})());
|
||||
evt.respondWith(fetch("server-stream-download.sjs").then(response => {
|
||||
console.log("server-stream-download.sjs Response received, propagating");
|
||||
return response;
|
||||
}));
|
||||
}
|
||||
|
||||
addEventListener("fetch", evt => {
|
||||
console.log(`SW processing fetch of ${evt.request.url}`);
|
||||
if (evt.request.url.indexOf("sw-stream-download") >= 0) {
|
||||
return handleStream(evt, "sw-stream-download");
|
||||
}
|
||||
if (evt.request.url.indexOf("sw-passthrough-download") >= 0) {
|
||||
return handlePassThrough(evt, "sw-passthrough-download");
|
||||
}
|
||||
})
|
||||
|
||||
addEventListener("message", evt => {
|
||||
if (evt.data === "claim") {
|
||||
evt.waitUntil(clients.claim());
|
||||
}
|
||||
});
|
|
@ -0,0 +1,19 @@
|
|||
This directory contains tests that are flaky when run with other tests
|
||||
but that we don't want to disable and where it's not trivial to make
|
||||
the tests not flaky at this time, but we have a plan to fix them via
|
||||
systemic fixes that are improving the codebase rather than hacking a
|
||||
test until it works.
|
||||
|
||||
This directory and ugly hack structure needs to exist because of
|
||||
multi-e10s propagation races that will go away when we finish
|
||||
implementing the multi-e10s overhaul for ServiceWorkers. Most
|
||||
specifically, unregister() calls need to propagate across all
|
||||
content processes. There are fixes on bug 1318142, but they're
|
||||
ugly and complicate things.
|
||||
|
||||
Specific test notes and rationalizations:
|
||||
- multi-e10s-update: This test relies on there being no registrations
|
||||
existing at its start. The preceding test that induces the breakage
|
||||
(`browser_force_refresh.js`) was made to clean itself up, but the
|
||||
unregister() race issue is not easily/cleanly hacked around and this
|
||||
test will itself become moot when the multi-e10s changes land.
|
|
@ -0,0 +1,8 @@
|
|||
[DEFAULT]
|
||||
support-files =
|
||||
file_multie10s_update.html
|
||||
server_multie10s_update.sjs
|
||||
|
||||
[browser_multie10s_update.js]
|
||||
skip-if = true # bug 1429794 is to re-enable, then un-comment the below.
|
||||
#skip-if = !e10s # this is an e10s-only test.
|
|
@ -0,0 +1,135 @@
|
|||
"use strict";
|
||||
|
||||
const { classes: Cc, interfaces: Ci, results: Cr } = Components;
|
||||
|
||||
// Testing if 2 child processes are correctly managed when they both try to do
|
||||
// an SW update.
|
||||
|
||||
const BASE_URI =
|
||||
"http://mochi.test:8888/browser/dom/workers/test/serviceworkers/isolated/multi-e10s-update/";
|
||||
|
||||
add_task(async function test_update() {
|
||||
info("Setting the prefs to having multi-e10s enabled");
|
||||
await SpecialPowers.pushPrefEnv({"set": [
|
||||
["dom.ipc.processCount", 4],
|
||||
["dom.serviceWorkers.enabled", true],
|
||||
["dom.serviceWorkers.testing.enabled", true],
|
||||
]});
|
||||
|
||||
let url = BASE_URI + "file_multie10s_update.html";
|
||||
|
||||
info("Creating the first tab...");
|
||||
let tab1 = BrowserTestUtils.addTab(gBrowser, url);
|
||||
let browser1 = gBrowser.getBrowserForTab(tab1);
|
||||
await BrowserTestUtils.browserLoaded(browser1);
|
||||
|
||||
info("Creating the second tab...");
|
||||
let tab2 = BrowserTestUtils.addTab(gBrowser, url);
|
||||
let browser2 = gBrowser.getBrowserForTab(tab2);
|
||||
await BrowserTestUtils.browserLoaded(browser2);
|
||||
|
||||
let sw = BASE_URI + "server_multie10s_update.sjs";
|
||||
|
||||
info("Let's make sure there are no existing registrations...");
|
||||
let existingCount = await ContentTask.spawn(browser1, null, async function() {
|
||||
const regs = await content.navigator.serviceWorker.getRegistrations();
|
||||
return regs.length;
|
||||
});
|
||||
is(existingCount, 0, "Previous tests should have cleaned up!");
|
||||
|
||||
info("Let's start the test...");
|
||||
let status = await ContentTask.spawn(browser1, sw, function(url) {
|
||||
// Let the SW be served immediately once by triggering a relase immediately.
|
||||
// We don't need to await this. We do this from a frame script because
|
||||
// it has fetch.
|
||||
content.fetch(url + "?release");
|
||||
|
||||
// Registration of the SW
|
||||
return content.navigator.serviceWorker.register(url)
|
||||
|
||||
// Activation
|
||||
.then(function(r) {
|
||||
content.registration = r;
|
||||
return new content.window.Promise(resolve => {
|
||||
let worker = r.installing;
|
||||
worker.addEventListener('statechange', () => {
|
||||
if (worker.state === 'installed') {
|
||||
resolve(true);
|
||||
}
|
||||
});
|
||||
});
|
||||
})
|
||||
|
||||
// Waiting for the result.
|
||||
.then(() => {
|
||||
return new content.window.Promise(resolveResults => {
|
||||
// Once both updates have been issued and a single update has failed, we
|
||||
// can tell the .sjs to release a single copy of the SW script.
|
||||
let updateCount = 0;
|
||||
const uc = new content.window.BroadcastChannel('update');
|
||||
// This promise tracks the updates tally.
|
||||
const updatesIssued = new Promise(resolveUpdatesIssued => {
|
||||
uc.onmessage = function(e) {
|
||||
updateCount++;
|
||||
console.log("got update() number", updateCount);
|
||||
if (updateCount === 2) {
|
||||
resolveUpdatesIssued();
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
let results = [];
|
||||
const rc = new content.window.BroadcastChannel('result');
|
||||
// This promise resolves when an update has failed.
|
||||
const oneFailed = new Promise(resolveOneFailed => {
|
||||
rc.onmessage = function(e) {
|
||||
console.log("got result", e.data);
|
||||
results.push(e.data);
|
||||
if (e.data === 1) {
|
||||
resolveOneFailed();
|
||||
}
|
||||
if (results.length != 2) {
|
||||
return;
|
||||
}
|
||||
|
||||
resolveResults(results[0] + results[1]);
|
||||
}
|
||||
});
|
||||
|
||||
Promise.all([updatesIssued, oneFailed]).then(() => {
|
||||
console.log("releasing update");
|
||||
content.fetch(url + "?release").catch((ex) => {
|
||||
console.error("problem releasing:", ex);
|
||||
});
|
||||
});
|
||||
|
||||
// Let's inform the tabs.
|
||||
const sc = new content.window.BroadcastChannel('start');
|
||||
sc.postMessage('go');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
if (status == 0) {
|
||||
ok(false, "both succeeded. This is wrong.");
|
||||
} else if (status == 1) {
|
||||
ok(true, "one succeded, one failed. This is good.");
|
||||
} else {
|
||||
ok(false, "both failed. This is definitely wrong.");
|
||||
}
|
||||
|
||||
// let's clean up the registration and get the fetch count. The count
|
||||
// should be 1 for the initial fetch and 1 for the update.
|
||||
const count = await ContentTask.spawn(browser1, sw, async function(url) {
|
||||
// We stored the registration on the frame script's wrapper, hence directly
|
||||
// accesss content without using wrappedJSObject.
|
||||
await content.registration.unregister();
|
||||
const { count } =
|
||||
await content.fetch(url + "?get-and-clear-count").then(r => r.json());
|
||||
return count;
|
||||
});
|
||||
is(count, 2, "SW should have been fetched only twice");
|
||||
|
||||
await BrowserTestUtils.removeTab(tab1);
|
||||
await BrowserTestUtils.removeTab(tab2);
|
||||
});
|
|
@ -24,6 +24,12 @@ bc.onmessage = function(e) {
|
|||
}, () => {
|
||||
bc.postMessage(1);
|
||||
});
|
||||
|
||||
// Tell the coordinating frame script that we've kicked off our update
|
||||
// call so that the SW script can be released once both instances of us
|
||||
// have triggered update() and 1 has failed.
|
||||
const blockingChannel = new BroadcastChannel('update');
|
||||
blockingChannel.postMessage(true);
|
||||
});
|
||||
}
|
||||
|
|
@ -0,0 +1,97 @@
|
|||
|
||||
// stolen from file_blocked_script.sjs
|
||||
function setGlobalState(data, key)
|
||||
{
|
||||
x = { data: data, QueryInterface: function(iid) { return this } };
|
||||
x.wrappedJSObject = x;
|
||||
setObjectState(key, x);
|
||||
}
|
||||
|
||||
function getGlobalState(key)
|
||||
{
|
||||
var data;
|
||||
getObjectState(key, function(x) {
|
||||
data = x && x.wrappedJSObject.data;
|
||||
});
|
||||
return data;
|
||||
}
|
||||
|
||||
function completeBlockingRequest(response)
|
||||
{
|
||||
response.write("42");
|
||||
response.finish();
|
||||
}
|
||||
|
||||
// This stores the response that's currently blocking, or true if the release
|
||||
// got here before the blocking request.
|
||||
const BLOCKING_KEY = "multie10s-update-release";
|
||||
// This tracks the number of blocking requests we received up to this point in
|
||||
// time. This value will be cleared when fetched. It's on the caller to make
|
||||
// sure that all the blocking requests that might occurr have already occurred.
|
||||
const COUNT_KEY = "multie10s-update-count";
|
||||
|
||||
/**
|
||||
* Serve a request that will only be completed when the ?release variant of this
|
||||
* .sjs is fetched. This allows us to avoid using a timer, which slows down the
|
||||
* tests and is brittle under slow hardware.
|
||||
*/
|
||||
function handleBlockingRequest(request, response)
|
||||
{
|
||||
response.processAsync();
|
||||
response.setHeader("Content-Type", "application/javascript", false);
|
||||
|
||||
const existingCount = getGlobalState(COUNT_KEY) || 0;
|
||||
setGlobalState(existingCount + 1, COUNT_KEY);
|
||||
|
||||
const alreadyReleased = getGlobalState(BLOCKING_KEY);
|
||||
if (alreadyReleased === true) {
|
||||
completeBlockingRequest(response);
|
||||
setGlobalState(null, BLOCKING_KEY);
|
||||
} else if (alreadyReleased) {
|
||||
// If we've got another response stacked up, this means something is wrong
|
||||
// with the test. The count mechanism will detect this, so just let this
|
||||
// one through so we fail fast rather than hanging.
|
||||
dump("we got multiple blocking requests stacked up!!\n");
|
||||
completeBlockingRequest(response);
|
||||
} else {
|
||||
setGlobalState(response, BLOCKING_KEY);
|
||||
}
|
||||
}
|
||||
|
||||
function handleReleaseRequest(request, response)
|
||||
{
|
||||
const blockingResponse = getGlobalState(BLOCKING_KEY);
|
||||
if (blockingResponse) {
|
||||
completeBlockingRequest(blockingResponse);
|
||||
setGlobalState(null, BLOCKING_KEY);
|
||||
} else {
|
||||
setGlobalState(true, BLOCKING_KEY);
|
||||
}
|
||||
|
||||
response.setHeader("Content-Type", "application/json", false);
|
||||
response.write(JSON.stringify({ released: true }));
|
||||
}
|
||||
|
||||
function handleCountRequest(request, response)
|
||||
{
|
||||
const count = getGlobalState(COUNT_KEY) || 0;
|
||||
// --verify requires that we clear this so the test can be re-run.
|
||||
setGlobalState(0, COUNT_KEY);
|
||||
|
||||
response.setHeader("Content-Type", "application/json", false);
|
||||
response.write(JSON.stringify({ count: count }));
|
||||
}
|
||||
|
||||
Components.utils.importGlobalProperties(["URLSearchParams"]);
|
||||
function handleRequest(request, response) {
|
||||
dump("server_multie10s_update.sjs: processing request for " + request.path +
|
||||
"?" + request.queryString + "\n");
|
||||
const query = new URLSearchParams(request.queryString);
|
||||
if (query.has("release")) {
|
||||
handleReleaseRequest(request, response);
|
||||
} else if (query.has("get-and-clear-count")) {
|
||||
handleCountRequest(request, response);
|
||||
} else {
|
||||
handleBlockingRequest(request, response);
|
||||
}
|
||||
}
|
|
@ -1,12 +0,0 @@
|
|||
function handleRequest(request, response)
|
||||
{
|
||||
response.processAsync();
|
||||
response.setHeader("Content-Type", "application/javascript", false);
|
||||
|
||||
timer = Components.classes["@mozilla.org/timer;1"].
|
||||
createInstance(Components.interfaces.nsITimer);
|
||||
timer.init(function() {
|
||||
response.write("42");
|
||||
response.finish();
|
||||
}, 3000, Components.interfaces.nsITimer.TYPE_ONE_SHOT);
|
||||
}
|
|
@ -194,7 +194,7 @@ for (let type of ['f32', 'f64']) {
|
|||
(func (export "") (call $foo))
|
||||
)`,
|
||||
WebAssembly.RuntimeError,
|
||||
["", ">", "1,>", "0,1,>", "interstitial,0,1,>", "trap handling,0,1,>", "", ">", ""]);
|
||||
["", ">", "1,>", "0,1,>", "1,>", "", ">", ""]);
|
||||
|
||||
testError(
|
||||
`(module
|
||||
|
|
|
@ -12350,7 +12350,7 @@ CodeGenerator::visitWasmTrap(LWasmTrap* lir)
|
|||
MOZ_ASSERT(gen->compilingWasm());
|
||||
const MWasmTrap* mir = lir->mir();
|
||||
|
||||
masm.jump(oldTrap(mir, mir->trap()));
|
||||
masm.wasmTrap(mir->trap(), mir->bytecodeOffset());
|
||||
}
|
||||
|
||||
void
|
||||
|
|
|
@ -2930,6 +2930,12 @@ MacroAssembler::maybeBranchTestType(MIRType type, MDefinition* maybeDef, Registe
|
|||
}
|
||||
}
|
||||
|
||||
void
|
||||
MacroAssembler::wasmTrap(wasm::Trap trap, wasm::BytecodeOffset bytecodeOffset)
|
||||
{
|
||||
append(trap, wasm::TrapSite(illegalInstruction().offset(), bytecodeOffset));
|
||||
}
|
||||
|
||||
void
|
||||
MacroAssembler::wasmCallImport(const wasm::CallSiteDesc& desc, const wasm::CalleeDesc& callee)
|
||||
{
|
||||
|
|
|
@ -1399,6 +1399,9 @@ class MacroAssembler : public MacroAssemblerSpecific
|
|||
// ========================================================================
|
||||
// wasm support
|
||||
|
||||
CodeOffset illegalInstruction() PER_SHARED_ARCH;
|
||||
void wasmTrap(wasm::Trap trap, wasm::BytecodeOffset bytecodeOffset);
|
||||
|
||||
// Emit a bounds check against the wasm heap limit, jumping to 'label' if 'cond' holds.
|
||||
// Required when WASM_HUGE_MEMORY is not defined.
|
||||
template <class L>
|
||||
|
|
|
@ -3005,6 +3005,14 @@ Assembler::as_bkpt()
|
|||
hit++;
|
||||
}
|
||||
|
||||
BufferOffset
|
||||
Assembler::as_illegal_trap()
|
||||
{
|
||||
// Encoding of the permanently-undefined 'udf' instruction, with the imm16
|
||||
// set to 0.
|
||||
return writeInst(0xe7f000f0);
|
||||
}
|
||||
|
||||
void
|
||||
Assembler::flushBuffer()
|
||||
{
|
||||
|
|
|
@ -1733,6 +1733,7 @@ class Assembler : public AssemblerShared
|
|||
static void Bind(uint8_t* rawCode, CodeOffset label, CodeOffset target);
|
||||
|
||||
void as_bkpt();
|
||||
BufferOffset as_illegal_trap();
|
||||
|
||||
public:
|
||||
static void TraceJumpRelocations(JSTracer* trc, JitCode* code, CompactBufferReader& reader);
|
||||
|
|
|
@ -4911,6 +4911,12 @@ template void
|
|||
MacroAssembler::storeUnboxedValue(const ConstantOrRegister& value, MIRType valueType,
|
||||
const BaseIndex& dest, MIRType slotType);
|
||||
|
||||
CodeOffset
|
||||
MacroAssembler::illegalInstruction()
|
||||
{
|
||||
return CodeOffset(as_illegal_trap().getOffset());
|
||||
}
|
||||
|
||||
void
|
||||
MacroAssembler::wasmTruncateDoubleToUInt32(FloatRegister input, Register output, Label* oolEntry)
|
||||
{
|
||||
|
|
|
@ -260,6 +260,11 @@ class SimInstruction {
|
|||
return typeValue() == 7 && bit(24) == 1 && svcValue() >= kStopCode;
|
||||
}
|
||||
|
||||
// Test for a udf instruction, which falls under type 3.
|
||||
inline bool isUDF() const {
|
||||
return (instructionBits() & 0xfff000f0) == 0xe7f000f0;
|
||||
}
|
||||
|
||||
// Special accessors that test for existence of a value.
|
||||
inline bool hasS() const { return sValue() == 1; }
|
||||
inline bool hasB() const { return bValue() == 1; }
|
||||
|
@ -1583,6 +1588,16 @@ Simulator::handleWasmInterrupt()
|
|||
set_pc(int32_t(cs->interruptCode()));
|
||||
}
|
||||
|
||||
static inline JitActivation*
|
||||
GetJitActivation(JSContext* cx)
|
||||
{
|
||||
if (!wasm::CodeExists)
|
||||
return nullptr;
|
||||
if (!cx->activation() || !cx->activation()->isJit())
|
||||
return nullptr;
|
||||
return cx->activation()->asJit();
|
||||
}
|
||||
|
||||
// WebAssembly memories contain an extra region of guard pages (see
|
||||
// WasmArrayRawBuffer comment). The guard pages catch out-of-bounds accesses
|
||||
// using a signal handler that redirects PC to a stub that safely reports an
|
||||
|
@ -1590,13 +1605,11 @@ Simulator::handleWasmInterrupt()
|
|||
// and cannot be redirected. Therefore, we must avoid hitting the handler by
|
||||
// redirecting in the simulator before the real handler would have been hit.
|
||||
bool
|
||||
Simulator::handleWasmFault(int32_t addr, unsigned numBytes)
|
||||
Simulator::handleWasmSegFault(int32_t addr, unsigned numBytes)
|
||||
{
|
||||
if (!wasm::CodeExists)
|
||||
JitActivation* act = GetJitActivation(cx_);
|
||||
if (!act)
|
||||
return false;
|
||||
if (!cx_->activation() || !cx_->activation()->isJit())
|
||||
return false;
|
||||
JitActivation* act = cx_->activation()->asJit();
|
||||
|
||||
void* pc = reinterpret_cast<void*>(get_pc());
|
||||
uint8_t* fp = reinterpret_cast<uint8_t*>(get_register(r11));
|
||||
|
@ -1623,10 +1636,34 @@ Simulator::handleWasmFault(int32_t addr, unsigned numBytes)
|
|||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
Simulator::handleWasmIllFault()
|
||||
{
|
||||
JitActivation* act = GetJitActivation(cx_);
|
||||
if (!act)
|
||||
return false;
|
||||
|
||||
void* pc = reinterpret_cast<void*>(get_pc());
|
||||
uint8_t* fp = reinterpret_cast<uint8_t*>(get_register(r11));
|
||||
|
||||
const wasm::CodeSegment* segment = wasm::LookupCodeSegment(pc);
|
||||
if (!segment)
|
||||
return false;
|
||||
|
||||
wasm::Trap trap;
|
||||
wasm::BytecodeOffset bytecode;
|
||||
if (!segment->code().lookupTrap(pc, &trap, &bytecode))
|
||||
return false;
|
||||
|
||||
act->startWasmTrap(trap, bytecode.offset, pc, fp);
|
||||
set_pc(int32_t(segment->trapCode()));
|
||||
return true;
|
||||
}
|
||||
|
||||
uint64_t
|
||||
Simulator::readQ(int32_t addr, SimInstruction* instr, UnalignedPolicy f)
|
||||
{
|
||||
if (handleWasmFault(addr, 8))
|
||||
if (handleWasmSegFault(addr, 8))
|
||||
return UINT64_MAX;
|
||||
|
||||
if ((addr & 3) == 0 || (f == AllowUnaligned && !HasAlignmentFault())) {
|
||||
|
@ -1649,7 +1686,7 @@ Simulator::readQ(int32_t addr, SimInstruction* instr, UnalignedPolicy f)
|
|||
void
|
||||
Simulator::writeQ(int32_t addr, uint64_t value, SimInstruction* instr, UnalignedPolicy f)
|
||||
{
|
||||
if (handleWasmFault(addr, 8))
|
||||
if (handleWasmSegFault(addr, 8))
|
||||
return;
|
||||
|
||||
if ((addr & 3) == 0 || (f == AllowUnaligned && !HasAlignmentFault())) {
|
||||
|
@ -1672,7 +1709,7 @@ Simulator::writeQ(int32_t addr, uint64_t value, SimInstruction* instr, Unaligned
|
|||
int
|
||||
Simulator::readW(int32_t addr, SimInstruction* instr, UnalignedPolicy f)
|
||||
{
|
||||
if (handleWasmFault(addr, 4))
|
||||
if (handleWasmSegFault(addr, 4))
|
||||
return -1;
|
||||
|
||||
if ((addr & 3) == 0 || (f == AllowUnaligned && !HasAlignmentFault())) {
|
||||
|
@ -1698,7 +1735,7 @@ Simulator::readW(int32_t addr, SimInstruction* instr, UnalignedPolicy f)
|
|||
void
|
||||
Simulator::writeW(int32_t addr, int value, SimInstruction* instr, UnalignedPolicy f)
|
||||
{
|
||||
if (handleWasmFault(addr, 4))
|
||||
if (handleWasmSegFault(addr, 4))
|
||||
return;
|
||||
|
||||
if ((addr & 3) == 0 || (f == AllowUnaligned && !HasAlignmentFault())) {
|
||||
|
@ -1743,7 +1780,7 @@ Simulator::readExW(int32_t addr, SimInstruction* instr)
|
|||
if (addr & 3)
|
||||
MOZ_CRASH("Unaligned exclusive read");
|
||||
|
||||
if (handleWasmFault(addr, 4))
|
||||
if (handleWasmSegFault(addr, 4))
|
||||
return -1;
|
||||
|
||||
SharedMem<int32_t*> ptr = SharedMem<int32_t*>::shared(reinterpret_cast<int32_t*>(addr));
|
||||
|
@ -1758,7 +1795,7 @@ Simulator::writeExW(int32_t addr, int value, SimInstruction* instr)
|
|||
if (addr & 3)
|
||||
MOZ_CRASH("Unaligned exclusive write");
|
||||
|
||||
if (handleWasmFault(addr, 4))
|
||||
if (handleWasmSegFault(addr, 4))
|
||||
return -1;
|
||||
|
||||
SharedMem<int32_t*> ptr = SharedMem<int32_t*>::shared(reinterpret_cast<int32_t*>(addr));
|
||||
|
@ -1773,7 +1810,7 @@ Simulator::writeExW(int32_t addr, int value, SimInstruction* instr)
|
|||
uint16_t
|
||||
Simulator::readHU(int32_t addr, SimInstruction* instr)
|
||||
{
|
||||
if (handleWasmFault(addr, 2))
|
||||
if (handleWasmSegFault(addr, 2))
|
||||
return UINT16_MAX;
|
||||
|
||||
// The regexp engine emits unaligned loads, so we don't check for them here
|
||||
|
@ -1799,7 +1836,7 @@ Simulator::readHU(int32_t addr, SimInstruction* instr)
|
|||
int16_t
|
||||
Simulator::readH(int32_t addr, SimInstruction* instr)
|
||||
{
|
||||
if (handleWasmFault(addr, 2))
|
||||
if (handleWasmSegFault(addr, 2))
|
||||
return -1;
|
||||
|
||||
if ((addr & 1) == 0 || !HasAlignmentFault()) {
|
||||
|
@ -1823,7 +1860,7 @@ Simulator::readH(int32_t addr, SimInstruction* instr)
|
|||
void
|
||||
Simulator::writeH(int32_t addr, uint16_t value, SimInstruction* instr)
|
||||
{
|
||||
if (handleWasmFault(addr, 2))
|
||||
if (handleWasmSegFault(addr, 2))
|
||||
return;
|
||||
|
||||
if ((addr & 1) == 0 || !HasAlignmentFault()) {
|
||||
|
@ -1846,7 +1883,7 @@ Simulator::writeH(int32_t addr, uint16_t value, SimInstruction* instr)
|
|||
void
|
||||
Simulator::writeH(int32_t addr, int16_t value, SimInstruction* instr)
|
||||
{
|
||||
if (handleWasmFault(addr, 2))
|
||||
if (handleWasmSegFault(addr, 2))
|
||||
return;
|
||||
|
||||
if ((addr & 1) == 0 || !HasAlignmentFault()) {
|
||||
|
@ -1872,7 +1909,7 @@ Simulator::readExHU(int32_t addr, SimInstruction* instr)
|
|||
if (addr & 1)
|
||||
MOZ_CRASH("Unaligned exclusive read");
|
||||
|
||||
if (handleWasmFault(addr, 2))
|
||||
if (handleWasmSegFault(addr, 2))
|
||||
return UINT16_MAX;
|
||||
|
||||
SharedMem<uint16_t*> ptr = SharedMem<uint16_t*>::shared(reinterpret_cast<uint16_t*>(addr));
|
||||
|
@ -1887,7 +1924,7 @@ Simulator::writeExH(int32_t addr, uint16_t value, SimInstruction* instr)
|
|||
if (addr & 1)
|
||||
MOZ_CRASH("Unaligned exclusive write");
|
||||
|
||||
if (handleWasmFault(addr, 2))
|
||||
if (handleWasmSegFault(addr, 2))
|
||||
return -1;
|
||||
|
||||
SharedMem<uint16_t*> ptr = SharedMem<uint16_t*>::shared(reinterpret_cast<uint16_t*>(addr));
|
||||
|
@ -1902,7 +1939,7 @@ Simulator::writeExH(int32_t addr, uint16_t value, SimInstruction* instr)
|
|||
uint8_t
|
||||
Simulator::readBU(int32_t addr)
|
||||
{
|
||||
if (handleWasmFault(addr, 1))
|
||||
if (handleWasmSegFault(addr, 1))
|
||||
return UINT8_MAX;
|
||||
|
||||
uint8_t* ptr = reinterpret_cast<uint8_t*>(addr);
|
||||
|
@ -1912,7 +1949,7 @@ Simulator::readBU(int32_t addr)
|
|||
uint8_t
|
||||
Simulator::readExBU(int32_t addr)
|
||||
{
|
||||
if (handleWasmFault(addr, 1))
|
||||
if (handleWasmSegFault(addr, 1))
|
||||
return UINT8_MAX;
|
||||
|
||||
SharedMem<uint8_t*> ptr = SharedMem<uint8_t*>::shared(reinterpret_cast<uint8_t*>(addr));
|
||||
|
@ -1924,7 +1961,7 @@ Simulator::readExBU(int32_t addr)
|
|||
int32_t
|
||||
Simulator::writeExB(int32_t addr, uint8_t value)
|
||||
{
|
||||
if (handleWasmFault(addr, 1))
|
||||
if (handleWasmSegFault(addr, 1))
|
||||
return -1;
|
||||
|
||||
SharedMem<uint8_t*> ptr = SharedMem<uint8_t*>::shared(reinterpret_cast<uint8_t*>(addr));
|
||||
|
@ -1939,7 +1976,7 @@ Simulator::writeExB(int32_t addr, uint8_t value)
|
|||
int8_t
|
||||
Simulator::readB(int32_t addr)
|
||||
{
|
||||
if (handleWasmFault(addr, 1))
|
||||
if (handleWasmSegFault(addr, 1))
|
||||
return -1;
|
||||
|
||||
int8_t* ptr = reinterpret_cast<int8_t*>(addr);
|
||||
|
@ -1949,7 +1986,7 @@ Simulator::readB(int32_t addr)
|
|||
void
|
||||
Simulator::writeB(int32_t addr, uint8_t value)
|
||||
{
|
||||
if (handleWasmFault(addr, 1))
|
||||
if (handleWasmSegFault(addr, 1))
|
||||
return;
|
||||
|
||||
uint8_t* ptr = reinterpret_cast<uint8_t*>(addr);
|
||||
|
@ -1959,7 +1996,7 @@ Simulator::writeB(int32_t addr, uint8_t value)
|
|||
void
|
||||
Simulator::writeB(int32_t addr, int8_t value)
|
||||
{
|
||||
if (handleWasmFault(addr, 1))
|
||||
if (handleWasmSegFault(addr, 1))
|
||||
return;
|
||||
|
||||
int8_t* ptr = reinterpret_cast<int8_t*>(addr);
|
||||
|
@ -1969,7 +2006,7 @@ Simulator::writeB(int32_t addr, int8_t value)
|
|||
int32_t*
|
||||
Simulator::readDW(int32_t addr)
|
||||
{
|
||||
if (handleWasmFault(addr, 8))
|
||||
if (handleWasmSegFault(addr, 8))
|
||||
return nullptr;
|
||||
|
||||
if ((addr & 3) == 0) {
|
||||
|
@ -1984,7 +2021,7 @@ Simulator::readDW(int32_t addr)
|
|||
void
|
||||
Simulator::writeDW(int32_t addr, int32_t value1, int32_t value2)
|
||||
{
|
||||
if (handleWasmFault(addr, 8))
|
||||
if (handleWasmSegFault(addr, 8))
|
||||
return;
|
||||
|
||||
if ((addr & 3) == 0) {
|
||||
|
@ -2004,7 +2041,7 @@ Simulator::readExDW(int32_t addr, int32_t* hibits)
|
|||
if (addr & 3)
|
||||
MOZ_CRASH("Unaligned exclusive read");
|
||||
|
||||
if (handleWasmFault(addr, 8))
|
||||
if (handleWasmSegFault(addr, 8))
|
||||
return -1;
|
||||
|
||||
SharedMem<uint64_t*> ptr = SharedMem<uint64_t*>::shared(reinterpret_cast<uint64_t*>(addr));
|
||||
|
@ -2025,7 +2062,7 @@ Simulator::writeExDW(int32_t addr, int32_t value1, int32_t value2)
|
|||
if (addr & 3)
|
||||
MOZ_CRASH("Unaligned exclusive write");
|
||||
|
||||
if (handleWasmFault(addr, 8))
|
||||
if (handleWasmSegFault(addr, 8))
|
||||
return -1;
|
||||
|
||||
SharedMem<uint64_t*> ptr = SharedMem<uint64_t*>::shared(reinterpret_cast<uint64_t*>(addr));
|
||||
|
@ -3551,6 +3588,12 @@ rotateBytes(uint32_t val, int32_t rotate)
|
|||
void
|
||||
Simulator::decodeType3(SimInstruction* instr)
|
||||
{
|
||||
if (MOZ_UNLIKELY(instr->isUDF())) {
|
||||
if (handleWasmIllFault())
|
||||
return;
|
||||
MOZ_CRASH("illegal instruction encountered");
|
||||
}
|
||||
|
||||
int rd = instr->rdValue();
|
||||
int rn = instr->rnValue();
|
||||
int32_t rn_val = get_register(rn);
|
||||
|
|
|
@ -296,7 +296,8 @@ class Simulator
|
|||
void startWasmInterrupt(JitActivation* act);
|
||||
|
||||
// Handle any wasm faults, returning true if the fault was handled.
|
||||
bool handleWasmFault(int32_t addr, unsigned numBytes);
|
||||
bool handleWasmSegFault(int32_t addr, unsigned numBytes);
|
||||
bool handleWasmIllFault();
|
||||
|
||||
// Read and write memory.
|
||||
inline uint8_t readBU(int32_t addr);
|
||||
|
|
|
@ -852,6 +852,12 @@ MacroAssembler::comment(const char* msg)
|
|||
// ========================================================================
|
||||
// wasm support
|
||||
|
||||
CodeOffset
|
||||
MacroAssembler::illegalInstruction()
|
||||
{
|
||||
MOZ_CRASH("NYI");
|
||||
}
|
||||
|
||||
void
|
||||
MacroAssembler::wasmTruncateDoubleToUInt32(FloatRegister input, Register output, Label* oolEntry)
|
||||
{
|
||||
|
|
|
@ -1863,6 +1863,14 @@ MacroAssembler::comment(const char* msg)
|
|||
Assembler::comment(msg);
|
||||
}
|
||||
|
||||
// ===============================================================
|
||||
// WebAssembly
|
||||
|
||||
CodeOffset
|
||||
MacroAssembler::illegalInstruction()
|
||||
{
|
||||
MOZ_CRASH("NYI");
|
||||
}
|
||||
|
||||
void
|
||||
MacroAssembler::wasmTruncateDoubleToInt32(FloatRegister input, Register output, Label* oolEntry)
|
||||
|
|
|
@ -871,6 +871,7 @@ class AssemblerShared
|
|||
{
|
||||
wasm::CallSiteVector callSites_;
|
||||
wasm::CallSiteTargetVector callSiteTargets_;
|
||||
wasm::TrapSiteVectorArray trapSites_;
|
||||
wasm::OldTrapSiteVector oldTrapSites_;
|
||||
wasm::OldTrapFarJumpVector oldTrapFarJumps_;
|
||||
wasm::CallFarJumpVector callFarJumps_;
|
||||
|
@ -930,6 +931,9 @@ class AssemblerShared
|
|||
enoughMemory_ &= callSites_.emplaceBack(desc, retAddr.offset());
|
||||
enoughMemory_ &= callSiteTargets_.emplaceBack(mozilla::Forward<Args>(args)...);
|
||||
}
|
||||
void append(wasm::Trap trap, wasm::TrapSite site) {
|
||||
enoughMemory_ &= trapSites_[trap].append(site);
|
||||
}
|
||||
void append(wasm::OldTrapSite trapSite) {
|
||||
enoughMemory_ &= oldTrapSites_.append(trapSite);
|
||||
}
|
||||
|
@ -966,6 +970,7 @@ class AssemblerShared
|
|||
|
||||
wasm::CallSiteVector& callSites() { return callSites_; }
|
||||
wasm::CallSiteTargetVector& callSiteTargets() { return callSiteTargets_; }
|
||||
wasm::TrapSiteVectorArray& trapSites() { return trapSites_; }
|
||||
wasm::OldTrapSiteVector& oldTrapSites() { return oldTrapSites_; }
|
||||
wasm::OldTrapFarJumpVector& oldTrapFarJumps() { return oldTrapFarJumps_; }
|
||||
wasm::CallFarJumpVector& callFarJumps() { return callFarJumps_; }
|
||||
|
|
|
@ -1101,6 +1101,11 @@ class AssemblerX86Shared : public AssemblerShared
|
|||
void breakpoint() {
|
||||
masm.int3();
|
||||
}
|
||||
CodeOffset ud2() {
|
||||
CodeOffset off(masm.currentOffset());
|
||||
masm.ud2();
|
||||
return off;
|
||||
}
|
||||
|
||||
static bool HasSSE2() { return CPUInfo::IsSSE2Present(); }
|
||||
static bool HasSSE3() { return CPUInfo::IsSSE3Present(); }
|
||||
|
|
|
@ -668,7 +668,14 @@ MacroAssembler::pushFakeReturnAddress(Register scratch)
|
|||
return retAddr;
|
||||
}
|
||||
|
||||
// wasm specific methods, used in both the wasm baseline compiler and ion.
|
||||
// ===============================================================
|
||||
// WebAssembly
|
||||
|
||||
CodeOffset
|
||||
MacroAssembler::illegalInstruction()
|
||||
{
|
||||
return ud2();
|
||||
}
|
||||
|
||||
// RAII class that generates the jumps to traps when it's destructed, to
|
||||
// prevent some code duplication in the outOfLineWasmTruncateXtoY methods.
|
||||
|
|
|
@ -20,9 +20,6 @@
|
|||
#include "vm/ErrorReporting.h"
|
||||
#include "vm/MallocProvider.h"
|
||||
#include "vm/Runtime.h"
|
||||
#ifdef XP_DARWIN
|
||||
# include "wasm/WasmSignalHandlers.h"
|
||||
#endif
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#pragma warning(push)
|
||||
|
|
|
@ -193,11 +193,13 @@ ArgumentsGetterImpl(JSContext* cx, const CallArgs& args)
|
|||
if (!argsobj)
|
||||
return false;
|
||||
|
||||
#ifndef JS_CODEGEN_NONE
|
||||
// Disabling compiling of this script in IonMonkey. IonMonkey doesn't
|
||||
// guarantee |f.arguments| can be fully recovered, so we try to mitigate
|
||||
// observing this behavior by detecting its use early.
|
||||
JSScript* script = iter.script();
|
||||
jit::ForbidCompilation(cx, script);
|
||||
#endif
|
||||
|
||||
args.rval().setObject(*argsobj);
|
||||
return true;
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
#include "mozilla/Attributes.h"
|
||||
#include "mozilla/DoublyLinkedList.h"
|
||||
#include "mozilla/LinkedList.h"
|
||||
#include "mozilla/MaybeOneOf.h"
|
||||
#include "mozilla/MemoryReporting.h"
|
||||
#include "mozilla/PodOperations.h"
|
||||
#include "mozilla/Scoped.h"
|
||||
|
@ -49,6 +50,7 @@
|
|||
#include "vm/Stack.h"
|
||||
#include "vm/Stopwatch.h"
|
||||
#include "vm/Symbol.h"
|
||||
#include "wasm/WasmSignalHandlers.h"
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#pragma warning(push)
|
||||
|
@ -1049,34 +1051,17 @@ struct JSRuntime : public js::MallocProvider<JSRuntime>
|
|||
public:
|
||||
js::RuntimeCaches& caches() { return caches_.ref(); }
|
||||
|
||||
private:
|
||||
// When wasm is interrupted, the pc at which we should return if the
|
||||
// interrupt hasn't stopped execution of the current running code. Since
|
||||
// this is used only by the interrupt handler and the latter is not
|
||||
// reentrant, this value can't be clobbered so there is at most one
|
||||
// resume PC at a time.
|
||||
js::ActiveThreadData<void*> wasmResumePC_;
|
||||
// When wasm traps or is interrupted, the signal handler records some data
|
||||
// for unwinding purposes. Wasm code can't interrupt or trap reentrantly.
|
||||
js::ActiveThreadData<
|
||||
mozilla::MaybeOneOf<js::wasm::TrapData, js::wasm::InterruptData>
|
||||
> wasmUnwindData;
|
||||
|
||||
// To ensure a consistent state of fp/pc, the unwound pc might be
|
||||
// different from the resumePC, especially at call boundaries.
|
||||
js::ActiveThreadData<void*> wasmUnwindPC_;
|
||||
|
||||
public:
|
||||
void startWasmInterrupt(void* resumePC, void* unwindPC) {
|
||||
MOZ_ASSERT(resumePC && unwindPC);
|
||||
wasmResumePC_ = resumePC;
|
||||
wasmUnwindPC_ = unwindPC;
|
||||
js::wasm::TrapData& wasmTrapData() {
|
||||
return wasmUnwindData.ref().ref<js::wasm::TrapData>();
|
||||
}
|
||||
void finishWasmInterrupt() {
|
||||
MOZ_ASSERT(wasmResumePC_ && wasmUnwindPC_);
|
||||
wasmResumePC_ = nullptr;
|
||||
wasmUnwindPC_ = nullptr;
|
||||
}
|
||||
void* wasmResumePC() const {
|
||||
return wasmResumePC_;
|
||||
}
|
||||
void* wasmUnwindPC() const {
|
||||
return wasmUnwindPC_;
|
||||
js::wasm::InterruptData& wasmInterruptData() {
|
||||
return wasmUnwindData.ref().ref<js::wasm::InterruptData>();
|
||||
}
|
||||
|
||||
public:
|
||||
|
|
|
@ -1552,6 +1552,7 @@ jit::JitActivation::~JitActivation()
|
|||
MOZ_ASSERT(!bailoutData_);
|
||||
|
||||
MOZ_ASSERT(!isWasmInterrupted());
|
||||
MOZ_ASSERT(!isWasmTrapping());
|
||||
|
||||
clearRematerializedFrames();
|
||||
js_delete(rematerializedFrames_);
|
||||
|
@ -1725,9 +1726,8 @@ jit::JitActivation::startWasmInterrupt(const JS::ProfilingFrameIterator::Registe
|
|||
MOZ_ASSERT(state.fp);
|
||||
|
||||
// Execution can only be interrupted in function code. Afterwards, control
|
||||
// flow does not reenter function code and thus there should be no
|
||||
// flow does not reenter function code and thus there can be no
|
||||
// interrupt-during-interrupt.
|
||||
MOZ_ASSERT(!isWasmInterrupted());
|
||||
|
||||
bool ignoredUnwound;
|
||||
wasm::UnwindState unwindState;
|
||||
|
@ -1736,7 +1736,7 @@ jit::JitActivation::startWasmInterrupt(const JS::ProfilingFrameIterator::Registe
|
|||
void* pc = unwindState.pc;
|
||||
MOZ_ASSERT(wasm::LookupCode(pc)->lookupRange(pc)->isFunction());
|
||||
|
||||
cx_->runtime()->startWasmInterrupt(state.pc, pc);
|
||||
cx_->runtime()->wasmUnwindData.ref().construct<wasm::InterruptData>(pc, state.pc);
|
||||
setWasmExitFP(unwindState.fp);
|
||||
|
||||
MOZ_ASSERT(compartment() == unwindState.fp->tls->instance->compartment());
|
||||
|
@ -1746,18 +1746,17 @@ jit::JitActivation::startWasmInterrupt(const JS::ProfilingFrameIterator::Registe
|
|||
void
|
||||
jit::JitActivation::finishWasmInterrupt()
|
||||
{
|
||||
MOZ_ASSERT(hasWasmExitFP());
|
||||
MOZ_ASSERT(isWasmInterrupted());
|
||||
|
||||
cx_->runtime()->finishWasmInterrupt();
|
||||
cx_->runtime()->wasmUnwindData.ref().destroy();
|
||||
packedExitFP_ = nullptr;
|
||||
}
|
||||
|
||||
bool
|
||||
jit::JitActivation::isWasmInterrupted() const
|
||||
{
|
||||
void* pc = cx_->runtime()->wasmUnwindPC();
|
||||
if (!pc)
|
||||
JSRuntime* rt = cx_->runtime();
|
||||
if (!rt->wasmUnwindData.ref().constructed<wasm::InterruptData>())
|
||||
return false;
|
||||
|
||||
Activation* act = cx_->activation();
|
||||
|
@ -1768,24 +1767,76 @@ jit::JitActivation::isWasmInterrupted() const
|
|||
return false;
|
||||
|
||||
DebugOnly<const wasm::Frame*> fp = wasmExitFP();
|
||||
MOZ_ASSERT(fp && fp->instance()->code().containsCodePC(pc));
|
||||
DebugOnly<void*> unwindPC = rt->wasmInterruptData().unwindPC;
|
||||
MOZ_ASSERT(fp->instance()->code().containsCodePC(unwindPC));
|
||||
return true;
|
||||
}
|
||||
|
||||
void*
|
||||
jit::JitActivation::wasmUnwindPC() const
|
||||
jit::JitActivation::wasmInterruptUnwindPC() const
|
||||
{
|
||||
MOZ_ASSERT(hasWasmExitFP());
|
||||
MOZ_ASSERT(isWasmInterrupted());
|
||||
return cx_->runtime()->wasmUnwindPC();
|
||||
return cx_->runtime()->wasmInterruptData().unwindPC;
|
||||
}
|
||||
|
||||
void*
|
||||
jit::JitActivation::wasmResumePC() const
|
||||
jit::JitActivation::wasmInterruptResumePC() const
|
||||
{
|
||||
MOZ_ASSERT(hasWasmExitFP());
|
||||
MOZ_ASSERT(isWasmInterrupted());
|
||||
return cx_->runtime()->wasmResumePC();
|
||||
return cx_->runtime()->wasmInterruptData().resumePC;
|
||||
}
|
||||
|
||||
void
|
||||
jit::JitActivation::startWasmTrap(wasm::Trap trap, uint32_t bytecodeOffset, void* pc, void* fp)
|
||||
{
|
||||
MOZ_ASSERT(pc);
|
||||
MOZ_ASSERT(fp);
|
||||
|
||||
cx_->runtime()->wasmUnwindData.ref().construct<wasm::TrapData>(pc, trap, bytecodeOffset);
|
||||
setWasmExitFP((wasm::Frame*)fp);
|
||||
}
|
||||
|
||||
void
|
||||
jit::JitActivation::finishWasmTrap()
|
||||
{
|
||||
MOZ_ASSERT(isWasmTrapping());
|
||||
|
||||
cx_->runtime()->wasmUnwindData.ref().destroy();
|
||||
packedExitFP_ = nullptr;
|
||||
}
|
||||
|
||||
bool
|
||||
jit::JitActivation::isWasmTrapping() const
|
||||
{
|
||||
JSRuntime* rt = cx_->runtime();
|
||||
if (!rt->wasmUnwindData.ref().constructed<wasm::TrapData>())
|
||||
return false;
|
||||
|
||||
Activation* act = cx_->activation();
|
||||
while (act && !act->hasWasmExitFP())
|
||||
act = act->prev();
|
||||
|
||||
if (act != this)
|
||||
return false;
|
||||
|
||||
DebugOnly<const wasm::Frame*> fp = wasmExitFP();
|
||||
DebugOnly<void*> unwindPC = rt->wasmTrapData().pc;
|
||||
MOZ_ASSERT(fp->instance()->code().containsCodePC(unwindPC));
|
||||
return true;
|
||||
}
|
||||
|
||||
void*
|
||||
jit::JitActivation::wasmTrapPC() const
|
||||
{
|
||||
MOZ_ASSERT(isWasmTrapping());
|
||||
return cx_->runtime()->wasmTrapData().pc;
|
||||
}
|
||||
|
||||
uint32_t
|
||||
jit::JitActivation::wasmTrapBytecodeOffset() const
|
||||
{
|
||||
MOZ_ASSERT(isWasmTrapping());
|
||||
return cx_->runtime()->wasmTrapData().bytecodeOffset;
|
||||
}
|
||||
|
||||
InterpreterFrameIterator&
|
||||
|
|
|
@ -1666,11 +1666,18 @@ class JitActivation : public Activation
|
|||
// Interrupts are started from the interrupt signal handler (or the ARM
|
||||
// simulator) and cleared by WasmHandleExecutionInterrupt or WasmHandleThrow
|
||||
// when the interrupt is handled.
|
||||
|
||||
void startWasmInterrupt(const JS::ProfilingFrameIterator::RegisterState& state);
|
||||
void finishWasmInterrupt();
|
||||
bool isWasmInterrupted() const;
|
||||
void* wasmUnwindPC() const;
|
||||
void* wasmResumePC() const;
|
||||
void* wasmInterruptUnwindPC() const;
|
||||
void* wasmInterruptResumePC() const;
|
||||
|
||||
void startWasmTrap(wasm::Trap trap, uint32_t bytecodeOffset, void* pc, void* fp);
|
||||
void finishWasmTrap();
|
||||
bool isWasmTrapping() const;
|
||||
void* wasmTrapPC() const;
|
||||
uint32_t wasmTrapBytecodeOffset() const;
|
||||
};
|
||||
|
||||
// A filtering of the ActivationIterator to only stop at JitActivations.
|
||||
|
|
|
@ -3807,15 +3807,6 @@ class BaseCompiler final : public BaseCompilerInterface
|
|||
#endif
|
||||
}
|
||||
|
||||
void unreachableTrap()
|
||||
{
|
||||
masm.jump(oldTrap(Trap::Unreachable));
|
||||
#ifdef DEBUG
|
||||
masm.breakpoint();
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
MOZ_MUST_USE bool
|
||||
supportsRoundInstruction(RoundingMode mode)
|
||||
{
|
||||
|
@ -4905,6 +4896,10 @@ class BaseCompiler final : public BaseCompilerInterface
|
|||
return iter_.bytecodeOffset();
|
||||
}
|
||||
|
||||
void trap(Trap t) const {
|
||||
masm.wasmTrap(t, bytecodeOffset());
|
||||
}
|
||||
|
||||
OldTrapDesc oldTrap(Trap t) const {
|
||||
// Use masm.framePushed() because the value needed by the trap machinery
|
||||
// is the size of the frame overall, not the height of the stack area of
|
||||
|
@ -8533,7 +8528,7 @@ BaseCompiler::emitBody()
|
|||
case uint16_t(Op::Unreachable):
|
||||
CHECK(iter_.readUnreachable());
|
||||
if (!deadCode_) {
|
||||
unreachableTrap();
|
||||
trap(Trap::Unreachable);
|
||||
deadCode_ = true;
|
||||
}
|
||||
NEXT();
|
||||
|
|
|
@ -87,7 +87,7 @@ WasmHandleExecutionInterrupt()
|
|||
|
||||
// If CheckForInterrupt succeeded, then execution can proceed and the
|
||||
// interrupt is over.
|
||||
void* resumePC = activation->wasmResumePC();
|
||||
void* resumePC = activation->wasmInterruptResumePC();
|
||||
activation->finishWasmInterrupt();
|
||||
return resumePC;
|
||||
}
|
||||
|
@ -225,6 +225,7 @@ wasm::HandleThrow(JSContext* cx, WasmFrameIter& iter)
|
|||
}
|
||||
|
||||
MOZ_ASSERT(!cx->activation()->asJit()->isWasmInterrupted(), "unwinding clears the interrupt");
|
||||
MOZ_ASSERT(!cx->activation()->asJit()->isWasmTrapping(), "unwinding clears the trapping state");
|
||||
|
||||
return iter.unwoundAddressOfReturnAddress();
|
||||
}
|
||||
|
@ -288,6 +289,13 @@ WasmOldReportTrap(int32_t trapIndex)
|
|||
JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, errorNumber);
|
||||
}
|
||||
|
||||
static void
|
||||
WasmReportTrap()
|
||||
{
|
||||
Trap trap = TlsContext.get()->runtime()->wasmTrapData().trap;
|
||||
WasmOldReportTrap(int32_t(trap));
|
||||
}
|
||||
|
||||
static void
|
||||
WasmReportOutOfBounds()
|
||||
{
|
||||
|
@ -444,6 +452,9 @@ AddressOf(SymbolicAddress imm, ABIFunctionType* abiType)
|
|||
case SymbolicAddress::HandleThrow:
|
||||
*abiType = Args_General0;
|
||||
return FuncCast(WasmHandleThrow, *abiType);
|
||||
case SymbolicAddress::ReportTrap:
|
||||
*abiType = Args_General0;
|
||||
return FuncCast(WasmReportTrap, *abiType);
|
||||
case SymbolicAddress::OldReportTrap:
|
||||
*abiType = Args_General1;
|
||||
return FuncCast(WasmOldReportTrap, *abiType);
|
||||
|
@ -600,6 +611,7 @@ wasm::NeedsBuiltinThunk(SymbolicAddress sym)
|
|||
case SymbolicAddress::HandleExecutionInterrupt: // GenerateInterruptExit
|
||||
case SymbolicAddress::HandleDebugTrap: // GenerateDebugTrapStub
|
||||
case SymbolicAddress::HandleThrow: // GenerateThrowStub
|
||||
case SymbolicAddress::ReportTrap: // GenerateTrapExit
|
||||
case SymbolicAddress::OldReportTrap: // GenerateOldTrapExit
|
||||
case SymbolicAddress::ReportOutOfBounds: // GenerateOutOfBoundsExit
|
||||
case SymbolicAddress::ReportUnalignedAccess: // GeneratesUnalignedExit
|
||||
|
@ -896,6 +908,7 @@ wasm::EnsureBuiltinThunksInitialized()
|
|||
MOZ_ASSERT(masm.callSites().empty());
|
||||
MOZ_ASSERT(masm.callSiteTargets().empty());
|
||||
MOZ_ASSERT(masm.callFarJumps().empty());
|
||||
MOZ_ASSERT(masm.trapSites().empty());
|
||||
MOZ_ASSERT(masm.oldTrapSites().empty());
|
||||
MOZ_ASSERT(masm.oldTrapFarJumps().empty());
|
||||
MOZ_ASSERT(masm.callFarJumps().empty());
|
||||
|
|
|
@ -242,9 +242,6 @@ CodeSegment::create(Tier tier,
|
|||
const Metadata& metadata)
|
||||
{
|
||||
// These should always exist and should never be first in the code segment.
|
||||
MOZ_ASSERT(linkData.interruptOffset != 0);
|
||||
MOZ_ASSERT(linkData.outOfBoundsOffset != 0);
|
||||
MOZ_ASSERT(linkData.unalignedAccessOffset != 0);
|
||||
|
||||
auto cs = js::MakeUnique<CodeSegment>();
|
||||
if (!cs)
|
||||
|
@ -268,6 +265,7 @@ CodeSegment::initialize(Tier tier,
|
|||
MOZ_ASSERT(linkData.interruptOffset);
|
||||
MOZ_ASSERT(linkData.outOfBoundsOffset);
|
||||
MOZ_ASSERT(linkData.unalignedAccessOffset);
|
||||
MOZ_ASSERT(linkData.trapOffset);
|
||||
|
||||
tier_ = tier;
|
||||
bytes_ = Move(codeBytes);
|
||||
|
@ -275,6 +273,7 @@ CodeSegment::initialize(Tier tier,
|
|||
interruptCode_ = bytes_.get() + linkData.interruptOffset;
|
||||
outOfBoundsCode_ = bytes_.get() + linkData.outOfBoundsOffset;
|
||||
unalignedAccessCode_ = bytes_.get() + linkData.unalignedAccessOffset;
|
||||
trapCode_ = bytes_.get() + linkData.trapOffset;
|
||||
|
||||
if (!StaticallyLink(*this, linkData))
|
||||
return false;
|
||||
|
@ -459,6 +458,7 @@ MetadataTier::serializedSize() const
|
|||
return SerializedPodVectorSize(memoryAccesses) +
|
||||
SerializedPodVectorSize(codeRanges) +
|
||||
SerializedPodVectorSize(callSites) +
|
||||
trapSites.serializedSize() +
|
||||
SerializedVectorSize(funcImports) +
|
||||
SerializedVectorSize(funcExports);
|
||||
}
|
||||
|
@ -469,6 +469,7 @@ MetadataTier::sizeOfExcludingThis(MallocSizeOf mallocSizeOf) const
|
|||
return memoryAccesses.sizeOfExcludingThis(mallocSizeOf) +
|
||||
codeRanges.sizeOfExcludingThis(mallocSizeOf) +
|
||||
callSites.sizeOfExcludingThis(mallocSizeOf) +
|
||||
trapSites.sizeOfExcludingThis(mallocSizeOf) +
|
||||
SizeOfVectorExcludingThis(funcImports, mallocSizeOf) +
|
||||
SizeOfVectorExcludingThis(funcExports, mallocSizeOf);
|
||||
}
|
||||
|
@ -480,6 +481,7 @@ MetadataTier::serialize(uint8_t* cursor) const
|
|||
cursor = SerializePodVector(cursor, memoryAccesses);
|
||||
cursor = SerializePodVector(cursor, codeRanges);
|
||||
cursor = SerializePodVector(cursor, callSites);
|
||||
cursor = trapSites.serialize(cursor);
|
||||
cursor = SerializeVector(cursor, funcImports);
|
||||
cursor = SerializeVector(cursor, funcExports);
|
||||
return cursor;
|
||||
|
@ -491,6 +493,7 @@ MetadataTier::deserialize(const uint8_t* cursor)
|
|||
(cursor = DeserializePodVector(cursor, &memoryAccesses)) &&
|
||||
(cursor = DeserializePodVector(cursor, &codeRanges)) &&
|
||||
(cursor = DeserializePodVector(cursor, &callSites)) &&
|
||||
(cursor = trapSites.deserialize(cursor)) &&
|
||||
(cursor = DeserializeVector(cursor, &funcImports)) &&
|
||||
(cursor = DeserializeVector(cursor, &funcExports));
|
||||
debugTrapFarJumpOffsets.clear();
|
||||
|
@ -750,7 +753,7 @@ Code::segment(Tier tier) const
|
|||
bool
|
||||
Code::containsCodePC(const void* pc) const
|
||||
{
|
||||
for (auto t : tiers()) {
|
||||
for (Tier t : tiers()) {
|
||||
const CodeSegment& cs = segment(t);
|
||||
if (cs.containsCodePC(pc))
|
||||
return true;
|
||||
|
@ -770,7 +773,7 @@ struct CallSiteRetAddrOffset
|
|||
const CallSite*
|
||||
Code::lookupCallSite(void* returnAddress) const
|
||||
{
|
||||
for (auto t : tiers()) {
|
||||
for (Tier t : tiers()) {
|
||||
uint32_t target = ((uint8_t*)returnAddress) - segment(t).base();
|
||||
size_t lowerBound = 0;
|
||||
size_t upperBound = metadata(t).callSites.length();
|
||||
|
@ -787,7 +790,7 @@ Code::lookupCallSite(void* returnAddress) const
|
|||
const CodeRange*
|
||||
Code::lookupRange(void* pc) const
|
||||
{
|
||||
for (auto t : tiers()) {
|
||||
for (Tier t : tiers()) {
|
||||
CodeRange::OffsetInCode target((uint8_t*)pc - segment(t).base());
|
||||
const CodeRange* result = LookupInSorted(metadata(t).codeRanges, target);
|
||||
if (result)
|
||||
|
@ -809,7 +812,7 @@ struct MemoryAccessOffset
|
|||
const MemoryAccess*
|
||||
Code::lookupMemoryAccess(void* pc) const
|
||||
{
|
||||
for (auto t : tiers()) {
|
||||
for (Tier t : tiers()) {
|
||||
const MemoryAccessVector& memoryAccesses = metadata(t).memoryAccesses;
|
||||
|
||||
uint32_t target = ((uint8_t*)pc) - segment(t).base();
|
||||
|
@ -827,6 +830,40 @@ Code::lookupMemoryAccess(void* pc) const
|
|||
return nullptr;
|
||||
}
|
||||
|
||||
struct TrapSitePCOffset
|
||||
{
|
||||
const TrapSiteVector& trapSites;
|
||||
explicit TrapSitePCOffset(const TrapSiteVector& trapSites) : trapSites(trapSites) {}
|
||||
uint32_t operator[](size_t index) const {
|
||||
return trapSites[index].pcOffset;
|
||||
}
|
||||
};
|
||||
|
||||
bool
|
||||
Code::lookupTrap(void* pc, Trap* trapOut, BytecodeOffset* bytecode) const
|
||||
{
|
||||
for (Tier t : tiers()) {
|
||||
const TrapSiteVectorArray& trapSitesArray = metadata(t).trapSites;
|
||||
for (Trap trap : MakeEnumeratedRange(Trap::Limit)) {
|
||||
const TrapSiteVector& trapSites = trapSitesArray[trap];
|
||||
|
||||
uint32_t target = ((uint8_t*)pc) - segment(t).base();
|
||||
size_t lowerBound = 0;
|
||||
size_t upperBound = trapSites.length();
|
||||
|
||||
size_t match;
|
||||
if (BinarySearch(TrapSitePCOffset(trapSites), lowerBound, upperBound, target, &match)) {
|
||||
MOZ_ASSERT(segment(t).containsCodePC(pc));
|
||||
*trapOut = trap;
|
||||
*bytecode = trapSites[match].bytecode;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// When enabled, generate profiling labels for every name in funcNames_ that is
|
||||
// the name of some Function CodeRange. This involves malloc() so do it now
|
||||
// since, once we start sampling, we'll be in a signal-handing context where we
|
||||
|
|
|
@ -81,6 +81,7 @@ class CodeSegment
|
|||
uint8_t* interruptCode_;
|
||||
uint8_t* outOfBoundsCode_;
|
||||
uint8_t* unalignedAccessCode_;
|
||||
uint8_t* trapCode_;
|
||||
|
||||
bool registered_;
|
||||
|
||||
|
@ -108,6 +109,7 @@ class CodeSegment
|
|||
interruptCode_(nullptr),
|
||||
outOfBoundsCode_(nullptr),
|
||||
unalignedAccessCode_(nullptr),
|
||||
trapCode_(nullptr),
|
||||
registered_(false)
|
||||
{}
|
||||
|
||||
|
@ -139,6 +141,7 @@ class CodeSegment
|
|||
uint8_t* interruptCode() const { return interruptCode_; }
|
||||
uint8_t* outOfBoundsCode() const { return outOfBoundsCode_; }
|
||||
uint8_t* unalignedAccessCode() const { return unalignedAccessCode_; }
|
||||
uint8_t* trapCode() const { return trapCode_; }
|
||||
|
||||
bool containsCodePC(const void* pc) const {
|
||||
return pc >= base() && pc < (base() + length_);
|
||||
|
@ -351,6 +354,7 @@ struct MetadataTier
|
|||
MemoryAccessVector memoryAccesses;
|
||||
CodeRangeVector codeRanges;
|
||||
CallSiteVector callSites;
|
||||
TrapSiteVectorArray trapSites;
|
||||
FuncImportVector funcImports;
|
||||
FuncExportVector funcExports;
|
||||
|
||||
|
@ -488,6 +492,7 @@ class Code : public ShareableBase<Code>
|
|||
const CodeRange* lookupRange(void* pc) const;
|
||||
const MemoryAccess* lookupMemoryAccess(void* pc) const;
|
||||
bool containsCodePC(const void* pc) const;
|
||||
bool lookupTrap(void* pc, Trap* trap, BytecodeOffset* bytecode) const;
|
||||
|
||||
// To save memory, profilingLabels_ are generated lazily when profiling mode
|
||||
// is enabled.
|
||||
|
|
|
@ -42,6 +42,23 @@ WasmFrameIter::WasmFrameIter(JitActivation* activation, wasm::Frame* fp)
|
|||
{
|
||||
MOZ_ASSERT(fp_);
|
||||
|
||||
// When the stack is captured during a trap (viz., to create the .stack
|
||||
// for an Error object), use the pc/bytecode information captured by the
|
||||
// signal handler in the runtime.
|
||||
|
||||
if (activation->isWasmTrapping()) {
|
||||
code_ = &fp_->tls->instance->code();
|
||||
MOZ_ASSERT(code_ == LookupCode(activation->wasmTrapPC()));
|
||||
|
||||
codeRange_ = code_->lookupRange(activation->wasmTrapPC());
|
||||
MOZ_ASSERT(codeRange_->kind() == CodeRange::Function);
|
||||
|
||||
lineOrBytecode_ = activation->wasmTrapBytecodeOffset();
|
||||
|
||||
MOZ_ASSERT(!done());
|
||||
return;
|
||||
}
|
||||
|
||||
// When asynchronously interrupted, exitFP is set to the interrupted frame
|
||||
// itself and so we do not want to skip it. Instead, we can recover the
|
||||
// Code and CodeRange from the JitActivation, which are set when control
|
||||
|
@ -52,9 +69,9 @@ WasmFrameIter::WasmFrameIter(JitActivation* activation, wasm::Frame* fp)
|
|||
|
||||
if (activation->isWasmInterrupted()) {
|
||||
code_ = &fp_->tls->instance->code();
|
||||
MOZ_ASSERT(code_ == LookupCode(activation->wasmUnwindPC()));
|
||||
MOZ_ASSERT(code_ == LookupCode(activation->wasmInterruptUnwindPC()));
|
||||
|
||||
codeRange_ = code_->lookupRange(activation->wasmUnwindPC());
|
||||
codeRange_ = code_->lookupRange(activation->wasmInterruptUnwindPC());
|
||||
MOZ_ASSERT(codeRange_->kind() == CodeRange::Function);
|
||||
|
||||
lineOrBytecode_ = codeRange_->funcLineOrBytecode();
|
||||
|
@ -98,6 +115,8 @@ WasmFrameIter::operator++()
|
|||
if (unwind_ == Unwind::True) {
|
||||
if (activation_->isWasmInterrupted())
|
||||
activation_->finishWasmInterrupt();
|
||||
else if (activation_->isWasmTrapping())
|
||||
activation_->finishWasmTrap();
|
||||
activation_->setWasmExitFP(fp_);
|
||||
}
|
||||
|
||||
|
@ -624,6 +643,7 @@ ProfilingFrameIterator::initFromExitFP(const Frame* fp)
|
|||
case CodeRange::ImportJitExit:
|
||||
case CodeRange::ImportInterpExit:
|
||||
case CodeRange::BuiltinThunk:
|
||||
case CodeRange::TrapExit:
|
||||
case CodeRange::OldTrapExit:
|
||||
case CodeRange::DebugTrap:
|
||||
case CodeRange::OutOfBoundsExit:
|
||||
|
@ -794,6 +814,7 @@ js::wasm::StartUnwinding(const RegisterState& registers, UnwindState* unwindStat
|
|||
break;
|
||||
}
|
||||
break;
|
||||
case CodeRange::TrapExit:
|
||||
case CodeRange::OutOfBoundsExit:
|
||||
case CodeRange::UnalignedExit:
|
||||
// These code stubs execute after the prologue/epilogue have completed
|
||||
|
@ -902,6 +923,7 @@ ProfilingFrameIterator::operator++()
|
|||
case CodeRange::ImportJitExit:
|
||||
case CodeRange::ImportInterpExit:
|
||||
case CodeRange::BuiltinThunk:
|
||||
case CodeRange::TrapExit:
|
||||
case CodeRange::OldTrapExit:
|
||||
case CodeRange::DebugTrap:
|
||||
case CodeRange::OutOfBoundsExit:
|
||||
|
@ -930,6 +952,7 @@ ThunkedNativeToDescription(SymbolicAddress func)
|
|||
case SymbolicAddress::HandleExecutionInterrupt:
|
||||
case SymbolicAddress::HandleDebugTrap:
|
||||
case SymbolicAddress::HandleThrow:
|
||||
case SymbolicAddress::ReportTrap:
|
||||
case SymbolicAddress::OldReportTrap:
|
||||
case SymbolicAddress::ReportOutOfBounds:
|
||||
case SymbolicAddress::ReportUnalignedAccess:
|
||||
|
@ -1060,6 +1083,7 @@ ProfilingFrameIterator::label() const
|
|||
case CodeRange::ImportJitExit: return importJitDescription;
|
||||
case CodeRange::BuiltinThunk: return builtinNativeDescription;
|
||||
case CodeRange::ImportInterpExit: return importInterpDescription;
|
||||
case CodeRange::TrapExit: return trapDescription;
|
||||
case CodeRange::OldTrapExit: return trapDescription;
|
||||
case CodeRange::DebugTrap: return debugTrapDescription;
|
||||
case CodeRange::OutOfBoundsExit: return "out-of-bounds stub (in wasm)";
|
||||
|
|
|
@ -47,6 +47,7 @@ CompiledCode::swap(MacroAssembler& masm)
|
|||
|
||||
callSites.swap(masm.callSites());
|
||||
callSiteTargets.swap(masm.callSiteTargets());
|
||||
trapSites.swap(masm.trapSites());
|
||||
oldTrapSites.swap(masm.oldTrapSites());
|
||||
callFarJumps.swap(masm.callFarJumps());
|
||||
oldTrapFarJumps.swap(masm.oldTrapFarJumps());
|
||||
|
@ -380,7 +381,7 @@ InRange(uint32_t caller, uint32_t callee)
|
|||
}
|
||||
|
||||
typedef HashMap<uint32_t, uint32_t, DefaultHasher<uint32_t>, SystemAllocPolicy> OffsetMap;
|
||||
typedef EnumeratedArray<Trap, Trap::Limit, Maybe<uint32_t>> TrapOffsetArray;
|
||||
typedef EnumeratedArray<Trap, Trap::Limit, Maybe<uint32_t>> TrapMaybeOffsetArray;
|
||||
|
||||
bool
|
||||
ModuleGenerator::linkCallSites()
|
||||
|
@ -399,7 +400,7 @@ ModuleGenerator::linkCallSites()
|
|||
if (!existingCallFarJumps.init())
|
||||
return false;
|
||||
|
||||
TrapOffsetArray existingTrapFarJumps;
|
||||
TrapMaybeOffsetArray existingTrapFarJumps;
|
||||
|
||||
for (; lastPatchedCallSite_ < metadataTier_->callSites.length(); lastPatchedCallSite_++) {
|
||||
const CallSite& callSite = metadataTier_->callSites[lastPatchedCallSite_];
|
||||
|
@ -523,6 +524,10 @@ ModuleGenerator::noteCodeRange(uint32_t codeRangeIndex, const CodeRange& codeRan
|
|||
MOZ_ASSERT(!linkDataTier_->interruptOffset);
|
||||
linkDataTier_->interruptOffset = codeRange.begin();
|
||||
break;
|
||||
case CodeRange::TrapExit:
|
||||
MOZ_ASSERT(!linkDataTier_->trapOffset);
|
||||
linkDataTier_->trapOffset = codeRange.begin();
|
||||
break;
|
||||
case CodeRange::Throw:
|
||||
// Jumped to by other stubs, so nothing to do.
|
||||
break;
|
||||
|
@ -580,6 +585,12 @@ ModuleGenerator::linkCompiledCode(const CompiledCode& code)
|
|||
if (!callSiteTargets_.appendAll(code.callSiteTargets))
|
||||
return false;
|
||||
|
||||
for (Trap trap : MakeEnumeratedRange(Trap::Limit)) {
|
||||
auto trapSiteOp = [=](uint32_t, TrapSite* ts) { ts->offsetBy(offsetInModule); };
|
||||
if (!AppendForEach(&metadataTier_->trapSites[trap], code.trapSites[trap], trapSiteOp))
|
||||
return false;
|
||||
}
|
||||
|
||||
MOZ_ASSERT(code.oldTrapSites.empty());
|
||||
|
||||
auto trapFarJumpOp = [=](uint32_t, OldTrapFarJump* tfj) { tfj->offsetBy(offsetInModule); };
|
||||
|
@ -798,6 +809,7 @@ ModuleGenerator::finishCode()
|
|||
|
||||
MOZ_ASSERT(masm_.callSites().empty());
|
||||
MOZ_ASSERT(masm_.callSiteTargets().empty());
|
||||
MOZ_ASSERT(masm_.trapSites().empty());
|
||||
MOZ_ASSERT(masm_.oldTrapSites().empty());
|
||||
MOZ_ASSERT(masm_.oldTrapFarJumps().empty());
|
||||
MOZ_ASSERT(masm_.callFarJumps().empty());
|
||||
|
@ -812,19 +824,32 @@ ModuleGenerator::finishCode()
|
|||
bool
|
||||
ModuleGenerator::finishMetadata(const ShareableBytes& bytecode)
|
||||
{
|
||||
// Assert all sorted metadata is sorted.
|
||||
#ifdef DEBUG
|
||||
// Assert CodeRanges are sorted.
|
||||
uint32_t lastEnd = 0;
|
||||
uint32_t last = 0;
|
||||
for (const CodeRange& codeRange : metadataTier_->codeRanges) {
|
||||
MOZ_ASSERT(codeRange.begin() >= lastEnd);
|
||||
lastEnd = codeRange.end();
|
||||
MOZ_ASSERT(codeRange.begin() >= last);
|
||||
last = codeRange.end();
|
||||
}
|
||||
|
||||
// Assert debugTrapFarJumpOffsets are sorted.
|
||||
uint32_t lastOffset = 0;
|
||||
last = 0;
|
||||
for (const CallSite& callSite : metadataTier_->callSites) {
|
||||
MOZ_ASSERT(callSite.returnAddressOffset() >= last);
|
||||
last = callSite.returnAddressOffset();
|
||||
}
|
||||
|
||||
for (Trap trap : MakeEnumeratedRange(Trap::Limit)) {
|
||||
last = 0;
|
||||
for (const TrapSite& trapSite : metadataTier_->trapSites[trap]) {
|
||||
MOZ_ASSERT(trapSite.pcOffset >= last);
|
||||
last = trapSite.pcOffset;
|
||||
}
|
||||
}
|
||||
|
||||
last = 0;
|
||||
for (uint32_t debugTrapFarJumpOffset : metadataTier_->debugTrapFarJumpOffsets) {
|
||||
MOZ_ASSERT(debugTrapFarJumpOffset >= lastOffset);
|
||||
lastOffset = debugTrapFarJumpOffset;
|
||||
MOZ_ASSERT(debugTrapFarJumpOffset >= last);
|
||||
last = debugTrapFarJumpOffset;
|
||||
}
|
||||
#endif
|
||||
|
||||
|
@ -849,7 +874,7 @@ ModuleGenerator::finishMetadata(const ShareableBytes& bytecode)
|
|||
|
||||
metadataTier_->memoryAccesses.podResizeToFit();
|
||||
metadataTier_->codeRanges.podResizeToFit();
|
||||
metadataTier_->callSites.podResizeToFit();
|
||||
metadataTier_->trapSites.podResizeToFit();
|
||||
metadataTier_->debugTrapFarJumpOffsets.podResizeToFit();
|
||||
metadataTier_->debugFuncToCodeRange.podResizeToFit();
|
||||
|
||||
|
|
|
@ -64,6 +64,7 @@ struct CompiledCode
|
|||
CodeRangeVector codeRanges;
|
||||
CallSiteVector callSites;
|
||||
CallSiteTargetVector callSiteTargets;
|
||||
TrapSiteVectorArray trapSites;
|
||||
OldTrapSiteVector oldTrapSites;
|
||||
OldTrapFarJumpVector oldTrapFarJumps;
|
||||
CallFarJumpVector callFarJumps;
|
||||
|
@ -78,6 +79,7 @@ struct CompiledCode
|
|||
codeRanges.clear();
|
||||
callSites.clear();
|
||||
callSiteTargets.clear();
|
||||
trapSites.clear();
|
||||
oldTrapSites.clear();
|
||||
oldTrapFarJumps.clear();
|
||||
callFarJumps.clear();
|
||||
|
@ -92,6 +94,7 @@ struct CompiledCode
|
|||
codeRanges.empty() &&
|
||||
callSites.empty() &&
|
||||
callSiteTargets.empty() &&
|
||||
trapSites.empty() &&
|
||||
oldTrapSites.empty() &&
|
||||
oldTrapFarJumps.empty() &&
|
||||
callFarJumps.empty() &&
|
||||
|
|
|
@ -45,6 +45,7 @@ struct LinkDataTierCacheablePod
|
|||
uint32_t interruptOffset;
|
||||
uint32_t outOfBoundsOffset;
|
||||
uint32_t unalignedAccessOffset;
|
||||
uint32_t trapOffset;
|
||||
|
||||
LinkDataTierCacheablePod() { mozilla::PodZero(this); }
|
||||
};
|
||||
|
|
|
@ -421,27 +421,31 @@ struct macos_arm_context {
|
|||
# define LR_sig(p) R31_sig(p)
|
||||
#endif
|
||||
|
||||
#if defined(FP_sig) && defined(SP_sig) && defined(SP_sig)
|
||||
# define KNOWS_MACHINE_STATE
|
||||
#endif
|
||||
|
||||
static uint8_t**
|
||||
ContextToPC(CONTEXT* context)
|
||||
{
|
||||
#ifdef JS_CODEGEN_NONE
|
||||
MOZ_CRASH();
|
||||
#else
|
||||
#ifdef KNOWS_MACHINE_STATE
|
||||
return reinterpret_cast<uint8_t**>(&PC_sig(context));
|
||||
#else
|
||||
MOZ_CRASH();
|
||||
#endif
|
||||
}
|
||||
|
||||
static uint8_t*
|
||||
ContextToFP(CONTEXT* context)
|
||||
{
|
||||
#ifdef JS_CODEGEN_NONE
|
||||
MOZ_CRASH();
|
||||
#else
|
||||
#ifdef KNOWS_MACHINE_STATE
|
||||
return reinterpret_cast<uint8_t*>(FP_sig(context));
|
||||
#else
|
||||
MOZ_CRASH();
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifndef JS_CODEGEN_NONE
|
||||
#ifdef KNOWS_MACHINE_STATE
|
||||
static uint8_t*
|
||||
ContextToSP(CONTEXT* context)
|
||||
{
|
||||
|
@ -455,7 +459,7 @@ ContextToLR(CONTEXT* context)
|
|||
return reinterpret_cast<uint8_t*>(LR_sig(context));
|
||||
}
|
||||
# endif
|
||||
#endif // JS_CODEGEN_NONE
|
||||
#endif // KNOWS_MACHINE_STATE
|
||||
|
||||
#if defined(XP_DARWIN)
|
||||
|
||||
|
@ -532,9 +536,7 @@ ToRegisterState(EMULATOR_CONTEXT* context)
|
|||
static JS::ProfilingFrameIterator::RegisterState
|
||||
ToRegisterState(CONTEXT* context)
|
||||
{
|
||||
#ifdef JS_CODEGEN_NONE
|
||||
MOZ_CRASH();
|
||||
#else
|
||||
#ifdef KNOWS_MACHINE_STATE
|
||||
JS::ProfilingFrameIterator::RegisterState state;
|
||||
state.fp = ContextToFP(context);
|
||||
state.pc = *ContextToPC(context);
|
||||
|
@ -543,6 +545,8 @@ ToRegisterState(CONTEXT* context)
|
|||
state.lr = ContextToLR(context);
|
||||
# endif
|
||||
return state;
|
||||
#else
|
||||
MOZ_CRASH();
|
||||
#endif
|
||||
}
|
||||
|
||||
|
@ -975,15 +979,15 @@ HandleFault(PEXCEPTION_POINTERS exception)
|
|||
EXCEPTION_RECORD* record = exception->ExceptionRecord;
|
||||
CONTEXT* context = exception->ContextRecord;
|
||||
|
||||
if (record->ExceptionCode != EXCEPTION_ACCESS_VIOLATION)
|
||||
if (record->ExceptionCode != EXCEPTION_ACCESS_VIOLATION &&
|
||||
record->ExceptionCode != EXCEPTION_ILLEGAL_INSTRUCTION)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
uint8_t** ppc = ContextToPC(context);
|
||||
uint8_t* pc = *ppc;
|
||||
|
||||
if (record->NumberParameters < 2)
|
||||
return false;
|
||||
|
||||
const CodeSegment* codeSegment = LookupCodeSegment(pc);
|
||||
if (!codeSegment)
|
||||
return false;
|
||||
|
@ -994,7 +998,7 @@ HandleFault(PEXCEPTION_POINTERS exception)
|
|||
const Instance* instance = LookupFaultingInstance(*codeSegment, pc, ContextToFP(context));
|
||||
if (!instance) {
|
||||
// On Windows, it is possible for InterruptRunningJitCode to execute
|
||||
// between a faulting heap access and the handling of the fault due
|
||||
// between a faulting instruction and the handling of the fault due
|
||||
// to InterruptRunningJitCode's use of SuspendThread. When this happens,
|
||||
// after ResumeThread, the exception handler is called with pc equal to
|
||||
// CodeSegment.interrupt, which is logically wrong. The Right Thing would
|
||||
|
@ -1004,9 +1008,36 @@ HandleFault(PEXCEPTION_POINTERS exception)
|
|||
// retrigger after the interrupt jumps back to resumePC).
|
||||
return activation->isWasmInterrupted() &&
|
||||
pc == codeSegment->interruptCode() &&
|
||||
codeSegment->containsCodePC(activation->wasmResumePC());
|
||||
codeSegment->containsCodePC(activation->wasmInterruptResumePC());
|
||||
}
|
||||
|
||||
// In the same race-with-interrupt situation above, it's *also* possible
|
||||
// that the reported 'pc' is the pre-interrupt pc, not post-interrupt
|
||||
// codeSegment->interruptCode (this may be windows-version-specific). In
|
||||
// this case, lookupTrap()/lookupMemoryAccess() will all succeed causing the
|
||||
// pc to be redirected *again* (to a trap stub), leading to the interrupt
|
||||
// stub never being called. Since the goal of the async interrupt is to break
|
||||
// out iloops and trapping does just that, this is fine, we just clear the
|
||||
// "interrupted" state.
|
||||
if (activation->isWasmInterrupted()) {
|
||||
MOZ_ASSERT(activation->wasmInterruptResumePC() == pc);
|
||||
activation->finishWasmInterrupt();
|
||||
}
|
||||
|
||||
if (record->ExceptionCode == EXCEPTION_ILLEGAL_INSTRUCTION) {
|
||||
Trap trap;
|
||||
BytecodeOffset bytecode;
|
||||
if (!codeSegment->code().lookupTrap(pc, &trap, &bytecode))
|
||||
return false;
|
||||
|
||||
activation->startWasmTrap(trap, bytecode.offset, pc, ContextToFP(context));
|
||||
*ppc = codeSegment->trapCode();
|
||||
return true;
|
||||
}
|
||||
|
||||
if (record->NumberParameters < 2)
|
||||
return false;
|
||||
|
||||
uint8_t* faultingAddress = reinterpret_cast<uint8_t*>(record->ExceptionInformation[1]);
|
||||
|
||||
// This check isn't necessary, but, since we can, check anyway to make
|
||||
|
@ -1016,18 +1047,6 @@ HandleFault(PEXCEPTION_POINTERS exception)
|
|||
|
||||
MOZ_ASSERT(activation->compartment() == instance->compartment());
|
||||
|
||||
// Similar to the non-atomic situation above, on Windows, an OOB fault at a
|
||||
// PC can trigger *after* an async interrupt observed that PC and attempted
|
||||
// to redirect to the async stub. In this unique case, isWasmInterrupted() is
|
||||
// already true when the OOB handler is called. Since the point of the async
|
||||
// interrupt is to get out of an iloop and the OOB trap will do just that,
|
||||
// we can simply clear the interrupt. (The update to CONTEXT.pc made by
|
||||
// HandleMemoryAccess will clobber the interrupt's previous update.)
|
||||
if (activation->isWasmInterrupted()) {
|
||||
MOZ_ASSERT(activation->wasmResumePC() == pc);
|
||||
activation->finishWasmInterrupt();
|
||||
}
|
||||
|
||||
HandleMemoryAccess(context, pc, faultingAddress, codeSegment, *instance, activation, ppc);
|
||||
return true;
|
||||
}
|
||||
|
@ -1113,8 +1132,11 @@ HandleMachException(JSContext* cx, const ExceptionRequest& request)
|
|||
uint8_t** ppc = ContextToPC(&context);
|
||||
uint8_t* pc = *ppc;
|
||||
|
||||
if (request.body.exception != EXC_BAD_ACCESS || request.body.codeCnt != 2)
|
||||
if (request.body.exception != EXC_BAD_ACCESS &&
|
||||
request.body.exception != EXC_BAD_INSTRUCTION)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// The faulting thread is suspended so we can access cx fields that can
|
||||
// normally only be accessed by the cx's active thread.
|
||||
|
@ -1128,17 +1150,31 @@ HandleMachException(JSContext* cx, const ExceptionRequest& request)
|
|||
if (!instance)
|
||||
return false;
|
||||
|
||||
uint8_t* faultingAddress = reinterpret_cast<uint8_t*>(request.body.code[1]);
|
||||
|
||||
// This check isn't necessary, but, since we can, check anyway to make
|
||||
// sure we aren't covering up a real bug.
|
||||
if (!IsHeapAccessAddress(*instance, faultingAddress))
|
||||
return false;
|
||||
|
||||
JitActivation* activation = cx->activation()->asJit();
|
||||
MOZ_ASSERT(activation->compartment() == instance->compartment());
|
||||
|
||||
HandleMemoryAccess(&context, pc, faultingAddress, codeSegment, *instance, activation, ppc);
|
||||
if (request.body.exception == EXC_BAD_INSTRUCTION) {
|
||||
Trap trap;
|
||||
BytecodeOffset bytecode;
|
||||
if (!codeSegment->code().lookupTrap(pc, &trap, &bytecode))
|
||||
return false;
|
||||
|
||||
activation->startWasmTrap(trap, bytecode.offset, pc, ContextToFP(&context));
|
||||
*ppc = codeSegment->trapCode();
|
||||
} else {
|
||||
MOZ_ASSERT(request.body.exception == EXC_BAD_ACCESS);
|
||||
if (request.body.codeCnt != 2)
|
||||
return false;
|
||||
|
||||
uint8_t* faultingAddress = reinterpret_cast<uint8_t*>(request.body.code[1]);
|
||||
|
||||
// This check isn't necessary, but, since we can, check anyway to make
|
||||
// sure we aren't covering up a real bug.
|
||||
if (!IsHeapAccessAddress(*instance, faultingAddress))
|
||||
return false;
|
||||
|
||||
HandleMemoryAccess(&context, pc, faultingAddress, codeSegment, *instance, activation, ppc);
|
||||
}
|
||||
|
||||
// Update the thread state with the new pc and register values.
|
||||
kret = thread_set_state(cxThread, float_state, (thread_state_t)&context.float_, float_state_count);
|
||||
|
@ -1221,7 +1257,7 @@ MachExceptionHandler::uninstall()
|
|||
if (installed_) {
|
||||
thread_port_t thread = mach_thread_self();
|
||||
kern_return_t kret = thread_set_exception_ports(thread,
|
||||
EXC_MASK_BAD_ACCESS,
|
||||
EXC_MASK_BAD_ACCESS | EXC_MASK_BAD_INSTRUCTION,
|
||||
MACH_PORT_NULL,
|
||||
EXCEPTION_DEFAULT | MACH_EXCEPTION_CODES,
|
||||
THREAD_STATE_NONE);
|
||||
|
@ -1286,7 +1322,7 @@ MachExceptionHandler::install(JSContext* cx)
|
|||
// exception, so we should be fine.
|
||||
thread = mach_thread_self();
|
||||
kret = thread_set_exception_ports(thread,
|
||||
EXC_MASK_BAD_ACCESS,
|
||||
EXC_MASK_BAD_ACCESS | EXC_MASK_BAD_INSTRUCTION,
|
||||
port_,
|
||||
EXCEPTION_DEFAULT | MACH_EXCEPTION_CODES,
|
||||
THREAD_STATE_NONE);
|
||||
|
@ -1311,7 +1347,7 @@ HandleFault(int signum, siginfo_t* info, void* ctx)
|
|||
return false;
|
||||
AutoSignalHandler ash;
|
||||
|
||||
MOZ_RELEASE_ASSERT(signum == SIGSEGV || signum == SIGBUS);
|
||||
MOZ_RELEASE_ASSERT(signum == SIGSEGV || signum == SIGBUS || signum == SIGILL);
|
||||
|
||||
CONTEXT* context = (CONTEXT*)ctx;
|
||||
uint8_t** ppc = ContextToPC(context);
|
||||
|
@ -1325,6 +1361,20 @@ HandleFault(int signum, siginfo_t* info, void* ctx)
|
|||
if (!instance)
|
||||
return false;
|
||||
|
||||
JitActivation* activation = TlsContext.get()->activation()->asJit();
|
||||
MOZ_ASSERT(activation->compartment() == instance->compartment());
|
||||
|
||||
if (signum == SIGILL) {
|
||||
Trap trap;
|
||||
BytecodeOffset bytecode;
|
||||
if (!segment->code().lookupTrap(pc, &trap, &bytecode))
|
||||
return false;
|
||||
|
||||
activation->startWasmTrap(trap, bytecode.offset, pc, ContextToFP(context));
|
||||
*ppc = segment->trapCode();
|
||||
return true;
|
||||
}
|
||||
|
||||
uint8_t* faultingAddress = reinterpret_cast<uint8_t*>(info->si_addr);
|
||||
|
||||
// Although it's not strictly necessary, to make sure we're not covering up
|
||||
|
@ -1346,9 +1396,6 @@ HandleFault(int signum, siginfo_t* info, void* ctx)
|
|||
return false;
|
||||
}
|
||||
|
||||
JitActivation* activation = TlsContext.get()->activation()->asJit();
|
||||
MOZ_ASSERT(activation->compartment() == instance->compartment());
|
||||
|
||||
#ifdef JS_CODEGEN_ARM
|
||||
if (signum == SIGBUS) {
|
||||
// TODO: We may see a bus error for something that is an unaligned access that
|
||||
|
@ -1367,6 +1414,7 @@ HandleFault(int signum, siginfo_t* info, void* ctx)
|
|||
|
||||
static struct sigaction sPrevSEGVHandler;
|
||||
static struct sigaction sPrevSIGBUSHandler;
|
||||
static struct sigaction sPrevSIGILLHandler;
|
||||
|
||||
static void
|
||||
WasmFaultHandler(int signum, siginfo_t* info, void* context)
|
||||
|
@ -1374,9 +1422,13 @@ WasmFaultHandler(int signum, siginfo_t* info, void* context)
|
|||
if (HandleFault(signum, info, context))
|
||||
return;
|
||||
|
||||
struct sigaction* previousSignal = signum == SIGSEGV
|
||||
? &sPrevSEGVHandler
|
||||
: &sPrevSIGBUSHandler;
|
||||
struct sigaction* previousSignal = nullptr;
|
||||
switch (signum) {
|
||||
case SIGSEGV: previousSignal = &sPrevSEGVHandler; break;
|
||||
case SIGBUS: previousSignal = &sPrevSIGBUSHandler; break;
|
||||
case SIGILL: previousSignal = &sPrevSIGILLHandler; break;
|
||||
}
|
||||
MOZ_ASSERT(previousSignal);
|
||||
|
||||
// This signal is not for any asm.js code we expect, so we need to forward
|
||||
// the signal to the next handler. If there is no next handler (SIG_IGN or
|
||||
|
@ -1617,6 +1669,15 @@ ProcessHasSignalHandlers()
|
|||
if (sigaction(SIGBUS, &busHandler, &sPrevSIGBUSHandler))
|
||||
MOZ_CRASH("unable to install sigbus handler");
|
||||
# endif
|
||||
|
||||
// Install a SIGILL handler to handle the ud2 instructions that are emitted
|
||||
// to implement wasm traps.
|
||||
struct sigaction illegalHandler;
|
||||
illegalHandler.sa_flags = SA_SIGINFO | SA_NODEFER | SA_ONSTACK;
|
||||
illegalHandler.sa_sigaction = WasmFaultHandler;
|
||||
sigemptyset(&illegalHandler.sa_mask);
|
||||
if (sigaction(SIGILL, &illegalHandler, &sPrevSIGILLHandler))
|
||||
MOZ_CRASH("unable to install segv handler");
|
||||
# endif
|
||||
|
||||
sHaveSignalHandlers = true;
|
||||
|
|
|
@ -25,6 +25,7 @@
|
|||
# include <mach/mach.h>
|
||||
#endif
|
||||
#include "threading/Thread.h"
|
||||
#include "wasm/WasmTypes.h"
|
||||
|
||||
struct JSContext;
|
||||
struct JSRuntime;
|
||||
|
@ -79,6 +80,35 @@ class MachExceptionHandler
|
|||
};
|
||||
#endif
|
||||
|
||||
// Typed wrappers encapsulating the data saved by the signal handler on async
|
||||
// interrupt or trap. On interrupt, the PC at which to resume is saved. On trap,
|
||||
// the bytecode offset to be reported in callstacks is saved.
|
||||
|
||||
struct InterruptData
|
||||
{
|
||||
// The pc to use for unwinding purposes which is kept consistent with fp at
|
||||
// call boundaries.
|
||||
void* unwindPC;
|
||||
|
||||
// The pc at which we should return if the interrupt doesn't stop execution.
|
||||
void* resumePC;
|
||||
|
||||
InterruptData(void* unwindPC, void* resumePC)
|
||||
: unwindPC(unwindPC), resumePC(resumePC)
|
||||
{}
|
||||
};
|
||||
|
||||
struct TrapData
|
||||
{
|
||||
void* pc;
|
||||
Trap trap;
|
||||
uint32_t bytecodeOffset;
|
||||
|
||||
TrapData(void* pc, Trap trap, uint32_t bytecodeOffset)
|
||||
: pc(pc), trap(trap), bytecodeOffset(bytecodeOffset)
|
||||
{}
|
||||
};
|
||||
|
||||
} // namespace wasm
|
||||
} // namespace js
|
||||
|
||||
|
|
|
@ -1001,6 +1001,29 @@ wasm::GenerateBuiltinThunk(MacroAssembler& masm, ABIFunctionType abiType, ExitRe
|
|||
return FinishOffsets(masm, offsets);
|
||||
}
|
||||
|
||||
// Generate a stub which calls WasmReportTrap() and can be executed by having
|
||||
// the signal handler redirect PC from any trapping instruction.
|
||||
static bool
|
||||
GenerateTrapExit(MacroAssembler& masm, Label* throwLabel, Offsets* offsets)
|
||||
{
|
||||
masm.haltingAlign(CodeAlignment);
|
||||
|
||||
offsets->begin = masm.currentOffset();
|
||||
|
||||
// We know that StackPointer is word-aligned, but not necessarily
|
||||
// stack-aligned, so we need to align it dynamically.
|
||||
masm.andToStackPtr(Imm32(~(ABIStackAlignment - 1)));
|
||||
if (ShadowStackSpace)
|
||||
masm.subFromStackPtr(Imm32(ShadowStackSpace));
|
||||
|
||||
masm.assertStackAlignment(ABIStackAlignment);
|
||||
masm.call(SymbolicAddress::ReportTrap);
|
||||
|
||||
masm.jump(throwLabel);
|
||||
|
||||
return FinishOffsets(masm, offsets);
|
||||
}
|
||||
|
||||
// Generate a stub that calls into WasmOldReportTrap with the right trap reason.
|
||||
// This stub is called with ABIStackAlignment by a trap out-of-line path. An
|
||||
// exit prologue/epilogue is used so that stack unwinding picks up the
|
||||
|
@ -1368,11 +1391,30 @@ wasm::GenerateStubs(const ModuleEnvironment& env, const FuncImportVector& import
|
|||
}
|
||||
|
||||
for (Trap trap : MakeEnumeratedRange(Trap::Limit)) {
|
||||
CallableOffsets offsets;
|
||||
if (!GenerateOldTrapExit(masm, trap, &throwLabel, &offsets))
|
||||
return false;
|
||||
if (!code->codeRanges.emplaceBack(trap, offsets))
|
||||
return false;
|
||||
switch (trap) {
|
||||
case Trap::Unreachable:
|
||||
break;
|
||||
// The TODO list of "old" traps to convert to new traps:
|
||||
case Trap::IntegerOverflow:
|
||||
case Trap::InvalidConversionToInteger:
|
||||
case Trap::IntegerDivideByZero:
|
||||
case Trap::OutOfBounds:
|
||||
case Trap::UnalignedAccess:
|
||||
case Trap::IndirectCallToNull:
|
||||
case Trap::IndirectCallBadSig:
|
||||
case Trap::ImpreciseSimdConversion:
|
||||
case Trap::StackOverflow:
|
||||
case Trap::ThrowReported: {
|
||||
CallableOffsets offsets;
|
||||
if (!GenerateOldTrapExit(masm, trap, &throwLabel, &offsets))
|
||||
return false;
|
||||
if (!code->codeRanges.emplaceBack(trap, offsets))
|
||||
return false;
|
||||
break;
|
||||
}
|
||||
case Trap::Limit:
|
||||
MOZ_CRASH("impossible");
|
||||
}
|
||||
}
|
||||
|
||||
Offsets offsets;
|
||||
|
@ -1387,6 +1429,11 @@ wasm::GenerateStubs(const ModuleEnvironment& env, const FuncImportVector& import
|
|||
if (!code->codeRanges.emplaceBack(CodeRange::UnalignedExit, offsets))
|
||||
return false;
|
||||
|
||||
if (!GenerateTrapExit(masm, &throwLabel, &offsets))
|
||||
return false;
|
||||
if (!code->codeRanges.emplaceBack(CodeRange::TrapExit, offsets))
|
||||
return false;
|
||||
|
||||
if (!GenerateInterruptExit(masm, &throwLabel, &offsets))
|
||||
return false;
|
||||
if (!code->codeRanges.emplaceBack(CodeRange::Interrupt, offsets))
|
||||
|
|
|
@ -30,6 +30,7 @@ using namespace js::jit;
|
|||
using namespace js::wasm;
|
||||
|
||||
using mozilla::IsPowerOfTwo;
|
||||
using mozilla::MakeEnumeratedRange;
|
||||
|
||||
// A sanity check. We have only tested WASM_HUGE_MEMORY on x64, and only tested
|
||||
// x64 with WASM_HUGE_MEMORY.
|
||||
|
@ -690,6 +691,75 @@ DebugFrame::leave(JSContext* cx)
|
|||
}
|
||||
}
|
||||
|
||||
bool
|
||||
TrapSiteVectorArray::empty() const
|
||||
{
|
||||
for (Trap trap : MakeEnumeratedRange(Trap::Limit)) {
|
||||
if (!(*this)[trap].empty())
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void
|
||||
TrapSiteVectorArray::clear()
|
||||
{
|
||||
for (Trap trap : MakeEnumeratedRange(Trap::Limit))
|
||||
(*this)[trap].clear();
|
||||
}
|
||||
|
||||
void
|
||||
TrapSiteVectorArray::swap(TrapSiteVectorArray& rhs)
|
||||
{
|
||||
for (Trap trap : MakeEnumeratedRange(Trap::Limit))
|
||||
(*this)[trap].swap(rhs[trap]);
|
||||
}
|
||||
|
||||
void
|
||||
TrapSiteVectorArray::podResizeToFit()
|
||||
{
|
||||
for (Trap trap : MakeEnumeratedRange(Trap::Limit))
|
||||
(*this)[trap].podResizeToFit();
|
||||
}
|
||||
|
||||
size_t
|
||||
TrapSiteVectorArray::serializedSize() const
|
||||
{
|
||||
size_t ret = 0;
|
||||
for (Trap trap : MakeEnumeratedRange(Trap::Limit))
|
||||
ret += SerializedPodVectorSize((*this)[trap]);
|
||||
return ret;
|
||||
}
|
||||
|
||||
uint8_t*
|
||||
TrapSiteVectorArray::serialize(uint8_t* cursor) const
|
||||
{
|
||||
for (Trap trap : MakeEnumeratedRange(Trap::Limit))
|
||||
cursor = SerializePodVector(cursor, (*this)[trap]);
|
||||
return cursor;
|
||||
}
|
||||
|
||||
const uint8_t*
|
||||
TrapSiteVectorArray::deserialize(const uint8_t* cursor)
|
||||
{
|
||||
for (Trap trap : MakeEnumeratedRange(Trap::Limit)) {
|
||||
cursor = DeserializePodVector(cursor, &(*this)[trap]);
|
||||
if (!cursor)
|
||||
return nullptr;
|
||||
}
|
||||
return cursor;
|
||||
}
|
||||
|
||||
size_t
|
||||
TrapSiteVectorArray::sizeOfExcludingThis(MallocSizeOf mallocSizeOf) const
|
||||
{
|
||||
size_t ret = 0;
|
||||
for (Trap trap : MakeEnumeratedRange(Trap::Limit))
|
||||
ret += (*this)[trap].sizeOfExcludingThis(mallocSizeOf);
|
||||
return ret;
|
||||
}
|
||||
|
||||
CodeRange::CodeRange(Kind kind, Offsets offsets)
|
||||
: begin_(offsets.begin),
|
||||
ret_(0),
|
||||
|
@ -703,6 +773,7 @@ CodeRange::CodeRange(Kind kind, Offsets offsets)
|
|||
case FarJumpIsland:
|
||||
case OutOfBoundsExit:
|
||||
case UnalignedExit:
|
||||
case TrapExit:
|
||||
case Throw:
|
||||
case Interrupt:
|
||||
break;
|
||||
|
|
|
@ -935,6 +935,52 @@ enum class Trap
|
|||
Limit
|
||||
};
|
||||
|
||||
// A wrapper around the bytecode offset of a wasm instruction within a whole
|
||||
// module, used for trap offsets or call offsets. These offsets should refer to
|
||||
// the first byte of the instruction that triggered the trap / did the call and
|
||||
// should ultimately derive from OpIter::bytecodeOffset.
|
||||
|
||||
struct BytecodeOffset
|
||||
{
|
||||
static const uint32_t INVALID = -1;
|
||||
uint32_t offset;
|
||||
|
||||
BytecodeOffset() : offset(INVALID) {}
|
||||
explicit BytecodeOffset(uint32_t offset) : offset(offset) {}
|
||||
|
||||
bool isValid() const { return offset != INVALID; }
|
||||
};
|
||||
|
||||
// A TrapSite (in the TrapSiteVector for a given Trap code) represents a wasm
|
||||
// instruction at a given bytecode offset that can fault at the given pc offset.
|
||||
// When such a fault occurs, a signal/exception handler looks up the TrapSite to
|
||||
// confirm the fault is intended/safe and redirects pc to the trap stub.
|
||||
|
||||
struct TrapSite
|
||||
{
|
||||
uint32_t pcOffset;
|
||||
BytecodeOffset bytecode;
|
||||
|
||||
TrapSite() : pcOffset(-1), bytecode() {}
|
||||
TrapSite(uint32_t pcOffset, BytecodeOffset bytecode) : pcOffset(pcOffset), bytecode(bytecode) {}
|
||||
|
||||
void offsetBy(uint32_t offset) {
|
||||
pcOffset += offset;
|
||||
}
|
||||
};
|
||||
|
||||
WASM_DECLARE_POD_VECTOR(TrapSite, TrapSiteVector)
|
||||
|
||||
struct TrapSiteVectorArray : EnumeratedArray<Trap, Trap::Limit, TrapSiteVector>
|
||||
{
|
||||
bool empty() const;
|
||||
void clear();
|
||||
void swap(TrapSiteVectorArray& rhs);
|
||||
void podResizeToFit();
|
||||
|
||||
WASM_DECLARE_SERIALIZABLE(TrapSiteVectorArray)
|
||||
};
|
||||
|
||||
// The (,Callable,Func)Offsets classes are used to record the offsets of
|
||||
// different key points in a CodeRange during compilation.
|
||||
|
||||
|
@ -1011,6 +1057,7 @@ class CodeRange
|
|||
ImportJitExit, // fast-path calling from wasm into JIT code
|
||||
ImportInterpExit, // slow-path calling from wasm into C++ interp
|
||||
BuiltinThunk, // fast-path calling from wasm into a C++ native
|
||||
TrapExit, // calls C++ to report and jumps to throw stub
|
||||
OldTrapExit, // calls C++ to report and jumps to throw stub
|
||||
DebugTrap, // calls C++ to handle debug event
|
||||
FarJumpIsland, // inserted to connect otherwise out-of-range insns
|
||||
|
@ -1087,7 +1134,7 @@ class CodeRange
|
|||
return kind() == ImportJitExit;
|
||||
}
|
||||
bool isTrapExit() const {
|
||||
return kind() == OldTrapExit;
|
||||
return kind() == OldTrapExit || kind() == TrapExit;
|
||||
}
|
||||
bool isDebugTrap() const {
|
||||
return kind() == DebugTrap;
|
||||
|
@ -1101,7 +1148,7 @@ class CodeRange
|
|||
// the return instruction to calculate the frame pointer.
|
||||
|
||||
bool hasReturn() const {
|
||||
return isFunction() || isImportExit() || isTrapExit() || isDebugTrap();
|
||||
return isFunction() || isImportExit() || kind() == OldTrapExit || isDebugTrap();
|
||||
}
|
||||
uint32_t ret() const {
|
||||
MOZ_ASSERT(hasReturn());
|
||||
|
@ -1179,22 +1226,6 @@ WASM_DECLARE_POD_VECTOR(CodeRange, CodeRangeVector)
|
|||
extern const CodeRange*
|
||||
LookupInSorted(const CodeRangeVector& codeRanges, CodeRange::OffsetInCode target);
|
||||
|
||||
// A wrapper around the bytecode offset of a wasm instruction within a whole
|
||||
// module, used for trap offsets or call offsets. These offsets should refer to
|
||||
// the first byte of the instruction that triggered the trap / did the call and
|
||||
// should ultimately derive from OpIter::bytecodeOffset.
|
||||
|
||||
struct BytecodeOffset
|
||||
{
|
||||
static const uint32_t INVALID = -1;
|
||||
uint32_t offset;
|
||||
|
||||
BytecodeOffset() : offset(INVALID) {}
|
||||
explicit BytecodeOffset(uint32_t offset) : offset(offset) {}
|
||||
|
||||
bool isValid() const { return offset != INVALID; }
|
||||
};
|
||||
|
||||
// While the frame-pointer chain allows the stack to be unwound without
|
||||
// metadata, Error.stack still needs to know the line/column of every call in
|
||||
// the chain. A CallSiteDesc describes a single callsite to which CallSite adds
|
||||
|
@ -1333,6 +1364,7 @@ enum class SymbolicAddress
|
|||
HandleExecutionInterrupt,
|
||||
HandleDebugTrap,
|
||||
HandleThrow,
|
||||
ReportTrap,
|
||||
OldReportTrap,
|
||||
ReportOutOfBounds,
|
||||
ReportUnalignedAccess,
|
||||
|
|
|
@ -17,7 +17,6 @@
|
|||
// PR LOGGING
|
||||
#include "mozilla/Logging.h"
|
||||
|
||||
#define DUMP_LAYOUT_LEVEL 9 // this turns on the dumping of each doucment's layout info
|
||||
static mozilla::LazyLogModule gPrintingLog("printing");
|
||||
|
||||
#define PR_PL(_p1) MOZ_LOG(gPrintingLog, mozilla::LogLevel::Debug, _p1);
|
||||
|
|
|
@ -136,7 +136,8 @@ using namespace mozilla::dom;
|
|||
//#define EXTENDED_DEBUG_PRINTING
|
||||
#endif
|
||||
|
||||
#define DUMP_LAYOUT_LEVEL 9 // this turns on the dumping of each doucment's layout info
|
||||
// this log level turns on the dumping of each document's layout info
|
||||
#define DUMP_LAYOUT_LEVEL (static_cast<mozilla::LogLevel>(9))
|
||||
|
||||
#ifndef PR_PL
|
||||
static mozilla::LazyLogModule gPrintingLog("printing")
|
||||
|
@ -162,13 +163,15 @@ static nsresult DeleteUnselectedNodes(nsIDocument* aOrigDoc, nsIDocument* aDoc);
|
|||
|
||||
#ifdef EXTENDED_DEBUG_PRINTING
|
||||
// Forward Declarations
|
||||
static void DumpPrintObjectsListStart(const char * aStr, nsTArray<nsPrintObject*> * aDocList);
|
||||
static void DumpPrintObjectsListStart(const char * aStr, const nsTArray<nsPrintObject*>& aDocList);
|
||||
static void DumpPrintObjectsTree(nsPrintObject * aPO, int aLevel= 0, FILE* aFD = nullptr);
|
||||
static void DumpPrintObjectsTreeLayout(nsPrintObject * aPO,nsDeviceContext * aDC, int aLevel= 0, FILE * aFD = nullptr);
|
||||
static void DumpPrintObjectsTreeLayout(const UniquePtr<nsPrintObject>& aPO,
|
||||
nsDeviceContext * aDC, int aLevel = 0,
|
||||
FILE * aFD = nullptr);
|
||||
|
||||
#define DUMP_DOC_LIST(_title) DumpPrintObjectsListStart((_title), mPrt->mPrintDocList);
|
||||
#define DUMP_DOC_TREE DumpPrintObjectsTree(mPrt->mPrintObject.get());
|
||||
#define DUMP_DOC_TREELAYOUT DumpPrintObjectsTreeLayout(mPrt->mPrintObject.get(), mPrt->mPrintDC);
|
||||
#define DUMP_DOC_TREELAYOUT DumpPrintObjectsTreeLayout(mPrt->mPrintObject, mPrt->mPrintDC);
|
||||
#else
|
||||
#define DUMP_DOC_LIST(_title)
|
||||
#define DUMP_DOC_TREE
|
||||
|
@ -600,13 +603,18 @@ nsPrintJob::GetSeqFrameAndCountPages(nsIFrame*& aSeqFrame, int32_t& aCount)
|
|||
|
||||
// Foward decl for Debug Helper Functions
|
||||
#ifdef EXTENDED_DEBUG_PRINTING
|
||||
#ifdef XP_WIN
|
||||
static int RemoveFilesInDir(const char * aDir);
|
||||
static void GetDocTitleAndURL(nsPrintObject* aPO, char *& aDocStr, char *& aURLStr);
|
||||
#endif
|
||||
static void GetDocTitleAndURL(const UniquePtr<nsPrintObject>& aPO,
|
||||
nsACString& aDocStr,
|
||||
nsACString& aURLStr);
|
||||
static void DumpPrintObjectsTree(nsPrintObject * aPO, int aLevel, FILE* aFD);
|
||||
static void DumpPrintObjectsList(nsTArray<nsPrintObject*> * aDocList);
|
||||
static void RootFrameList(nsPresContext* aPresContext, FILE* out, int32_t aIndent);
|
||||
static void DumpPrintObjectsList(const nsTArray<nsPrintObject*>& aDocList);
|
||||
static void RootFrameList(nsPresContext* aPresContext, FILE* out,
|
||||
const char* aPrefix);
|
||||
static void DumpViews(nsIDocShell* aDocShell, FILE* out);
|
||||
static void DumpLayoutData(char* aTitleStr, char* aURLStr,
|
||||
static void DumpLayoutData(const char* aTitleStr, const char* aURLStr,
|
||||
nsPresContext* aPresContext,
|
||||
nsDeviceContext * aDC, nsIFrame * aRootFrame,
|
||||
nsIDocShell * aDocShell, FILE* aFD);
|
||||
|
@ -1664,7 +1672,7 @@ nsPrintJob::ReconstructAndReflow(bool doSetPixelScale)
|
|||
#if defined(XP_WIN) && defined(EXTENDED_DEBUG_PRINTING)
|
||||
// We need to clear all the output files here
|
||||
// because they will be re-created with second reflow of the docs
|
||||
if (kPrintingLogMod && kPrintingLogMod->level == DUMP_LAYOUT_LEVEL) {
|
||||
if (MOZ_LOG_TEST(gPrintingLog, DUMP_LAYOUT_LEVEL)) {
|
||||
RemoveFilesInDir(".\\");
|
||||
gDumpFileNameCnt = 0;
|
||||
gDumpLOFileNameCnt = 0;
|
||||
|
@ -2409,7 +2417,7 @@ nsPrintJob::ReflowPrintObject(const UniquePtr<nsPrintObject>& aPO)
|
|||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
#ifdef EXTENDED_DEBUG_PRINTING
|
||||
if (kPrintingLogMod && kPrintingLogMod->level == DUMP_LAYOUT_LEVEL) {
|
||||
if (MOZ_LOG_TEST(gPrintingLog, DUMP_LAYOUT_LEVEL)) {
|
||||
nsAutoCString docStr;
|
||||
nsAutoCString urlStr;
|
||||
GetDocTitleAndURL(aPO, docStr, urlStr);
|
||||
|
@ -2435,9 +2443,9 @@ nsPrintJob::ReflowPrintObject(const UniquePtr<nsPrintObject>& aPO)
|
|||
} else {
|
||||
printf("View is null!\n");
|
||||
}
|
||||
if (docShell) {
|
||||
if (aPO->mDocShell) {
|
||||
fprintf(fd, "--------------- All Views ----------------\n");
|
||||
DumpViews(docShell, fd);
|
||||
DumpViews(aPO->mDocShell, fd);
|
||||
fprintf(fd, "---------------------------------------\n\n");
|
||||
}
|
||||
fclose(fd);
|
||||
|
@ -2661,7 +2669,7 @@ nsPrintJob::DoPrint(const UniquePtr<nsPrintObject>& aPO)
|
|||
nsAutoCString urlStr;
|
||||
GetDocTitleAndURL(aPO, docStr, urlStr);
|
||||
DumpLayoutData(docStr.get(), urlStr.get(), poPresContext,
|
||||
printData->mPrintDocDC, rootFrame, docShell, nullptr);
|
||||
printData->mPrintDC, rootFrame, aPO->mDocShell, nullptr);
|
||||
}
|
||||
#endif
|
||||
|
||||
|
@ -3639,7 +3647,8 @@ int RemoveFilesInDir(const char * aDir)
|
|||
/** ---------------------------------------------------
|
||||
* Dumps Frames for Printing
|
||||
*/
|
||||
static void RootFrameList(nsPresContext* aPresContext, FILE* out, int32_t aIndent)
|
||||
static void RootFrameList(nsPresContext* aPresContext, FILE* out,
|
||||
const char* aPrefix)
|
||||
{
|
||||
if (!aPresContext || !out)
|
||||
return;
|
||||
|
@ -3648,7 +3657,7 @@ static void RootFrameList(nsPresContext* aPresContext, FILE* out, int32_t aInden
|
|||
if (shell) {
|
||||
nsIFrame* frame = shell->FrameManager()->GetRootFrame();
|
||||
if (frame) {
|
||||
frame->List(aPresContext, out, aIndent);
|
||||
frame->List(out, aPrefix);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -3676,7 +3685,7 @@ static void DumpFrames(FILE* out,
|
|||
child->GetFrameName(tmp);
|
||||
fputs(NS_LossyConvertUTF16toASCII(tmp).get(), out);
|
||||
bool isSelected;
|
||||
if (NS_SUCCEEDED(child->IsVisibleForPainting(aPresContext, *aRendContext, true, &isSelected))) {
|
||||
if (child->IsVisibleForPainting()) {
|
||||
fprintf(out, " %p %s", child, isSelected?"VIS":"UVS");
|
||||
nsRect rect = child->GetRect();
|
||||
fprintf(out, "[%d,%d,%d,%d] ", rect.x, rect.y, rect.width, rect.height);
|
||||
|
@ -3700,7 +3709,7 @@ DumpViews(nsIDocShell* aDocShell, FILE* out)
|
|||
|
||||
if (nullptr != aDocShell) {
|
||||
fprintf(out, "docshell=%p \n", aDocShell);
|
||||
nsIPresShell* shell = nsPrintJob::GetPresShellFor(aDocShell);
|
||||
nsIPresShell* shell = aDocShell->GetPresShell();
|
||||
if (shell) {
|
||||
nsViewManager* vm = shell->GetViewManager();
|
||||
if (vm) {
|
||||
|
@ -3731,15 +3740,17 @@ DumpViews(nsIDocShell* aDocShell, FILE* out)
|
|||
/** ---------------------------------------------------
|
||||
* Dumps the Views and Frames
|
||||
*/
|
||||
void DumpLayoutData(char* aTitleStr,
|
||||
char* aURLStr,
|
||||
nsPresContext* aPresContext,
|
||||
nsDeviceContext * aDC,
|
||||
nsIFrame * aRootFrame,
|
||||
nsIDocShekk * aDocShell,
|
||||
void DumpLayoutData(const char* aTitleStr,
|
||||
const char* aURLStr,
|
||||
nsPresContext* aPresContext,
|
||||
nsDeviceContext* aDC,
|
||||
nsIFrame* aRootFrame,
|
||||
nsIDocShell* aDocShell,
|
||||
FILE* aFD = nullptr)
|
||||
{
|
||||
if (!kPrintingLogMod || kPrintingLogMod->level != DUMP_LAYOUT_LEVEL) return;
|
||||
if (!MOZ_LOG_TEST(gPrintingLog, DUMP_LAYOUT_LEVEL)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (aPresContext == nullptr || aDC == nullptr) {
|
||||
return;
|
||||
|
@ -3765,7 +3776,7 @@ void DumpLayoutData(char* aTitleStr,
|
|||
fprintf(fd, "--------------- Frames ----------------\n");
|
||||
//RefPtr<gfxContext> renderingContext =
|
||||
// aDC->CreateRenderingContext();
|
||||
RootFrameList(aPresContext, fd, 0);
|
||||
RootFrameList(aPresContext, fd, "");
|
||||
//DumpFrames(fd, aPresContext, renderingContext, aRootFrame, 0);
|
||||
fprintf(fd, "---------------------------------------\n\n");
|
||||
fprintf(fd, "--------------- Views From Root Frame----------------\n");
|
||||
|
@ -3787,18 +3798,16 @@ void DumpLayoutData(char* aTitleStr,
|
|||
}
|
||||
|
||||
//-------------------------------------------------------------
|
||||
static void DumpPrintObjectsList(nsTArray<nsPrintObject*> * aDocList)
|
||||
static void DumpPrintObjectsList(const nsTArray<nsPrintObject*>& aDocList)
|
||||
{
|
||||
if (!kPrintingLogMod || kPrintingLogMod->level != DUMP_LAYOUT_LEVEL) return;
|
||||
|
||||
NS_ASSERTION(aDocList, "Pointer is null!");
|
||||
if (!MOZ_LOG_TEST(gPrintingLog, DUMP_LAYOUT_LEVEL)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const char types[][3] = {"DC", "FR", "IF", "FS"};
|
||||
PR_PL(("Doc List\n***************************************************\n"));
|
||||
PR_PL(("T P A H PO DocShell Seq Page Root Page# Rect\n"));
|
||||
int32_t cnt = aDocList->Length();
|
||||
for (int32_t i=0;i<cnt;i++) {
|
||||
nsPrintObject* po = aDocList->ElementAt(i);
|
||||
for (nsPrintObject* po : aDocList) {
|
||||
NS_ASSERTION(po, "nsPrintObject can't be null!");
|
||||
nsIFrame* rootFrame = nullptr;
|
||||
if (po->mPresShell) {
|
||||
|
@ -3812,16 +3821,18 @@ static void DumpPrintObjectsList(nsTArray<nsPrintObject*> * aDocList)
|
|||
}
|
||||
}
|
||||
|
||||
PR_PL(("%s %d %d %d %p %p %p %p %p %d %d,%d,%d,%d\n", types[po->mFrameType],
|
||||
po->IsPrintable(), po->mPrintAsIs, po->mHasBeenPrinted, po, po->mDocShell.get(), po->mSeqFrame,
|
||||
po->mPageFrame, rootFrame, po->mPageNum, po->mRect.x, po->mRect.y, po->mRect.width, po->mRect.height));
|
||||
PR_PL(("%s %d %d %d %p %p %p\n", types[po->mFrameType],
|
||||
po->IsPrintable(), po->mPrintAsIs, po->mHasBeenPrinted, po,
|
||||
po->mDocShell.get(), rootFrame));
|
||||
}
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------
|
||||
static void DumpPrintObjectsTree(nsPrintObject * aPO, int aLevel, FILE* aFD)
|
||||
{
|
||||
if (!kPrintingLogMod || kPrintingLogMod->level != DUMP_LAYOUT_LEVEL) return;
|
||||
if (!MOZ_LOG_TEST(gPrintingLog, DUMP_LAYOUT_LEVEL)) {
|
||||
return;
|
||||
}
|
||||
|
||||
NS_ASSERTION(aPO, "Pointer is null!");
|
||||
|
||||
|
@ -3831,13 +3842,11 @@ static void DumpPrintObjectsTree(nsPrintObject * aPO, int aLevel, FILE* aFD)
|
|||
fprintf(fd, "DocTree\n***************************************************\n");
|
||||
fprintf(fd, "T PO DocShell Seq Page Page# Rect\n");
|
||||
}
|
||||
int32_t cnt = aPO->mKids.Length();
|
||||
for (int32_t i=0;i<cnt;i++) {
|
||||
nsPrintObject* po = aPO->mKids.ElementAt(i);
|
||||
for (const auto& po : aPO->mKids) {
|
||||
NS_ASSERTION(po, "nsPrintObject can't be null!");
|
||||
for (int32_t k=0;k<aLevel;k++) fprintf(fd, " ");
|
||||
fprintf(fd, "%s %p %p %p %p %d %d,%d,%d,%d\n", types[po->mFrameType], po, po->mDocShell.get(), po->mSeqFrame,
|
||||
po->mPageFrame, po->mPageNum, po->mRect.x, po->mRect.y, po->mRect.width, po->mRect.height);
|
||||
fprintf(fd, "%s %p %p\n", types[po->mFrameType], po.get(),
|
||||
po->mDocShell.get());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -3848,19 +3857,19 @@ static void GetDocTitleAndURL(const UniquePtr<nsPrintObject>& aPO,
|
|||
{
|
||||
nsAutoString docTitleStr;
|
||||
nsAutoString docURLStr;
|
||||
nsPrintJob::GetDisplayTitleAndURL(aPO,
|
||||
docTitleStr, docURLStr,
|
||||
nsPrintJob::eDocTitleDefURLDoc);
|
||||
GetDocumentTitleAndURL(aPO->mDocument, docTitleStr, docURLStr);
|
||||
aDocStr = NS_ConvertUTF16toUTF8(docTitleStr);
|
||||
aURLStr = NS_ConvertUTF16toUTF8(docURLStr);
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------
|
||||
static void DumpPrintObjectsTreeLayout(nsPrintObject * aPO,
|
||||
static void DumpPrintObjectsTreeLayout(const UniquePtr<nsPrintObject>& aPO,
|
||||
nsDeviceContext * aDC,
|
||||
int aLevel, FILE * aFD)
|
||||
{
|
||||
if (!kPrintingLogMod || kPrintingLogMod->level != DUMP_LAYOUT_LEVEL) return;
|
||||
if (!MOZ_LOG_TEST(gPrintingLog, DUMP_LAYOUT_LEVEL)) {
|
||||
return;
|
||||
}
|
||||
|
||||
NS_ASSERTION(aPO, "Pointer is null!");
|
||||
NS_ASSERTION(aDC, "Pointer is null!");
|
||||
|
@ -3881,8 +3890,8 @@ static void DumpPrintObjectsTreeLayout(nsPrintObject * aPO,
|
|||
rootFrame = aPO->mPresShell->FrameManager()->GetRootFrame();
|
||||
}
|
||||
for (int32_t k=0;k<aLevel;k++) fprintf(fd, " ");
|
||||
fprintf(fd, "%s %p %p %p %p %d %d,%d,%d,%d\n", types[aPO->mFrameType], aPO, aPO->mDocShell.get(), aPO->mSeqFrame,
|
||||
aPO->mPageFrame, aPO->mPageNum, aPO->mRect.x, aPO->mRect.y, aPO->mRect.width, aPO->mRect.height);
|
||||
fprintf(fd, "%s %p %p\n", types[aPO->mFrameType], aPO.get(),
|
||||
aPO->mDocShell.get());
|
||||
if (aPO->IsPrintable()) {
|
||||
nsAutoCString docStr;
|
||||
nsAutoCString urlStr;
|
||||
|
@ -3891,9 +3900,7 @@ static void DumpPrintObjectsTreeLayout(nsPrintObject * aPO,
|
|||
}
|
||||
fprintf(fd, "<***************************************************>\n");
|
||||
|
||||
int32_t cnt = aPO->mKids.Length();
|
||||
for (int32_t i=0;i<cnt;i++) {
|
||||
nsPrintObject* po = aPO->mKids.ElementAt(i);
|
||||
for (const auto& po : aPO->mKids) {
|
||||
NS_ASSERTION(po, "nsPrintObject can't be null!");
|
||||
DumpPrintObjectsTreeLayout(po, aDC, aLevel+1, fd);
|
||||
}
|
||||
|
@ -3904,25 +3911,19 @@ static void DumpPrintObjectsTreeLayout(nsPrintObject * aPO,
|
|||
}
|
||||
|
||||
//-------------------------------------------------------------
|
||||
static void DumpPrintObjectsListStart(const char * aStr, nsTArray<nsPrintObject*> * aDocList)
|
||||
static void DumpPrintObjectsListStart(const char * aStr,
|
||||
const nsTArray<nsPrintObject*>& aDocList)
|
||||
{
|
||||
if (!kPrintingLogMod || kPrintingLogMod->level != DUMP_LAYOUT_LEVEL) return;
|
||||
if (!MOZ_LOG_TEST(gPrintingLog, DUMP_LAYOUT_LEVEL)) {
|
||||
return;
|
||||
}
|
||||
|
||||
NS_ASSERTION(aStr, "Pointer is null!");
|
||||
NS_ASSERTION(aDocList, "Pointer is null!");
|
||||
|
||||
PR_PL(("%s\n", aStr));
|
||||
DumpPrintObjectsList(aDocList);
|
||||
}
|
||||
|
||||
#define DUMP_DOC_LIST(_title) DumpPrintObjectsListStart((_title), mPrt->mPrintDocList);
|
||||
#define DUMP_DOC_TREE DumpPrintObjectsTree(mPrt->mPrintObject.get());
|
||||
#define DUMP_DOC_TREELAYOUT DumpPrintObjectsTreeLayout(mPrt->mPrintObject.get(), mPrt->mPrintDC);
|
||||
|
||||
#else
|
||||
#define DUMP_DOC_LIST(_title)
|
||||
#define DUMP_DOC_TREE
|
||||
#define DUMP_DOC_TREELAYOUT
|
||||
#endif
|
||||
|
||||
//---------------------------------------------------------------
|
||||
|
|
|
@ -3254,12 +3254,11 @@ PeerConnectionImpl::IceGatheringStateChange(
|
|||
return;
|
||||
}
|
||||
WrappableJSErrorResult rv;
|
||||
RUN_ON_THREAD(mThread,
|
||||
WrapRunnable(pco,
|
||||
&PeerConnectionObserver::OnStateChange,
|
||||
PCObserverStateType::IceGatheringState,
|
||||
rv, static_cast<JSCompartment*>(nullptr)),
|
||||
NS_DISPATCH_NORMAL);
|
||||
mThread->Dispatch(WrapRunnable(pco,
|
||||
&PeerConnectionObserver::OnStateChange,
|
||||
PCObserverStateType::IceGatheringState,
|
||||
rv, static_cast<JSCompartment*>(nullptr)),
|
||||
NS_DISPATCH_NORMAL);
|
||||
|
||||
if (mIceGatheringState == PCImplIceGatheringState::Complete) {
|
||||
SendLocalIceCandidateToContent(0, "", "");
|
||||
|
|
|
@ -159,6 +159,11 @@ public final class GeckoSessionSettings implements Parcelable {
|
|||
return mBundle;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return mBundle.toString();
|
||||
}
|
||||
|
||||
private <T> boolean valueChangedLocked(final Key<T> key, T value) {
|
||||
if (key.initOnly && mSession != null && mSession.isOpen()) {
|
||||
throw new IllegalStateException("Read-only property");
|
||||
|
|
|
@ -123,9 +123,12 @@ public class GeckoViewActivity extends Activity {
|
|||
}
|
||||
|
||||
private void loadSettings(final Intent intent) {
|
||||
mGeckoView.getSettings().setBoolean(
|
||||
final GeckoSessionSettings settings = mGeckoView.getSettings();
|
||||
settings.setBoolean(
|
||||
GeckoSessionSettings.USE_REMOTE_DEBUGGER,
|
||||
intent.getBooleanExtra(USE_REMOTE_DEBUGGER_EXTRA, false));
|
||||
|
||||
Log.i(LOGTAG, "Load with settings " + settings);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -37,6 +37,21 @@ public:
|
|||
// Suspend()/Resume() functions.
|
||||
virtual nsresult SuspendMessageDiversion() = 0;
|
||||
virtual nsresult ResumeMessageDiversion() = 0;
|
||||
|
||||
// Cancel an ongoing diversion by using IPC to invoke Cancel() in the child.
|
||||
// This is necessary because most of the channel's state machine is suspended
|
||||
// during diversion, so an explicit action must be taken to interrupt the
|
||||
// diversion process so cancellation can be fully processed.
|
||||
//
|
||||
// Historically, diversions were assumed to be shortlived, where it was merely
|
||||
// a question of diverting some small amount of network traffic back to the
|
||||
// parent. However, Service Worker child interception made it possible for
|
||||
// the data to entirely be sourced from the child, which makes diversion
|
||||
// potentially long-lived. Especially when large files are involved.
|
||||
//
|
||||
// This mechanism is expected to be removed when ServiceWorkers move from
|
||||
// child intercept to parent intercept (in the short to medium term).
|
||||
virtual nsresult CancelDiversion() = 0;
|
||||
};
|
||||
|
||||
} // namespace net
|
||||
|
|
|
@ -737,6 +737,15 @@ FTPChannelParent::ResumeMessageDiversion()
|
|||
return NS_OK;
|
||||
}
|
||||
|
||||
nsresult
|
||||
FTPChannelParent::CancelDiversion()
|
||||
{
|
||||
// Only HTTP channels can have child-process-sourced-data that's long-lived
|
||||
// so this isn't currently relevant for FTP channels and there is nothing to
|
||||
// do.
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
void
|
||||
FTPChannelParent::DivertTo(nsIStreamListener *aListener)
|
||||
{
|
||||
|
|
|
@ -54,6 +54,8 @@ public:
|
|||
nsresult SuspendForDiversion() override;
|
||||
nsresult SuspendMessageDiversion() override;
|
||||
nsresult ResumeMessageDiversion() override;
|
||||
nsresult CancelDiversion() override;
|
||||
|
||||
|
||||
// Calls OnStartRequest for "DivertTo" listener, then notifies child channel
|
||||
// that it should divert OnDataAvailable and OnStopRequest calls to this
|
||||
|
|
|
@ -615,8 +615,6 @@ HttpChannelChild::OnStartRequest(const nsresult& channelStatus,
|
|||
DoOnStartRequest(this, mListenerContext);
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
class SyntheticDiversionListener final : public nsIStreamListener
|
||||
{
|
||||
RefPtr<HttpChannelChild> mChannel;
|
||||
|
@ -643,7 +641,10 @@ public:
|
|||
OnStopRequest(nsIRequest* aRequest, nsISupports* aContext,
|
||||
nsresult aStatus) override
|
||||
{
|
||||
mChannel->SendDivertOnStopRequest(aStatus);
|
||||
if (mChannel->mIPCOpen) {
|
||||
mChannel->SendDivertOnStopRequest(aStatus);
|
||||
mChannel->SendDivertComplete();
|
||||
}
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
|
@ -652,6 +653,11 @@ public:
|
|||
nsIInputStream* aInputStream, uint64_t aOffset,
|
||||
uint32_t aCount) override
|
||||
{
|
||||
if (!mChannel->mIPCOpen) {
|
||||
aRequest->Cancel(NS_ERROR_ABORT);
|
||||
return NS_ERROR_ABORT;
|
||||
}
|
||||
|
||||
nsAutoCString data;
|
||||
nsresult rv = NS_ConsumeStream(aInputStream, aCount, data);
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
|
@ -668,8 +674,6 @@ public:
|
|||
|
||||
NS_IMPL_ISUPPORTS(SyntheticDiversionListener, nsIStreamListener);
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
void
|
||||
HttpChannelChild::DoOnStartRequest(nsIRequest* aRequest, nsISupports* aContext)
|
||||
{
|
||||
|
@ -1884,7 +1888,11 @@ HttpChannelChild::FlushedForDiversion()
|
|||
// received from the parent channel, nor dequeued from the ChannelEventQueue.
|
||||
mFlushedForDiversion = true;
|
||||
|
||||
SendDivertComplete();
|
||||
// If we're synthesized, it's up to the SyntheticDiversionListener to invoke
|
||||
// SendDivertComplete after it has sent the DivertOnStopRequestMessage.
|
||||
if (!mSynthesizedResponse) {
|
||||
SendDivertComplete();
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
|
@ -3860,6 +3868,21 @@ HttpChannelChild::RecvAttachStreamFilter(Endpoint<extensions::PStreamFilterParen
|
|||
return IPC_OK();
|
||||
}
|
||||
|
||||
mozilla::ipc::IPCResult
|
||||
HttpChannelChild::RecvCancelDiversion()
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
|
||||
// This method is a very special case for cancellation of a diverted
|
||||
// intercepted channel. Normally we would go through the mEventQ in order to
|
||||
// serialize event execution in the face of sync XHR and now background
|
||||
// channels. However, similar to how CancelOnMainThread describes, Cancel()
|
||||
// pre-empts everything. (And frankly, we want this stack frame on the stack
|
||||
// if a crash happens.)
|
||||
Cancel(NS_ERROR_ABORT);
|
||||
return IPC_OK();
|
||||
}
|
||||
|
||||
void
|
||||
HttpChannelChild::ActorDestroy(ActorDestroyReason aWhy)
|
||||
{
|
||||
|
|
|
@ -47,6 +47,7 @@ namespace net {
|
|||
class HttpBackgroundChannelChild;
|
||||
class InterceptedChannelContent;
|
||||
class InterceptStreamListener;
|
||||
class SyntheticDiversionListener;
|
||||
|
||||
class HttpChannelChild final : public PHttpChannelChild
|
||||
, public HttpBaseChannel
|
||||
|
@ -168,6 +169,8 @@ protected:
|
|||
|
||||
mozilla::ipc::IPCResult RecvAttachStreamFilter(Endpoint<extensions::PStreamFilterParent>&& aEndpoint) override;
|
||||
|
||||
mozilla::ipc::IPCResult RecvCancelDiversion() override;
|
||||
|
||||
virtual void ActorDestroy(ActorDestroyReason aWhy) override;
|
||||
|
||||
MOZ_MUST_USE bool
|
||||
|
@ -472,6 +475,7 @@ private:
|
|||
friend class HttpAsyncAborter<HttpChannelChild>;
|
||||
friend class InterceptStreamListener;
|
||||
friend class InterceptedChannelContent;
|
||||
friend class SyntheticDiversionListener;
|
||||
friend class HttpBackgroundChannelChild;
|
||||
friend class NeckoTargetChannelEvent<HttpChannelChild>;
|
||||
};
|
||||
|
|
|
@ -2001,6 +2001,16 @@ HttpChannelParent::ResumeMessageDiversion()
|
|||
return NS_OK;
|
||||
}
|
||||
|
||||
nsresult
|
||||
HttpChannelParent::CancelDiversion()
|
||||
{
|
||||
LOG(("HttpChannelParent::CancelDiversion [this=%p]", this));
|
||||
if (!mIPCClosed) {
|
||||
Unused << SendCancelDiversion();
|
||||
}
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
/* private, supporting function for ADivertableParentChannel */
|
||||
nsresult
|
||||
HttpChannelParent::ResumeForDiversion()
|
||||
|
|
|
@ -83,6 +83,7 @@ public:
|
|||
MOZ_MUST_USE nsresult SuspendForDiversion() override;
|
||||
MOZ_MUST_USE nsresult SuspendMessageDiversion() override;
|
||||
MOZ_MUST_USE nsresult ResumeMessageDiversion() override;
|
||||
MOZ_MUST_USE nsresult CancelDiversion() override;
|
||||
|
||||
// Calls OnStartRequest for "DivertTo" listener, then notifies child channel
|
||||
// that it should divert OnDataAvailable and OnStopRequest calls to this
|
||||
|
|
|
@ -33,6 +33,7 @@ InterceptedHttpChannel::InterceptedHttpChannel(PRTime aCreationTime,
|
|||
, mResumeStartPos(0)
|
||||
, mSynthesizedOrReset(Invalid)
|
||||
, mCallingStatusAndProgress(false)
|
||||
, mDiverting(false)
|
||||
{
|
||||
// Pre-set the creation and AsyncOpen times based on the original channel
|
||||
// we are intercepting. We don't want our extra internal redirect to mask
|
||||
|
@ -501,6 +502,15 @@ InterceptedHttpChannel::Cancel(nsresult aStatus)
|
|||
mStatus = aStatus;
|
||||
}
|
||||
|
||||
// Everything is suspended during diversion until it completes. Since the
|
||||
// intercepted channel could be a long-running stream, we need to request that
|
||||
// cancellation be triggered in the child, completing the diversion and
|
||||
// allowing cancellation to run to completion.
|
||||
if (mDiverting) {
|
||||
Unused << mParentChannel->CancelDiversion();
|
||||
// (We want the pump to be canceled as well, so don't directly return.)
|
||||
}
|
||||
|
||||
if (mPump) {
|
||||
return mPump->Cancel(mStatus);
|
||||
}
|
||||
|
@ -1114,6 +1124,7 @@ InterceptedHttpChannel::MessageDiversionStarted(ADivertableParentChannel* aParen
|
|||
{
|
||||
MOZ_ASSERT(!mParentChannel);
|
||||
mParentChannel = aParentChannel;
|
||||
mDiverting = true;
|
||||
uint32_t suspendCount = mSuspendCount;
|
||||
while(suspendCount--) {
|
||||
mParentChannel->SuspendMessageDiversion();
|
||||
|
@ -1126,6 +1137,7 @@ InterceptedHttpChannel::MessageDiversionStop()
|
|||
{
|
||||
MOZ_ASSERT(mParentChannel);
|
||||
mParentChannel = nullptr;
|
||||
mDiverting = false;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
|
|
|
@ -94,6 +94,7 @@ private:
|
|||
Reset
|
||||
} mSynthesizedOrReset;
|
||||
Atomic<bool> mCallingStatusAndProgress;
|
||||
bool mDiverting;
|
||||
|
||||
InterceptedHttpChannel(PRTime aCreationTime,
|
||||
const TimeStamp& aCreationTimestamp,
|
||||
|
|
|
@ -149,6 +149,9 @@ child:
|
|||
|
||||
async AttachStreamFilter(Endpoint<PStreamFilterParent> aEndpoint);
|
||||
|
||||
// See ADivertableParentChannel::CancelDiversion
|
||||
async CancelDiversion();
|
||||
|
||||
both:
|
||||
// After receiving this message, the parent also calls
|
||||
// SendFinishInterceptedRedirect, and makes sure not to send any more messages
|
||||
|
|
|
@ -135,7 +135,11 @@ EXTRA_COMPONENTS += [
|
|||
]
|
||||
|
||||
if CONFIG['OS_TARGET'] == 'Darwin':
|
||||
if CONFIG['HOST_MAJOR_VERSION'] >= '15':
|
||||
if not CONFIG['HOST_MAJOR_VERSION']:
|
||||
DEFINES.update(
|
||||
HAS_CONNECTX=True,
|
||||
)
|
||||
elif CONFIG['HOST_MAJOR_VERSION'] >= '15':
|
||||
DEFINES.update(
|
||||
HAS_CONNECTX=True,
|
||||
)
|
||||
|
|
|
@ -192,11 +192,15 @@ nsMenuItemIconX::GetIconURI(nsIURI** aIconURI)
|
|||
|
||||
// Return NS_ERROR_FAILURE if the image region is invalid so the image
|
||||
// is not drawn, and behavior is similar to XUL menus.
|
||||
if (r.X() < 0 || r.Y() < 0 || r.IsEmpty()) {
|
||||
if (r.X() < 0 || r.Y() < 0 || r.Width() < 0 || r.Height() < 0) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
mImageRegionRect = r.ToNearestPixels(mozilla::AppUnitsPerCSSPixel());
|
||||
// 'auto' is represented by a [0, 0, 0, 0] rect. Only set mImageRegionRect
|
||||
// if we have some other value.
|
||||
if (!r.IsEmpty()) {
|
||||
mImageRegionRect = r.ToNearestPixels(mozilla::AppUnitsPerCSSPixel());
|
||||
}
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
|
|
|
@ -1365,6 +1365,12 @@ GfxInfo::GetGfxDriverInfo()
|
|||
nsIGfxInfo::FEATURE_D3D11_KEYED_MUTEX, nsIGfxInfo::FEATURE_BLOCKED_DEVICE,
|
||||
DRIVER_LESS_THAN, GfxDriverInfo::allDriverVersions, "FEATURE_FAILURE_BUG_1359416");
|
||||
|
||||
// bug 1419264
|
||||
APPEND_TO_DRIVER_BLOCKLIST(OperatingSystem::Windows7,
|
||||
(nsAString&) GfxDriverInfo::GetDeviceVendor(VendorNVIDIA), GfxDriverInfo::allDevices,
|
||||
nsIGfxInfo::FEATURE_ADVANCED_LAYERS, nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION,
|
||||
DRIVER_GREATER_THAN_OR_EQUAL, V(23,21,13,8813),
|
||||
"FEATURE_FAILURE_BUG_1419264", "Windows 10");
|
||||
}
|
||||
return *mDriverInfo;
|
||||
}
|
||||
|
|
Загрузка…
Ссылка в новой задаче