зеркало из https://github.com/mozilla/gecko-dev.git
Bug 900784 part 1.5 - Encode JS bytecode when no more scripts are executed. r=mrbkap
This commit is contained in:
Родитель
5b59221f67
Коммит
c6509f9e01
|
@ -57,9 +57,11 @@
|
|||
#include "mozilla/dom/EncodingUtils.h"
|
||||
#include "mozilla/ConsoleReportCollector.h"
|
||||
|
||||
#include "mozilla/AsyncEventDispatcher.h"
|
||||
#include "mozilla/Attributes.h"
|
||||
#include "mozilla/Unused.h"
|
||||
#include "nsIScriptError.h"
|
||||
#include "nsIOutputStream.h"
|
||||
|
||||
using namespace mozilla;
|
||||
using namespace mozilla::dom;
|
||||
|
@ -102,11 +104,24 @@ ImplCycleCollectionTraverse(nsCycleCollectionTraversalCallback& aCallback,
|
|||
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsScriptLoadRequest)
|
||||
NS_INTERFACE_MAP_END
|
||||
|
||||
NS_IMPL_CYCLE_COLLECTION_0(nsScriptLoadRequest)
|
||||
|
||||
NS_IMPL_CYCLE_COLLECTING_ADDREF(nsScriptLoadRequest)
|
||||
NS_IMPL_CYCLE_COLLECTING_RELEASE(nsScriptLoadRequest)
|
||||
|
||||
NS_IMPL_CYCLE_COLLECTION_CLASS(nsScriptLoadRequest)
|
||||
|
||||
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsScriptLoadRequest)
|
||||
NS_IMPL_CYCLE_COLLECTION_UNLINK(mCacheInfo)
|
||||
tmp->DropBytecodeCacheReferences();
|
||||
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
|
||||
|
||||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsScriptLoadRequest)
|
||||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCacheInfo)
|
||||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
|
||||
|
||||
NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(nsScriptLoadRequest)
|
||||
NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mScript)
|
||||
NS_IMPL_CYCLE_COLLECTION_TRACE_END
|
||||
|
||||
nsScriptLoadRequest::~nsScriptLoadRequest()
|
||||
{
|
||||
// We should always clean up any off-thread script parsing resources.
|
||||
|
@ -115,6 +130,10 @@ nsScriptLoadRequest::~nsScriptLoadRequest()
|
|||
// But play it safe in release builds and try to clean them up here
|
||||
// as a fail safe.
|
||||
MaybeCancelOffThreadScript();
|
||||
|
||||
if (mScript) {
|
||||
DropBytecodeCacheReferences();
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
|
@ -543,6 +562,7 @@ nsScriptLoader::nsScriptLoader(nsIDocument *aDocument)
|
|||
mDeferEnabled(false),
|
||||
mDocumentParsingDone(false),
|
||||
mBlockingDOMContentLoaded(false),
|
||||
mLoadEventFired(false),
|
||||
mReporter(new ConsoleReportCollector())
|
||||
{
|
||||
}
|
||||
|
@ -2162,9 +2182,9 @@ nsScriptLoader::ProcessRequest(nsScriptLoadRequest* aRequest)
|
|||
aRequest->MaybeCancelOffThreadScript();
|
||||
}
|
||||
|
||||
// Free any source data.
|
||||
// Free any source data, but keep the bytecode content as we might have to
|
||||
// save it later.
|
||||
aRequest->mScriptText.clearAndFree();
|
||||
aRequest->mScriptBytecode.clearAndFree();
|
||||
|
||||
return rv;
|
||||
}
|
||||
|
@ -2348,6 +2368,7 @@ nsScriptLoader::EvaluateScript(nsScriptLoadRequest* aRequest)
|
|||
MOZ_ASSERT(module);
|
||||
rv = nsJSUtils::ModuleEvaluation(aes.cx(), module);
|
||||
}
|
||||
aRequest->mCacheInfo = nullptr;
|
||||
} else {
|
||||
JS::CompileOptions options(aes.cx());
|
||||
rv = FillCompileOptionsForRequest(aes, aRequest, global, &options);
|
||||
|
@ -2363,41 +2384,249 @@ nsScriptLoader::EvaluateScript(nsScriptLoadRequest* aRequest)
|
|||
rv = exec.DecodeAndExec(options, aRequest->mScriptBytecode,
|
||||
aRequest->mBytecodeOffset);
|
||||
}
|
||||
// We do not expect to be saving anything when we already have some
|
||||
// bytecode.
|
||||
MOZ_ASSERT(!aRequest->mCacheInfo);
|
||||
} else {
|
||||
nsJSUtils::ExecutionContext exec(aes.cx(), global);
|
||||
MOZ_ASSERT(aRequest->IsSource());
|
||||
if (aRequest->mOffThreadToken) {
|
||||
// Off-main-thread parsing.
|
||||
LOG(("ScriptLoadRequest (%p): Join (off-thread parsing) and Execute",
|
||||
aRequest));
|
||||
JS::Rooted<JSScript*> script(aes.cx());
|
||||
rv = exec.JoinAndExec(&aRequest->mOffThreadToken, &script);
|
||||
{
|
||||
nsJSUtils::ExecutionContext exec(aes.cx(), global);
|
||||
JS::Rooted<JSScript*> script(aes.cx());
|
||||
if (!aRequest->mCacheInfo) {
|
||||
rv = exec.JoinAndExec(&aRequest->mOffThreadToken, &script);
|
||||
LOG(("ScriptLoadRequest (%p): Cannot cache anything (cacheInfo = nullptr)",
|
||||
aRequest));
|
||||
} else {
|
||||
MOZ_ASSERT(aRequest->mBytecodeOffset ==
|
||||
aRequest->mScriptBytecode.length());
|
||||
rv = exec.JoinEncodeAndExec(&aRequest->mOffThreadToken,
|
||||
aRequest->mScriptBytecode,
|
||||
&script);
|
||||
// Queue the current script load request to later save the bytecode.
|
||||
if (NS_SUCCEEDED(rv)) {
|
||||
aRequest->mScript = script;
|
||||
HoldJSObjects(aRequest);
|
||||
RegisterForBytecodeEncoding(aRequest);
|
||||
} else {
|
||||
LOG(("ScriptLoadRequest (%p): Cannot cache anything (rv = %X, script = %p, cacheInfo = %p)",
|
||||
aRequest, unsigned(rv), script.get(), aRequest->mCacheInfo.get()));
|
||||
aRequest->mCacheInfo = nullptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Main thread parsing (inline and small scripts)
|
||||
LOG(("ScriptLoadRequest (%p): Compile And Exec", aRequest));
|
||||
nsJSUtils::ExecutionContext exec(aes.cx(), global);
|
||||
nsAutoString inlineData;
|
||||
SourceBufferHolder srcBuf = GetScriptSource(aRequest, inlineData);
|
||||
rv = exec.CompileAndExec(options, srcBuf);
|
||||
aRequest->mCacheInfo = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// Even if we are not saving the bytecode of the current script, we have
|
||||
// to trigger the encoding of the bytecode, as the current script can
|
||||
// call functions of a script for which we are recording the bytecode.
|
||||
MaybeTriggerBytecodeEncoding();
|
||||
}
|
||||
|
||||
context->SetProcessingScriptTag(oldProcessingScriptTag);
|
||||
return rv;
|
||||
}
|
||||
|
||||
void
|
||||
nsScriptLoader::RegisterForBytecodeEncoding(nsScriptLoadRequest* aRequest)
|
||||
{
|
||||
MOZ_ASSERT(aRequest->mCacheInfo);
|
||||
MOZ_ASSERT(aRequest->mScript);
|
||||
mBytecodeEncodingQueue.AppendElement(aRequest);
|
||||
}
|
||||
|
||||
void
|
||||
nsScriptLoader::LoadEventFired()
|
||||
{
|
||||
mLoadEventFired = true;
|
||||
MaybeTriggerBytecodeEncoding();
|
||||
}
|
||||
|
||||
void
|
||||
nsScriptLoader::MaybeTriggerBytecodeEncoding()
|
||||
{
|
||||
// We wait for the load event to be fired before saving the bytecode of
|
||||
// any script to the cache. It is quite common to have load event
|
||||
// listeners trigger more JavaScript execution, that we want to save as
|
||||
// part of this start-up bytecode cache.
|
||||
if (!mLoadEventFired) {
|
||||
return;
|
||||
}
|
||||
|
||||
// No need to fire any event if there is no bytecode to be saved.
|
||||
if (mBytecodeEncodingQueue.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Wait until all scripts are loaded before saving the bytecode, such that
|
||||
// we capture most of the intialization of the page.
|
||||
if (HasPendingRequests()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Create a new runnable dedicated to encoding the content of the bytecode
|
||||
// of all enqueued scripts. In case of failure, we give-up on encoding the
|
||||
// bytecode.
|
||||
nsCOMPtr<nsIRunnable> encoder =
|
||||
NewRunnableMethod("nsScriptLoader::EncodeBytecode",
|
||||
this, &nsScriptLoader::EncodeBytecode);
|
||||
if (NS_FAILED(NS_DispatchToCurrentThread(encoder))) {
|
||||
GiveUpBytecodeEncoding();
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
nsScriptLoader::EncodeBytecode()
|
||||
{
|
||||
// If any script got added in the previous loop cycle, wait until all
|
||||
// remaining script executions are completed, such that we capture most of
|
||||
// the initialization.
|
||||
if (HasPendingRequests()) {
|
||||
return;
|
||||
}
|
||||
|
||||
nsCOMPtr<nsIScriptGlobalObject> globalObject = GetScriptGlobalObject();
|
||||
if (!globalObject) {
|
||||
GiveUpBytecodeEncoding();
|
||||
return;
|
||||
}
|
||||
|
||||
nsCOMPtr<nsIScriptContext> context = globalObject->GetScriptContext();
|
||||
if (!context) {
|
||||
GiveUpBytecodeEncoding();
|
||||
return;
|
||||
}
|
||||
|
||||
AutoEntryScript aes(globalObject, "encode bytecode", true);
|
||||
RefPtr<nsScriptLoadRequest> request;
|
||||
while (!mBytecodeEncodingQueue.isEmpty()) {
|
||||
request = mBytecodeEncodingQueue.StealFirst();
|
||||
EncodeRequestBytecode(aes.cx(), request);
|
||||
request->mScriptBytecode.clearAndFree();
|
||||
request->DropBytecodeCacheReferences();
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
nsScriptLoader::EncodeRequestBytecode(JSContext* aCx, nsScriptLoadRequest* aRequest)
|
||||
{
|
||||
nsresult rv = NS_OK;
|
||||
MOZ_ASSERT(aRequest->mCacheInfo);
|
||||
|
||||
JS::RootedScript script(aCx, aRequest->mScript);
|
||||
if (!JS::FinishIncrementalEncoding(aCx, script)) {
|
||||
LOG(("ScriptLoadRequest (%p): Cannot serialize bytecode",
|
||||
aRequest));
|
||||
return;
|
||||
}
|
||||
|
||||
if (aRequest->mScriptBytecode.length() >= UINT32_MAX) {
|
||||
LOG(("ScriptLoadRequest (%p): Bytecode cache is too large to be decoded correctly.",
|
||||
aRequest));
|
||||
return;
|
||||
}
|
||||
|
||||
// Open the output stream to the cache entry alternate data storage. This
|
||||
// might fail if the stream is already open by another request, in which
|
||||
// case, we just ignore the current one.
|
||||
nsCOMPtr<nsIOutputStream> output;
|
||||
rv = aRequest->mCacheInfo->OpenAlternativeOutputStream(kBytecodeMimeType,
|
||||
getter_AddRefs(output));
|
||||
if (NS_FAILED(rv)) {
|
||||
LOG(("ScriptLoadRequest (%p): Cannot open bytecode cache (rv = %X, output = %p)",
|
||||
aRequest, unsigned(rv), output.get()));
|
||||
return;
|
||||
}
|
||||
MOZ_ASSERT(output);
|
||||
auto closeOutStream = mozilla::MakeScopeExit([&]() {
|
||||
nsresult rv = output->Close();
|
||||
LOG(("ScriptLoadRequest (%p): Closing (rv = %X)",
|
||||
aRequest, unsigned(rv)));
|
||||
});
|
||||
|
||||
uint32_t n;
|
||||
rv = output->Write(reinterpret_cast<char*>(aRequest->mScriptBytecode.begin()),
|
||||
aRequest->mScriptBytecode.length(), &n);
|
||||
LOG(("ScriptLoadRequest (%p): Write bytecode cache (rv = %X, length = %u, written = %u)",
|
||||
aRequest, unsigned(rv), unsigned(aRequest->mScriptBytecode.length()), n));
|
||||
if (NS_FAILED(rv)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
nsScriptLoadRequest::DropBytecodeCacheReferences()
|
||||
{
|
||||
mCacheInfo = nullptr;
|
||||
mScript = nullptr;
|
||||
DropJSObjects(this);
|
||||
}
|
||||
|
||||
void
|
||||
nsScriptLoader::GiveUpBytecodeEncoding()
|
||||
{
|
||||
// Ideally we prefer to properly end the incremental encoder, such that we
|
||||
// would not keep a large buffer around. If we cannot, we fallback on the
|
||||
// removal of all request from the current list.
|
||||
nsCOMPtr<nsIScriptGlobalObject> globalObject = GetScriptGlobalObject();
|
||||
if (globalObject) {
|
||||
nsCOMPtr<nsIScriptContext> context = globalObject->GetScriptContext();
|
||||
if (context) {
|
||||
AutoEntryScript aes(globalObject, "give-up bytecode encoding", true);
|
||||
JS::RootedScript script(aes.cx());
|
||||
while (!mBytecodeEncodingQueue.isEmpty()) {
|
||||
RefPtr<nsScriptLoadRequest> request = mBytecodeEncodingQueue.StealFirst();
|
||||
LOG(("ScriptLoadRequest (%p): Cannot serialize bytecode", request.get()));
|
||||
script.set(request->mScript);
|
||||
Unused << JS::FinishIncrementalEncoding(aes.cx(), script);
|
||||
request->mScriptBytecode.clearAndFree();
|
||||
request->DropBytecodeCacheReferences();
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
while (!mBytecodeEncodingQueue.isEmpty()) {
|
||||
RefPtr<nsScriptLoadRequest> request = mBytecodeEncodingQueue.StealFirst();
|
||||
LOG(("ScriptLoadRequest (%p): Cannot serialize bytecode", request.get()));
|
||||
// Note: Do not clear the mScriptBytecode buffer, because the incremental
|
||||
// encoder owned by the ScriptSource object still has a reference to this
|
||||
// buffer. This reference would be removed as soon as the ScriptSource
|
||||
// object would be GC.
|
||||
request->mCacheInfo = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
bool
|
||||
nsScriptLoader::HasPendingRequests()
|
||||
{
|
||||
return mParserBlockingRequest ||
|
||||
!mXSLTRequests.isEmpty() ||
|
||||
!mLoadedAsyncRequests.isEmpty() ||
|
||||
!mNonAsyncExternalScriptInsertedRequests.isEmpty() ||
|
||||
!mDeferRequests.isEmpty() ||
|
||||
!mPendingChildLoaders.IsEmpty();
|
||||
}
|
||||
|
||||
void
|
||||
nsScriptLoader::ProcessPendingRequestsAsync()
|
||||
{
|
||||
if (mParserBlockingRequest ||
|
||||
!mXSLTRequests.isEmpty() ||
|
||||
!mLoadedAsyncRequests.isEmpty() ||
|
||||
!mNonAsyncExternalScriptInsertedRequests.isEmpty() ||
|
||||
!mDeferRequests.isEmpty() ||
|
||||
!mPendingChildLoaders.IsEmpty()) {
|
||||
if (HasPendingRequests()) {
|
||||
nsCOMPtr<nsIRunnable> task = NewRunnableMethod(this,
|
||||
&nsScriptLoader::ProcessPendingRequests);
|
||||
if (mDocument) {
|
||||
|
@ -3425,9 +3654,24 @@ nsScriptLoadHandler::OnStreamComplete(nsIIncrementalStreamLoader* aLoader,
|
|||
}
|
||||
}
|
||||
|
||||
// Everything went well, keep the CacheInfoChannel alive such that we can
|
||||
// later save the bytecode on the cache entry.
|
||||
if (NS_SUCCEEDED(rv) && mRequest->IsSource() && IsBytecodeCacheEnabled()) {
|
||||
mRequest->mCacheInfo = do_QueryInterface(channelRequest);
|
||||
LOG(("ScriptLoadRequest (%p): nsICacheInfoChannel = %p",
|
||||
mRequest.get(), mRequest->mCacheInfo.get()));
|
||||
}
|
||||
|
||||
// we have to mediate and use mRequest.
|
||||
return mScriptLoader->OnStreamComplete(aLoader, mRequest, aStatus, mSRIStatus,
|
||||
mSRIDataVerifier);
|
||||
rv = mScriptLoader->OnStreamComplete(aLoader, mRequest, aStatus, mSRIStatus,
|
||||
mSRIDataVerifier);
|
||||
|
||||
// In case of failure, clear the mCacheInfoChannel to avoid keeping it alive.
|
||||
if (NS_FAILED(rv)) {
|
||||
mRequest->mCacheInfo = nullptr;
|
||||
}
|
||||
|
||||
return rv;
|
||||
}
|
||||
|
||||
#undef LOG_ENABLED
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
#include "nsCycleCollectionParticipant.h"
|
||||
#include "nsTArray.h"
|
||||
#include "nsAutoPtr.h"
|
||||
#include "nsICacheInfoChannel.h"
|
||||
#include "nsIDocument.h"
|
||||
#include "nsIIncrementalStreamLoader.h"
|
||||
#include "nsURIHashKey.h"
|
||||
|
@ -99,7 +100,7 @@ public:
|
|||
}
|
||||
|
||||
NS_DECL_CYCLE_COLLECTING_ISUPPORTS
|
||||
NS_DECL_CYCLE_COLLECTION_CLASS(nsScriptLoadRequest)
|
||||
NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(nsScriptLoadRequest)
|
||||
|
||||
bool IsModuleRequest() const
|
||||
{
|
||||
|
@ -187,6 +188,7 @@ public:
|
|||
}
|
||||
|
||||
void MaybeCancelOffThreadScript();
|
||||
void DropBytecodeCacheReferences();
|
||||
|
||||
using super::getNext;
|
||||
using super::isInList;
|
||||
|
@ -208,6 +210,10 @@ public:
|
|||
void* mOffThreadToken; // Off-thread parsing token.
|
||||
nsString mSourceMapURL; // Holds source map url for loaded scripts
|
||||
|
||||
// Holds the top-level JSScript that corresponds to the current source, once
|
||||
// it is parsed, and planned to be saved in the bytecode cache.
|
||||
JS::Heap<JSScript*> mScript;
|
||||
|
||||
// Holds script text for non-inline scripts. Don't use nsString so we can give
|
||||
// ownership to jsapi.
|
||||
mozilla::Vector<char16_t> mScriptText;
|
||||
|
@ -225,6 +231,10 @@ public:
|
|||
const mozilla::CORSMode mCORSMode;
|
||||
const mozilla::dom::SRIMetadata mIntegrity;
|
||||
mozilla::net::ReferrerPolicy mReferrerPolicy;
|
||||
|
||||
// Holds the Cache information, which is used to register the bytecode
|
||||
// on the cache entry, such that we can load it the next time.
|
||||
nsCOMPtr<nsICacheInfoChannel> mCacheInfo;
|
||||
};
|
||||
|
||||
class nsScriptLoadRequestList : private mozilla::LinkedList<nsScriptLoadRequest>
|
||||
|
@ -468,6 +478,11 @@ public:
|
|||
nsresult aSRIStatus,
|
||||
mozilla::dom::SRICheckDataVerifier* aSRIDataVerifier);
|
||||
|
||||
/**
|
||||
* Returns wether any request is queued, and not executed yet.
|
||||
*/
|
||||
bool HasPendingRequests();
|
||||
|
||||
/**
|
||||
* Processes any pending requests that are ready for processing.
|
||||
*/
|
||||
|
@ -537,6 +552,13 @@ public:
|
|||
return mDocument->GetDocGroup();
|
||||
}
|
||||
|
||||
/**
|
||||
* Register the fact that we saw the load event, and that we need to save the
|
||||
* bytecode at the next loop cycle unless new scripts are waiting in the
|
||||
* pipeline.
|
||||
*/
|
||||
void LoadEventFired();
|
||||
|
||||
private:
|
||||
virtual ~nsScriptLoader();
|
||||
|
||||
|
@ -622,6 +644,30 @@ private:
|
|||
nsScriptLoadRequest* aRequest);
|
||||
nsresult EvaluateScript(nsScriptLoadRequest* aRequest);
|
||||
|
||||
/**
|
||||
* Queue the current script load request to be saved, when the page
|
||||
* initialization ends. The page initialization end is defined as being the
|
||||
* time when the load event got received, and when no more scripts are waiting
|
||||
* to be executed.
|
||||
*/
|
||||
void RegisterForBytecodeEncoding(nsScriptLoadRequest* aRequest);
|
||||
|
||||
/**
|
||||
* Check if all conditions are met, i-e that the onLoad event fired and that
|
||||
* no more script have to be processed. If all conditions are met, queue an
|
||||
* event to encode all the bytecode and save them on the cache.
|
||||
*/
|
||||
void MaybeTriggerBytecodeEncoding();
|
||||
|
||||
/**
|
||||
* Iterate over all script load request and save the bytecode of executed
|
||||
* functions on the cache provided by the channel.
|
||||
*/
|
||||
void EncodeBytecode();
|
||||
void EncodeRequestBytecode(JSContext* aCx, nsScriptLoadRequest* aRequest);
|
||||
|
||||
void GiveUpBytecodeEncoding();
|
||||
|
||||
already_AddRefed<nsIScriptGlobalObject> GetScriptGlobalObject();
|
||||
nsresult FillCompileOptionsForRequest(const mozilla::dom::AutoJSAPI& jsapi,
|
||||
nsScriptLoadRequest* aRequest,
|
||||
|
@ -676,6 +722,10 @@ private:
|
|||
nsScriptLoadRequestList mXSLTRequests;
|
||||
RefPtr<nsScriptLoadRequest> mParserBlockingRequest;
|
||||
|
||||
// List of script load request that are holding a buffer which has to be saved
|
||||
// on the cache.
|
||||
nsScriptLoadRequestList mBytecodeEncodingQueue;
|
||||
|
||||
// In mRequests, the additional information here is stored by the element.
|
||||
struct PreloadInfo {
|
||||
RefPtr<nsScriptLoadRequest> mRequest;
|
||||
|
@ -709,6 +759,7 @@ private:
|
|||
bool mDeferEnabled;
|
||||
bool mDocumentParsingDone;
|
||||
bool mBlockingDOMContentLoaded;
|
||||
bool mLoadEventFired;
|
||||
|
||||
// Module map
|
||||
nsRefPtrHashtable<nsURIHashKey, mozilla::GenericPromise::Private> mFetchingModules;
|
||||
|
|
|
@ -1082,6 +1082,12 @@ nsDocumentViewer::LoadComplete(nsresult aStatus)
|
|||
}
|
||||
}
|
||||
|
||||
// Release the JS bytecode cache from its wait on the load event, and
|
||||
// potentially dispatch the encoding of the bytecode.
|
||||
if (mDocument && mDocument->ScriptLoader()) {
|
||||
mDocument->ScriptLoader()->LoadEventFired();
|
||||
}
|
||||
|
||||
nsJSContext::LoadEnd();
|
||||
|
||||
// It's probably a good idea to GC soon since we have finished loading.
|
||||
|
|
Загрузка…
Ссылка в новой задаче