Issue #1603 - Part 1: Reorganize ScriptLoader/ScriptElement

- Moves scripting parts of DOM into 'dom/script'
- Renames nsScript{Loader/Element} to Script{Loader/Element}
- Adjusts all callers
This commit is contained in:
Moonchild 2020-06-30 11:32:07 +00:00 • committed by Roy Tam
commit f85b274e94
50 changed files with 554 additions and 514 deletions

View file

@ -0,0 +1,150 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "ScriptElement.h"
#include "ScriptLoader.h"
#include "mozilla/BasicEvents.h"
#include "mozilla/EventDispatcher.h"
#include "mozilla/dom/Element.h"
#include "nsContentUtils.h"
#include "nsPresContext.h"
#include "nsIParser.h"
#include "nsGkAtoms.h"
#include "nsContentSink.h"
using namespace mozilla;
using namespace mozilla::dom;
NS_IMETHODIMP
ScriptElement::ScriptAvailable(nsresult aResult,
nsIScriptElement *aElement,
bool aIsInline,
nsIURI *aURI,
int32_t aLineNo)
{
if (!aIsInline && NS_FAILED(aResult)) {
nsCOMPtr<nsIParser> parser = do_QueryReferent(mCreatorParser);
if (parser) {
parser->PushDefinedInsertionPoint();
}
nsresult rv = FireErrorEvent();
if (parser) {
parser->PopDefinedInsertionPoint();
}
return rv;
}
return NS_OK;
}
/* virtual */ nsresult
ScriptElement::FireErrorEvent()
{
nsCOMPtr<nsIContent> cont =
do_QueryInterface((nsIScriptElement*) this);
return nsContentUtils::DispatchTrustedEvent(cont->OwnerDoc(),
cont,
NS_LITERAL_STRING("error"),
false /* bubbles */,
false /* cancelable */);
}
NS_IMETHODIMP
ScriptElement::ScriptEvaluated(nsresult aResult,
nsIScriptElement *aElement,
bool aIsInline)
{
nsresult rv = NS_OK;
if (!aIsInline) {
nsCOMPtr<nsIContent> cont =
do_QueryInterface((nsIScriptElement*) this);
RefPtr<nsPresContext> presContext =
nsContentUtils::GetContextForContent(cont);
nsEventStatus status = nsEventStatus_eIgnore;
EventMessage message = NS_SUCCEEDED(aResult) ? eLoad : eLoadError;
WidgetEvent event(true, message);
// Load event doesn't bubble.
event.mFlags.mBubbles = (message != eLoad);
EventDispatcher::Dispatch(cont, presContext, &event, nullptr, &status);
}
return rv;
}
void
ScriptElement::CharacterDataChanged(nsIDocument *aDocument,
nsIContent* aContent,
CharacterDataChangeInfo* aInfo)
{
MaybeProcessScript();
}
void
ScriptElement::AttributeChanged(nsIDocument* aDocument,
Element* aElement,
int32_t aNameSpaceID,
nsIAtom* aAttribute,
int32_t aModType,
const nsAttrValue* aOldValue)
{
MaybeProcessScript();
}
void
ScriptElement::ContentAppended(nsIDocument* aDocument,
nsIContent* aContainer,
nsIContent* aFirstNewContent,
int32_t aNewIndexInContainer)
{
MaybeProcessScript();
}
void
ScriptElement::ContentInserted(nsIDocument *aDocument,
nsIContent* aContainer,
nsIContent* aChild,
int32_t aIndexInContainer)
{
MaybeProcessScript();
}
bool
ScriptElement::MaybeProcessScript()
{
nsCOMPtr<nsIContent> cont =
do_QueryInterface((nsIScriptElement*) this);
NS_ASSERTION(cont->DebugGetSlots()->mMutationObservers.Contains(this),
"You forgot to add self as observer");
if (mAlreadyStarted || !mDoneAddingChildren ||
!cont->GetComposedDoc() || mMalformed || !HasScriptContent()) {
return false;
}
FreezeUriAsyncDefer();
mAlreadyStarted = true;
nsIDocument* ownerDoc = cont->OwnerDoc();
nsCOMPtr<nsIParser> parser = ((nsIScriptElement*) this)->GetCreatorParser();
if (parser) {
nsCOMPtr<nsIContentSink> sink = parser->GetContentSink();
if (sink) {
nsCOMPtr<nsIDocument> parserDoc = do_QueryInterface(sink->GetTarget());
if (ownerDoc != parserDoc) {
// Willful violation of HTML5 as of 2010-12-01
return false;
}
}
}
RefPtr<ScriptLoader> loader = ownerDoc->ScriptLoader();
return loader->ProcessScriptElement(this);
}

View file

@ -0,0 +1,58 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef mozilla_dom_ScriptElement_h
#define mozilla_dom_ScriptElement_h
#include "mozilla/Attributes.h"
#include "nsIScriptLoaderObserver.h"
#include "nsIScriptElement.h"
#include "nsStubMutationObserver.h"
namespace mozilla {
namespace dom {
/**
* Baseclass useful for script elements (such as <xhtml:script> and
* <svg:script>). Currently the class assumes that only the 'src'
* attribute and the children of the class affect what script to execute.
*/
class ScriptElement : public nsIScriptElement,
public nsStubMutationObserver
{
public:
// nsIScriptLoaderObserver
NS_DECL_NSISCRIPTLOADEROBSERVER
// nsIMutationObserver
NS_DECL_NSIMUTATIONOBSERVER_CHARACTERDATACHANGED
NS_DECL_NSIMUTATIONOBSERVER_ATTRIBUTECHANGED
NS_DECL_NSIMUTATIONOBSERVER_CONTENTAPPENDED
NS_DECL_NSIMUTATIONOBSERVER_CONTENTINSERTED
explicit ScriptElement(FromParser aFromParser)
: nsIScriptElement(aFromParser)
{
}
virtual nsresult FireErrorEvent() override;
protected:
// Internal methods
/**
* Check if this element contains any script, linked or inline
*/
virtual bool HasScriptContent() = 0;
virtual bool MaybeProcessScript() override;
};
} // dom namespace
} // mozilla namespace
#endif // mozilla_dom_ScriptElement_h

3063
dom/script/ScriptLoader.cpp Normal file

File diff suppressed because it is too large Load diff

719
dom/script/ScriptLoader.h Normal file
View file

@ -0,0 +1,719 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/*
* A class that handles loading and evaluation of <script> elements.
*/
#ifndef mozilla_dom_ScriptLoader_h
#define mozilla_dom_ScriptLoader_h
#include "nsCOMPtr.h"
#include "nsRefPtrHashtable.h"
#include "nsIUnicodeDecoder.h"
#include "nsIScriptElement.h"
#include "nsCOMArray.h"
#include "nsCycleCollectionParticipant.h"
#include "nsTArray.h"
#include "nsAutoPtr.h"
#include "nsIDocument.h"
#include "nsIIncrementalStreamLoader.h"
#include "nsURIHashKey.h"
#include "mozilla/CORSMode.h"
#include "mozilla/dom/SRIMetadata.h"
#include "mozilla/dom/SRICheck.h"
#include "mozilla/LinkedList.h"
#include "mozilla/MozPromise.h"
#include "mozilla/net/ReferrerPolicy.h"
#include "mozilla/Vector.h"
class nsIURI;
namespace JS {
class SourceBufferHolder;
} // namespace JS
namespace mozilla {
namespace dom {
class AutoJSAPI;
class ModuleLoadRequest;
class ModuleScript;
class ScriptLoadRequestList;
//////////////////////////////////////////////////////////////
// Per-request data structure
//////////////////////////////////////////////////////////////
enum class ScriptKind {
Classic,
Module
};
class ScriptLoadRequest : public nsISupports,
private mozilla::LinkedListElement<ScriptLoadRequest>
{
typedef LinkedListElement<ScriptLoadRequest> super;
// Allow LinkedListElement<ScriptLoadRequest> to cast us to itself as needed.
friend class mozilla::LinkedListElement<ScriptLoadRequest>;
friend class ScriptLoadRequestList;
protected:
virtual ~ScriptLoadRequest();
public:
ScriptLoadRequest(ScriptKind aKind,
nsIScriptElement* aElement,
uint32_t aVersion,
mozilla::CORSMode aCORSMode,
const mozilla::dom::SRIMetadata &aIntegrity)
: mKind(aKind),
mElement(aElement),
mProgress(Progress::Loading),
mIsInline(true),
mHasSourceMapURL(false),
mIsDefer(false),
mIsAsync(false),
mIsNonAsyncScriptInserted(false),
mIsXSLT(false),
mIsCanceled(false),
mWasCompiledOMT(false),
mOffThreadToken(nullptr),
mScriptTextBuf(nullptr),
mScriptTextLength(0),
mJSVersion(aVersion),
mLineNo(1),
mCORSMode(aCORSMode),
mIntegrity(aIntegrity),
mReferrerPolicy(mozilla::net::RP_Default)
{
}
NS_DECL_CYCLE_COLLECTING_ISUPPORTS
NS_DECL_CYCLE_COLLECTION_CLASS(ScriptLoadRequest)
bool IsModuleRequest() const
{
return mKind == ScriptKind::Module;
}
ModuleLoadRequest* AsModuleRequest();
void FireScriptAvailable(nsresult aResult)
{
mElement->ScriptAvailable(aResult, mElement, mIsInline, mURI, mLineNo);
}
void FireScriptEvaluated(nsresult aResult)
{
mElement->ScriptEvaluated(aResult, mElement, mIsInline);
}
bool IsPreload()
{
return mElement == nullptr;
}
virtual void Cancel();
bool IsCanceled() const
{
return mIsCanceled;
}
virtual void SetReady();
void** OffThreadTokenPtr()
{
return mOffThreadToken ? &mOffThreadToken : nullptr;
}
enum class Progress {
Loading,
Compiling,
FetchingImports,
Ready
};
bool IsReadyToRun() const {
return mProgress == Progress::Ready;
}
bool IsLoading() const {
return mProgress == Progress::Loading;
}
bool InCompilingStage() const {
return mProgress == Progress::Compiling ||
(IsReadyToRun() && mWasCompiledOMT);
}
void MaybeCancelOffThreadScript();
using super::getNext;
using super::isInList;
const ScriptKind mKind;
nsCOMPtr<nsIScriptElement> mElement;
Progress mProgress; // Are we still waiting for a load to complete?
bool mIsInline; // Is the script inline or loaded?
bool mHasSourceMapURL; // Does the HTTP header have a source map url?
bool mIsDefer; // True if we live in mDeferRequests.
bool mIsAsync; // True if we live in mLoadingAsyncRequests or mLoadedAsyncRequests.
bool mIsNonAsyncScriptInserted; // True if we live in mNonAsyncExternalScriptInsertedRequests
bool mIsXSLT; // True if we live in mXSLTRequests.
bool mIsCanceled; // True if we have been explicitly canceled.
bool mWasCompiledOMT; // True if the script has been compiled off main thread.
void* mOffThreadToken; // Off-thread parsing token.
nsString mSourceMapURL; // Holds source map url for loaded scripts
char16_t* mScriptTextBuf; // Holds script text for non-inline scripts. Don't
size_t mScriptTextLength; // use nsString so we can give ownership to jsapi.
uint32_t mJSVersion;
nsCOMPtr<nsIURI> mURI;
nsCOMPtr<nsIPrincipal> mOriginPrincipal;
nsAutoCString mURL; // Keep the URI's filename alive during off thread parsing.
int32_t mLineNo;
const mozilla::CORSMode mCORSMode;
const mozilla::dom::SRIMetadata mIntegrity;
mozilla::net::ReferrerPolicy mReferrerPolicy;
};
class ScriptLoadRequestList : private mozilla::LinkedList<ScriptLoadRequest>
{
typedef mozilla::LinkedList<ScriptLoadRequest> super;
public:
~ScriptLoadRequestList();
void Clear();
#ifdef DEBUG
bool Contains(ScriptLoadRequest* aElem) const;
#endif // DEBUG
using super::getFirst;
using super::isEmpty;
void AppendElement(ScriptLoadRequest* aElem)
{
MOZ_ASSERT(!aElem->isInList());
NS_ADDREF(aElem);
insertBack(aElem);
}
MOZ_MUST_USE
already_AddRefed<ScriptLoadRequest> Steal(ScriptLoadRequest* aElem)
{
aElem->removeFrom(*this);
return dont_AddRef(aElem);
}
MOZ_MUST_USE
already_AddRefed<ScriptLoadRequest> StealFirst()
{
MOZ_ASSERT(!isEmpty());
return Steal(getFirst());
}
void Remove(ScriptLoadRequest* aElem)
{
aElem->removeFrom(*this);
NS_RELEASE(aElem);
}
};
//////////////////////////////////////////////////////////////
// Script loader implementation
//////////////////////////////////////////////////////////////
class ScriptLoader final : public nsISupports
{
class MOZ_STACK_CLASS AutoCurrentScriptUpdater
{
public:
AutoCurrentScriptUpdater(ScriptLoader* aScriptLoader,
nsIScriptElement* aCurrentScript)
: mOldScript(aScriptLoader->mCurrentScript)
, mScriptLoader(aScriptLoader)
{
mScriptLoader->mCurrentScript = aCurrentScript;
}
~AutoCurrentScriptUpdater()
{
mScriptLoader->mCurrentScript.swap(mOldScript);
}
private:
nsCOMPtr<nsIScriptElement> mOldScript;
ScriptLoader* mScriptLoader;
};
friend class ModuleLoadRequest;
friend class ScriptRequestProcessor;
friend class ScriptLoadHandler;
friend class AutoCurrentScriptUpdater;
public:
explicit ScriptLoader(nsIDocument* aDocument);
NS_DECL_CYCLE_COLLECTING_ISUPPORTS
NS_DECL_CYCLE_COLLECTION_CLASS(ScriptLoader)
/**
* The loader maintains a weak reference to the document with
* which it is initialized. This call forces the reference to
* be dropped.
*/
void DropDocumentReference()
{
mDocument = nullptr;
}
/**
* Add an observer for all scripts loaded through this loader.
*
* @param aObserver observer for all script processing.
*/
nsresult AddObserver(nsIScriptLoaderObserver* aObserver)
{
return mObservers.AppendObject(aObserver) ? NS_OK :
NS_ERROR_OUT_OF_MEMORY;
}
/**
* Remove an observer.
*
* @param aObserver observer to be removed
*/
void RemoveObserver(nsIScriptLoaderObserver* aObserver)
{
mObservers.RemoveObject(aObserver);
}
/**
* Process a script element. This will include both loading the
* source of the element if it is not inline and evaluating
* the script itself.
*
* If the script is an inline script that can be executed immediately
* (i.e. there are no other scripts pending) then ScriptAvailable
* and ScriptEvaluated will be called before the function returns.
*
* If true is returned the script could not be executed immediately.
* In this case ScriptAvailable is guaranteed to be called at a later
* point (as well as possibly ScriptEvaluated).
*
* @param aElement The element representing the script to be loaded and
* evaluated.
*/
bool ProcessScriptElement(nsIScriptElement* aElement);
/**
* Gets the currently executing script. This is useful if you want to
* generate a unique key based on the currently executing script.
*/
nsIScriptElement* GetCurrentScript()
{
return mCurrentScript;
}
nsIScriptElement* GetCurrentParserInsertedScript()
{
return mCurrentParserInsertedScript;
}
/**
* Whether the loader is enabled or not.
* When disabled, processing of new script elements is disabled.
* Any call to ProcessScriptElement() will return false. Note that
* this DOES NOT disable currently loading or executing scripts.
*/
bool GetEnabled()
{
return mEnabled;
}
void SetEnabled(bool aEnabled)
{
if (!mEnabled && aEnabled) {
ProcessPendingRequestsAsync();
}
mEnabled = aEnabled;
}
/**
* Add/remove a blocker for parser-blocking scripts (and XSLT
* scripts). Blockers will stop such scripts from executing, but not from
* loading.
*/
void AddParserBlockingScriptExecutionBlocker()
{
++mParserBlockingBlockerCount;
}
void RemoveParserBlockingScriptExecutionBlocker()
{
if (!--mParserBlockingBlockerCount && ReadyToExecuteScripts()) {
ProcessPendingRequestsAsync();
}
}
/**
* Add/remove a blocker for execution of all scripts. Blockers will stop
* scripts from executing, but not from loading.
*/
void AddExecuteBlocker()
{
++mBlockerCount;
}
void RemoveExecuteBlocker()
{
MOZ_ASSERT(mBlockerCount);
if (!--mBlockerCount) {
ProcessPendingRequestsAsync();
}
}
/**
* Convert the given buffer to a UTF-16 string.
* @param aChannel Channel corresponding to the data. May be null.
* @param aData The data to convert
* @param aLength Length of the data
* @param aHintCharset Hint for the character set (e.g., from a charset
* attribute). May be the empty string.
* @param aDocument Document which the data is loaded for. Must not be
* null.
* @param aBufOut [out] char16_t array allocated by ConvertToUTF16 and
* containing data converted to unicode. Caller must
* js_free() this data when no longer needed.
* @param aLengthOut [out] Length of array returned in aBufOut in number
* of char16_t code units.
*/
static nsresult ConvertToUTF16(nsIChannel* aChannel, const uint8_t* aData,
uint32_t aLength,
const nsAString& aHintCharset,
nsIDocument* aDocument,
char16_t*& aBufOut, size_t& aLengthOut);
/**
* Handle the completion of a stream. This is called by the
* ScriptLoadHandler object which observes the IncrementalStreamLoader
* loading the script.
*/
nsresult OnStreamComplete(nsIIncrementalStreamLoader* aLoader,
nsISupports* aContext,
nsresult aChannelStatus,
nsresult aSRIStatus,
mozilla::Vector<char16_t> &aString,
mozilla::dom::SRICheckDataVerifier* aSRIDataVerifier);
/**
* Processes any pending requests that are ready for processing.
*/
void ProcessPendingRequests();
/**
* Starts deferring deferred scripts and puts them in the mDeferredRequests
* queue instead.
*/
void BeginDeferringScripts()
{
mDeferEnabled = true;
if (mDocument) {
mDocument->BlockOnload();
}
}
/**
* Notifies the script loader that parsing is done. If aTerminated is true,
* this will drop any pending scripts that haven't run yet. Otherwise, it
* will stops deferring scripts and immediately processes the
* mDeferredRequests queue.
*
* WARNING: This function will synchronously execute content scripts, so be
* prepared that the world might change around you.
*/
void ParsingComplete(bool aTerminated);
/**
* Returns the number of pending scripts, deferred or not.
*/
uint32_t HasPendingOrCurrentScripts()
{
return mCurrentScript || mParserBlockingRequest;
}
/**
* Adds aURI to the preload list and starts loading it.
*
* @param aURI The URI of the external script.
* @param aCharset The charset parameter for the script.
* @param aType The type parameter for the script.
* @param aCrossOrigin The crossorigin attribute for the script.
* Void if not present.
* @param aIntegrity The expect hash url, if avail, of the request
* @param aScriptFromHead Whether or not the script was a child of head
*/
virtual void PreloadURI(nsIURI *aURI, const nsAString &aCharset,
const nsAString &aType,
const nsAString &aCrossOrigin,
const nsAString& aIntegrity,
bool aScriptFromHead,
const mozilla::net::ReferrerPolicy aReferrerPolicy);
/**
* Process a request that was deferred so that the script could be compiled
* off thread.
*/
nsresult ProcessOffThreadRequest(ScriptLoadRequest *aRequest);
bool AddPendingChildLoader(ScriptLoader* aChild) {
return mPendingChildLoaders.AppendElement(aChild) != nullptr;
}
private:
virtual ~ScriptLoader();
ScriptLoadRequest* CreateLoadRequest(
ScriptKind aKind,
nsIScriptElement* aElement,
uint32_t aVersion,
mozilla::CORSMode aCORSMode,
const mozilla::dom::SRIMetadata &aIntegrity);
/**
* Unblocks the creator parser of the parser-blocking scripts.
*/
void UnblockParser(ScriptLoadRequest* aParserBlockingRequest);
/**
* Asynchronously resumes the creator parser of the parser-blocking scripts.
*/
void ContinueParserAsync(ScriptLoadRequest* aParserBlockingRequest);
/**
* Helper function to check the content policy for a given request.
*/
static nsresult CheckContentPolicy(nsIDocument* aDocument,
nsISupports *aContext,
nsIURI *aURI,
const nsAString &aType,
bool aIsPreLoad);
/**
* Start a load for aRequest's URI.
*/
nsresult StartLoad(ScriptLoadRequest *aRequest, const nsAString &aType,
bool aScriptFromHead);
/**
* Process any pending requests asynchronously (i.e. off an event) if there
* are any. Note that this is a no-op if there aren't any currently pending
* requests.
*
* This function is virtual to allow cross-library calls to SetEnabled()
*/
virtual void ProcessPendingRequestsAsync();
/**
* If true, the loader is ready to execute parser-blocking scripts, and so are
* all its ancestors. If the loader itself is ready but some ancestor is not,
* this function will add an execute blocker and ask the ancestor to remove it
* once it becomes ready.
*/
bool ReadyToExecuteParserBlockingScripts();
/**
* Return whether just this loader is ready to execute parser-blocking
* scripts.
*/
bool SelfReadyToExecuteParserBlockingScripts()
{
return ReadyToExecuteScripts() && !mParserBlockingBlockerCount;
}
/**
* Return whether this loader is ready to execute scripts in general.
*/
bool ReadyToExecuteScripts()
{
return mEnabled && !mBlockerCount;
}
nsresult AttemptAsyncScriptCompile(ScriptLoadRequest* aRequest);
nsresult ProcessRequest(ScriptLoadRequest* aRequest);
nsresult CompileOffThreadOrProcessRequest(ScriptLoadRequest* aRequest);
void FireScriptAvailable(nsresult aResult,
ScriptLoadRequest* aRequest);
void FireScriptEvaluated(nsresult aResult,
ScriptLoadRequest* aRequest);
nsresult EvaluateScript(ScriptLoadRequest* aRequest);
already_AddRefed<nsIScriptGlobalObject> GetScriptGlobalObject();
nsresult FillCompileOptionsForRequest(const mozilla::dom::AutoJSAPI& jsapi,
ScriptLoadRequest* aRequest,
JS::Handle<JSObject*> aScopeChain,
JS::CompileOptions* aOptions);
uint32_t NumberOfProcessors();
nsresult PrepareLoadedRequest(ScriptLoadRequest* aRequest,
nsIIncrementalStreamLoader* aLoader,
nsresult aStatus,
mozilla::Vector<char16_t> &aString);
void AddDeferRequest(ScriptLoadRequest* aRequest);
bool MaybeRemovedDeferRequests();
void MaybeMoveToLoadedList(ScriptLoadRequest* aRequest);
JS::SourceBufferHolder GetScriptSource(ScriptLoadRequest* aRequest,
nsAutoString& inlineData);
bool ModuleScriptsEnabled();
void SetModuleFetchStarted(ModuleLoadRequest *aRequest);
void SetModuleFetchFinishedAndResumeWaitingRequests(ModuleLoadRequest *aRequest,
nsresult aResult);
bool IsFetchingModule(ModuleLoadRequest *aRequest) const;
bool ModuleMapContainsModule(ModuleLoadRequest *aRequest) const;
RefPtr<mozilla::GenericPromise> WaitForModuleFetch(ModuleLoadRequest *aRequest);
ModuleScript* GetFetchedModule(nsIURI* aURL) const;
friend bool
HostResolveImportedModule(JSContext* aCx, unsigned argc, JS::Value* vp);
nsresult CreateModuleScript(ModuleLoadRequest* aRequest);
nsresult ProcessFetchedModuleSource(ModuleLoadRequest* aRequest);
void ProcessLoadedModuleTree(ModuleLoadRequest* aRequest);
bool InstantiateModuleTree(ModuleLoadRequest* aRequest);
void StartFetchingModuleDependencies(ModuleLoadRequest* aRequest);
RefPtr<mozilla::GenericPromise>
StartFetchingModuleAndDependencies(ModuleLoadRequest* aRequest, nsIURI* aURI);
nsIDocument* mDocument; // [WEAK]
nsCOMArray<nsIScriptLoaderObserver> mObservers;
ScriptLoadRequestList mNonAsyncExternalScriptInsertedRequests;
// mLoadingAsyncRequests holds async requests while they're loading; when they
// have been loaded they are moved to mLoadedAsyncRequests.
ScriptLoadRequestList mLoadingAsyncRequests;
ScriptLoadRequestList mLoadedAsyncRequests;
ScriptLoadRequestList mDeferRequests;
ScriptLoadRequestList mXSLTRequests;
RefPtr<ScriptLoadRequest> mParserBlockingRequest;
// In mRequests, the additional information here is stored by the element.
struct PreloadInfo {
RefPtr<ScriptLoadRequest> mRequest;
nsString mCharset;
};
friend void ImplCycleCollectionUnlink(ScriptLoader::PreloadInfo& aField);
friend void ImplCycleCollectionTraverse(nsCycleCollectionTraversalCallback& aCallback,
ScriptLoader::PreloadInfo& aField,
const char* aName, uint32_t aFlags);
struct PreloadRequestComparator {
bool Equals(const PreloadInfo &aPi, ScriptLoadRequest * const &aRequest)
const
{
return aRequest == aPi.mRequest;
}
};
struct PreloadURIComparator {
bool Equals(const PreloadInfo &aPi, nsIURI * const &aURI) const;
};
nsTArray<PreloadInfo> mPreloads;
nsCOMPtr<nsIScriptElement> mCurrentScript;
nsCOMPtr<nsIScriptElement> mCurrentParserInsertedScript;
nsTArray< RefPtr<ScriptLoader> > mPendingChildLoaders;
uint32_t mParserBlockingBlockerCount;
uint32_t mBlockerCount;
uint32_t mNumberOfProcessors;
bool mEnabled;
bool mDeferEnabled;
bool mDocumentParsingDone;
bool mBlockingDOMContentLoaded;
// Module map
nsRefPtrHashtable<nsURIHashKey, mozilla::GenericPromise::Private> mFetchingModules;
nsRefPtrHashtable<nsURIHashKey, ModuleScript> mFetchedModules;
nsCOMPtr<nsIConsoleReportCollector> mReporter;
};
class ScriptLoadHandler final : public nsIIncrementalStreamLoaderObserver
{
public:
explicit ScriptLoadHandler(ScriptLoader* aScriptLoader,
ScriptLoadRequest *aRequest,
mozilla::dom::SRICheckDataVerifier *aSRIDataVerifier);
NS_DECL_ISUPPORTS
NS_DECL_NSIINCREMENTALSTREAMLOADEROBSERVER
private:
virtual ~ScriptLoadHandler();
/*
* Try to decode some raw data.
*/
nsresult TryDecodeRawData(const uint8_t* aData, uint32_t aDataLength,
bool aEndOfStream);
/*
* Discover the charset by looking at the stream data, the script
* tag, and other indicators. Returns true if charset has been
* discovered.
*/
bool EnsureDecoder(nsIIncrementalStreamLoader *aLoader,
const uint8_t* aData, uint32_t aDataLength,
bool aEndOfStream);
// ScriptLoader which will handle the parsed script.
RefPtr<ScriptLoader> mScriptLoader;
// The ScriptLoadRequest for this load.
RefPtr<ScriptLoadRequest> mRequest;
// SRI data verifier.
nsAutoPtr<mozilla::dom::SRICheckDataVerifier> mSRIDataVerifier;
// Status of SRI data operations.
nsresult mSRIStatus;
// Unicode decoder for charset.
nsCOMPtr<nsIUnicodeDecoder> mDecoder;
// Accumulated decoded char buffer.
mozilla::Vector<char16_t> mBuffer;
};
class nsAutoScriptLoaderDisabler
{
public:
explicit nsAutoScriptLoaderDisabler(nsIDocument* aDoc)
{
mLoader = aDoc->ScriptLoader();
mWasEnabled = mLoader->GetEnabled();
if (mWasEnabled) {
mLoader->SetEnabled(false);
}
}
~nsAutoScriptLoaderDisabler()
{
if (mWasEnabled) {
mLoader->SetEnabled(true);
}
}
bool mWasEnabled;
RefPtr<ScriptLoader> mLoader;
};
} // namespace dom
} // namespace mozilla
#endif //mozilla_dom_ScriptLoader_h

View file

@ -0,0 +1,839 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "mozilla/dom/ScriptSettings.h"
#include "mozilla/ThreadLocal.h"
#include "mozilla/Assertions.h"
#include "mozilla/CycleCollectedJSContext.h"
#include "jsapi.h"
#include "xpcpublic.h"
#include "nsIGlobalObject.h"
#include "nsIDocShell.h"
#include "nsIScriptGlobalObject.h"
#include "nsIScriptContext.h"
#include "nsContentUtils.h"
#include "nsGlobalWindow.h"
#include "nsPIDOMWindow.h"
#include "nsTArray.h"
#include "nsJSUtils.h"
#include "nsDOMJSUtils.h"
#include "WorkerPrivate.h"
namespace mozilla {
namespace dom {
static MOZ_THREAD_LOCAL(ScriptSettingsStackEntry*) sScriptSettingsTLS;
static bool sScriptSettingsTLSInitialized;
class ScriptSettingsStack {
public:
static ScriptSettingsStackEntry* Top() {
return sScriptSettingsTLS.get();
}
static void Push(ScriptSettingsStackEntry *aEntry) {
MOZ_ASSERT(!aEntry->mOlder);
// Whenever JSAPI use is disabled, the next stack entry pushed must
// not be an AutoIncumbentScript.
MOZ_ASSERT_IF(!Top() || Top()->NoJSAPI(),
!aEntry->IsIncumbentScript());
// Whenever the top entry is not an incumbent canidate, the next stack entry
// pushed must not be an AutoIncumbentScript.
MOZ_ASSERT_IF(Top() && !Top()->IsIncumbentCandidate(),
!aEntry->IsIncumbentScript());
aEntry->mOlder = Top();
sScriptSettingsTLS.set(aEntry);
}
static void Pop(ScriptSettingsStackEntry *aEntry) {
MOZ_ASSERT(aEntry == Top());
sScriptSettingsTLS.set(aEntry->mOlder);
}
static nsIGlobalObject* IncumbentGlobal() {
ScriptSettingsStackEntry *entry = Top();
while (entry) {
if (entry->IsIncumbentCandidate()) {
return entry->mGlobalObject;
}
entry = entry->mOlder;
}
return nullptr;
}
static ScriptSettingsStackEntry* EntryPoint() {
ScriptSettingsStackEntry *entry = Top();
while (entry) {
if (entry->IsEntryCandidate()) {
return entry;
}
entry = entry->mOlder;
}
return nullptr;
}
static nsIGlobalObject* EntryGlobal() {
ScriptSettingsStackEntry *entry = EntryPoint();
if (!entry) {
return nullptr;
}
return entry->mGlobalObject;
}
#ifdef DEBUG
static ScriptSettingsStackEntry* TopNonIncumbentScript() {
ScriptSettingsStackEntry *entry = Top();
while (entry) {
if (!entry->IsIncumbentScript()) {
return entry;
}
entry = entry->mOlder;
}
return nullptr;
}
#endif // DEBUG
};
static unsigned long gRunToCompletionListeners = 0;
void
UseEntryScriptProfiling()
{
MOZ_ASSERT(NS_IsMainThread());
++gRunToCompletionListeners;
}
void
UnuseEntryScriptProfiling()
{
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(gRunToCompletionListeners > 0);
--gRunToCompletionListeners;
}
void
InitScriptSettings()
{
bool success = sScriptSettingsTLS.init();
if (!success) {
MOZ_CRASH();
}
sScriptSettingsTLS.set(nullptr);
sScriptSettingsTLSInitialized = true;
}
void
DestroyScriptSettings()
{
MOZ_ASSERT(sScriptSettingsTLS.get() == nullptr);
}
bool
ScriptSettingsInitialized()
{
return sScriptSettingsTLSInitialized;
}
ScriptSettingsStackEntry::ScriptSettingsStackEntry(nsIGlobalObject *aGlobal,
Type aType)
: mGlobalObject(aGlobal)
, mType(aType)
, mOlder(nullptr)
{
MOZ_ASSERT_IF(IsIncumbentCandidate() && !NoJSAPI(), mGlobalObject);
MOZ_ASSERT(!mGlobalObject || mGlobalObject->GetGlobalJSObject(),
"Must have an actual JS global for the duration on the stack");
MOZ_ASSERT(!mGlobalObject ||
JS_IsGlobalObject(mGlobalObject->GetGlobalJSObject()),
"No outer windows allowed");
}
ScriptSettingsStackEntry::~ScriptSettingsStackEntry()
{
// We must have an actual JS global for the entire time this is on the stack.
MOZ_ASSERT_IF(mGlobalObject, mGlobalObject->GetGlobalJSObject());
}
// If the entry or incumbent global ends up being something that the subject
// principal doesn't subsume, we don't want to use it. This never happens on
// the web, but can happen with asymmetric privilege relationships (i.e.
// nsExpandedPrincipal and System Principal).
//
// The most correct thing to use instead would be the topmost global on the
// callstack whose principal is subsumed by the subject principal. But that's
// hard to compute, so we just substitute the global of the current
// compartment. In practice, this is fine.
//
// Note that in particular things like:
//
// |SpecialPowers.wrap(crossOriginWindow).eval(open())|
//
// trigger this case. Although both the entry global and the current global
// have normal principals, the use of Gecko-specific System-Principaled JS
// puts the code from two different origins on the callstack at once, which
// doesn't happen normally on the web.
static nsIGlobalObject*
ClampToSubject(nsIGlobalObject* aGlobalOrNull)
{
if (!aGlobalOrNull || !NS_IsMainThread()) {
return aGlobalOrNull;
}
nsIPrincipal* globalPrin = aGlobalOrNull->PrincipalOrNull();
NS_ENSURE_TRUE(globalPrin, GetCurrentGlobal());
if (!nsContentUtils::SubjectPrincipalOrSystemIfNativeCaller()->SubsumesConsideringDomain(globalPrin)) {
return GetCurrentGlobal();
}
return aGlobalOrNull;
}
nsIGlobalObject*
GetEntryGlobal()
{
return ClampToSubject(ScriptSettingsStack::EntryGlobal());
}
nsIDocument*
GetEntryDocument()
{
nsIGlobalObject* global = GetEntryGlobal();
nsCOMPtr<nsPIDOMWindowInner> entryWin = do_QueryInterface(global);
// If our entry global isn't a window, see if it's an addon scope associated
// with a window. If it is, the caller almost certainly wants that rather
// than null.
if (!entryWin && global) {
if (auto* win = xpc::AddonWindowOrNull(global->GetGlobalJSObject())) {
entryWin = win->AsInner();
}
}
return entryWin ? entryWin->GetExtantDoc() : nullptr;
}
nsIGlobalObject*
GetIncumbentGlobal()
{
// We need the current JSContext in order to check the JS for
// scripted frames that may have appeared since anyone last
// manipulated the stack. If it's null, that means that there
// must be no entry global on the stack, and therefore no incumbent
// global either.
JSContext *cx = nsContentUtils::GetCurrentJSContextForThread();
if (!cx) {
MOZ_ASSERT(ScriptSettingsStack::EntryGlobal() == nullptr);
return nullptr;
}
// See what the JS engine has to say. If we've got a scripted caller
// override in place, the JS engine will lie to us and pretend that
// there's nothing on the JS stack, which will cause us to check the
// incumbent script stack below.
if (JSObject *global = JS::GetScriptedCallerGlobal(cx)) {
return ClampToSubject(xpc::NativeGlobal(global));
}
// Ok, nothing from the JS engine. Let's use whatever's on the
// explicit stack.
return ClampToSubject(ScriptSettingsStack::IncumbentGlobal());
}
nsIGlobalObject*
GetCurrentGlobal()
{
JSContext *cx = nsContentUtils::GetCurrentJSContextForThread();
if (!cx) {
return nullptr;
}
JSObject *global = JS::CurrentGlobalOrNull(cx);
if (!global) {
return nullptr;
}
return xpc::NativeGlobal(global);
}
nsIPrincipal*
GetWebIDLCallerPrincipal()
{
MOZ_ASSERT(NS_IsMainThread());
ScriptSettingsStackEntry *entry = ScriptSettingsStack::EntryPoint();
// If we have an entry point that is not NoJSAPI, we know it must be an
// AutoEntryScript.
if (!entry || entry->NoJSAPI()) {
return nullptr;
}
AutoEntryScript* aes = static_cast<AutoEntryScript*>(entry);
return aes->mWebIDLCallerPrincipal;
}
bool
IsJSAPIActive()
{
ScriptSettingsStackEntry* topEntry = ScriptSettingsStack::Top();
return topEntry && !topEntry->NoJSAPI();
}
namespace danger {
JSContext*
GetJSContext()
{
return CycleCollectedJSContext::Get()->Context();
}
} // namespace danger
JS::RootingContext*
RootingCx()
{
return CycleCollectedJSContext::Get()->RootingCx();
}
AutoJSAPI::AutoJSAPI()
: ScriptSettingsStackEntry(nullptr, eJSAPI)
, mCx(nullptr)
, mIsMainThread(false) // For lack of anything better
{
}
AutoJSAPI::~AutoJSAPI()
{
if (!mCx) {
// No need to do anything here: we never managed to Init, so can't have an
// exception on our (nonexistent) JSContext. We also don't need to restore
// any state on it. Finally, we never made it to pushing outselves onto the
// ScriptSettingsStack, so shouldn't pop.
MOZ_ASSERT(ScriptSettingsStack::Top() != this);
return;
}
ReportException();
if (mOldWarningReporter.isSome()) {
JS::SetWarningReporter(cx(), mOldWarningReporter.value());
}
// Leave the request before popping.
if (mIsMainThread) {
mAutoRequest.reset();
}
ScriptSettingsStack::Pop(this);
}
void
WarningOnlyErrorReporter(JSContext* aCx, JSErrorReport* aRep);
void
AutoJSAPI::InitInternal(nsIGlobalObject* aGlobalObject, JSObject* aGlobal,
JSContext* aCx, bool aIsMainThread)
{
MOZ_ASSERT(aCx);
MOZ_ASSERT(aCx == danger::GetJSContext());
MOZ_ASSERT(aIsMainThread == NS_IsMainThread());
MOZ_ASSERT(bool(aGlobalObject) == bool(aGlobal));
MOZ_ASSERT_IF(aGlobalObject, aGlobalObject->GetGlobalJSObject() == aGlobal);
#ifdef DEBUG
bool haveException = JS_IsExceptionPending(aCx);
#endif // DEBUG
mCx = aCx;
mIsMainThread = aIsMainThread;
mGlobalObject = aGlobalObject;
if (aIsMainThread) {
// We _could_ just unconditionally emplace mAutoRequest here. It's just not
// needed on worker threads, and we're hoping to kill it on the main thread
// too.
mAutoRequest.emplace(mCx);
}
if (aGlobal) {
JS::ExposeObjectToActiveJS(aGlobal);
}
mAutoNullableCompartment.emplace(mCx, aGlobal);
ScriptSettingsStack::Push(this);
mOldWarningReporter.emplace(JS::GetWarningReporter(aCx));
JS::SetWarningReporter(aCx, WarningOnlyErrorReporter);
#ifdef DEBUG
if (haveException) {
JS::Rooted<JS::Value> exn(aCx);
JS_GetPendingException(aCx, &exn);
JS_ClearPendingException(aCx);
if (exn.isObject()) {
JS::Rooted<JSObject*> exnObj(aCx, &exn.toObject());
nsAutoJSString stack, filename, name, message;
int32_t line;
JS::Rooted<JS::Value> tmp(aCx);
if (!JS_GetProperty(aCx, exnObj, "filename", &tmp)) {
JS_ClearPendingException(aCx);
}
if (tmp.isUndefined()) {
if (!JS_GetProperty(aCx, exnObj, "fileName", &tmp)) {
JS_ClearPendingException(aCx);
}
}
if (!filename.init(aCx, tmp)) {
JS_ClearPendingException(aCx);
}
if (!JS_GetProperty(aCx, exnObj, "stack", &tmp) ||
!stack.init(aCx, tmp)) {
JS_ClearPendingException(aCx);
}
if (!JS_GetProperty(aCx, exnObj, "name", &tmp) ||
!name.init(aCx, tmp)) {
JS_ClearPendingException(aCx);
}
if (!JS_GetProperty(aCx, exnObj, "message", &tmp) ||
!message.init(aCx, tmp)) {
JS_ClearPendingException(aCx);
}
if (!JS_GetProperty(aCx, exnObj, "lineNumber", &tmp) ||
!JS::ToInt32(aCx, tmp, &line)) {
JS_ClearPendingException(aCx);
line = 0;
}
printf_stderr("PREEXISTING EXCEPTION OBJECT: '%s: %s'\n%s:%d\n%s\n",
NS_ConvertUTF16toUTF8(name).get(),
NS_ConvertUTF16toUTF8(message).get(),
NS_ConvertUTF16toUTF8(filename).get(), line,
NS_ConvertUTF16toUTF8(stack).get());
} else {
// It's a primitive... not much we can do other than stringify it.
nsAutoJSString exnStr;
if (!exnStr.init(aCx, exn)) {
JS_ClearPendingException(aCx);
}
printf_stderr("PREEXISTING EXCEPTION PRIMITIVE: %s\n",
NS_ConvertUTF16toUTF8(exnStr).get());
}
MOZ_ASSERT(false, "We had an exception; we should not have");
}
#endif // DEBUG
}
AutoJSAPI::AutoJSAPI(nsIGlobalObject* aGlobalObject,
bool aIsMainThread,
Type aType)
: ScriptSettingsStackEntry(aGlobalObject, aType)
, mIsMainThread(aIsMainThread)
{
MOZ_ASSERT(aGlobalObject);
MOZ_ASSERT(aGlobalObject->GetGlobalJSObject(), "Must have a JS global");
MOZ_ASSERT(aIsMainThread == NS_IsMainThread());
InitInternal(aGlobalObject, aGlobalObject->GetGlobalJSObject(),
danger::GetJSContext(), aIsMainThread);
}
void
AutoJSAPI::Init()
{
MOZ_ASSERT(!mCx, "An AutoJSAPI should only be initialised once");
InitInternal(/* aGlobalObject */ nullptr, /* aGlobal */ nullptr,
danger::GetJSContext(), NS_IsMainThread());
}
bool
AutoJSAPI::Init(nsIGlobalObject* aGlobalObject, JSContext* aCx)
{
MOZ_ASSERT(!mCx, "An AutoJSAPI should only be initialised once");
MOZ_ASSERT(aCx);
if (NS_WARN_IF(!aGlobalObject)) {
return false;
}
JSObject* global = aGlobalObject->GetGlobalJSObject();
if (NS_WARN_IF(!global)) {
return false;
}
InitInternal(aGlobalObject, global, aCx, NS_IsMainThread());
return true;
}
bool
AutoJSAPI::Init(nsIGlobalObject* aGlobalObject)
{
return Init(aGlobalObject, danger::GetJSContext());
}
bool
AutoJSAPI::Init(JSObject* aObject)
{
nsIGlobalObject* global = nullptr;
if (aObject)
global = xpc::NativeGlobal(aObject);
if (global)
return Init(global);
else
return false;
}
bool
AutoJSAPI::Init(nsPIDOMWindowInner* aWindow, JSContext* aCx)
{
return Init(nsGlobalWindow::Cast(aWindow), aCx);
}
bool
AutoJSAPI::Init(nsPIDOMWindowInner* aWindow)
{
return Init(nsGlobalWindow::Cast(aWindow));
}
bool
AutoJSAPI::Init(nsGlobalWindow* aWindow, JSContext* aCx)
{
return Init(static_cast<nsIGlobalObject*>(aWindow), aCx);
}
bool
AutoJSAPI::Init(nsGlobalWindow* aWindow)
{
return Init(static_cast<nsIGlobalObject*>(aWindow));
}
// Even with autoJSAPIOwnsErrorReporting, the JS engine still sends warning
// reports to the JSErrorReporter as soon as they are generated. These go
// directly to the console, so we can handle them easily here.
//
// Eventually, SpiderMonkey will have a special-purpose callback for warnings
// only.
void
WarningOnlyErrorReporter(JSContext* aCx, JSErrorReport* aRep)
{
MOZ_ASSERT(JSREPORT_IS_WARNING(aRep->flags));
if (!NS_IsMainThread()) {
// Reporting a warning on workers is a bit complicated because we have to
// climb our parent chain until we get to the main thread. So go ahead and
// just go through the worker ReportError codepath here.
//
// That said, it feels like we should be able to short-circuit things a bit
// here by posting an appropriate runnable to the main thread directly...
// Worth looking into sometime.
workers::WorkerPrivate* worker = workers::GetWorkerPrivateFromContext(aCx);
MOZ_ASSERT(worker);
worker->ReportError(aCx, JS::ConstUTF8CharsZ(), aRep);
return;
}
RefPtr<xpc::ErrorReport> xpcReport = new xpc::ErrorReport();
nsGlobalWindow* win = xpc::CurrentWindowOrNull(aCx);
if (!win) {
// We run addons in a separate privileged compartment, but if we're in an
// addon compartment we should log warnings to the console of the associated
// DOM Window.
win = xpc::AddonWindowOrNull(JS::CurrentGlobalOrNull(aCx));
}
xpcReport->Init(aRep, nullptr, nsContentUtils::IsCallerChrome(),
win ? win->AsInner()->WindowID() : 0);
xpcReport->LogToConsole();
}
void
AutoJSAPI::ReportException()
{
if (!HasException()) {
return;
}
// AutoJSAPI uses a JSAutoNullableCompartment, and may be in a null
// compartment when the destructor is called. However, the JS engine
// requires us to be in a compartment when we fetch the pending exception.
// In this case, we enter the privileged junk scope and don't dispatch any
// error events.
JS::Rooted<JSObject*> errorGlobal(cx(), JS::CurrentGlobalOrNull(cx()));
if (!errorGlobal) {
if (mIsMainThread) {
errorGlobal = xpc::PrivilegedJunkScope();
} else {
errorGlobal = workers::GetCurrentThreadWorkerGlobal();
}
}
JSAutoCompartment ac(cx(), errorGlobal);
JS::Rooted<JS::Value> exn(cx());
js::ErrorReport jsReport(cx());
if (StealException(&exn) &&
jsReport.init(cx(), exn, js::ErrorReport::WithSideEffects)) {
if (mIsMainThread) {
RefPtr<xpc::ErrorReport> xpcReport = new xpc::ErrorReport();
RefPtr<nsGlobalWindow> win = xpc::WindowGlobalOrNull(errorGlobal);
if (!win) {
// We run addons in a separate privileged compartment, but they still
// expect to trigger the onerror handler of their associated DOM Window.
win = xpc::AddonWindowOrNull(errorGlobal);
}
nsPIDOMWindowInner* inner = win ? win->AsInner() : nullptr;
xpcReport->Init(jsReport.report(), jsReport.toStringResult().c_str(),
nsContentUtils::IsCallerChrome(),
inner ? inner->WindowID() : 0);
if (inner && jsReport.report()->errorNumber != JSMSG_OUT_OF_MEMORY) {
JS::RootingContext* rcx = JS::RootingContext::get(cx());
DispatchScriptErrorEvent(inner, rcx, xpcReport, exn);
} else {
JS::Rooted<JSObject*> stack(cx(),
xpc::FindExceptionStackForConsoleReport(inner, exn));
xpcReport->LogToConsoleWithStack(stack);
}
} else {
// On a worker, we just use the worker error reporting mechanism and don't
// bother with xpc::ErrorReport. This will ensure that all the right
// events (which are a lot more complicated than in the window case) get
// fired.
workers::WorkerPrivate* worker = workers::GetCurrentThreadWorkerPrivate();
MOZ_ASSERT(worker);
MOZ_ASSERT(worker->GetJSContext() == cx());
// Before invoking ReportError, put the exception back on the context,
// because it may want to put it in its error events and has no other way
// to get hold of it. After we invoke ReportError, clear the exception on
// cx(), just in case ReportError didn't.
JS_SetPendingException(cx(), exn);
worker->ReportError(cx(), jsReport.toStringResult(), jsReport.report());
ClearException();
}
} else {
NS_WARNING("OOMed while acquiring uncaught exception from JSAPI");
ClearException();
}
}
bool
AutoJSAPI::PeekException(JS::MutableHandle<JS::Value> aVal)
{
MOZ_ASSERT_IF(mIsMainThread, IsStackTop());
MOZ_ASSERT(HasException());
MOZ_ASSERT(js::GetContextCompartment(cx()));
if (!JS_GetPendingException(cx(), aVal)) {
return false;
}
return true;
}
bool
AutoJSAPI::StealException(JS::MutableHandle<JS::Value> aVal)
{
if (!PeekException(aVal)) {
return false;
}
JS_ClearPendingException(cx());
return true;
}
#ifdef DEBUG
bool
AutoJSAPI::IsStackTop() const
{
return ScriptSettingsStack::TopNonIncumbentScript() == this;
}
#endif // DEBUG
AutoEntryScript::AutoEntryScript(nsIGlobalObject* aGlobalObject,
const char *aReason,
bool aIsMainThread)
: AutoJSAPI(aGlobalObject, aIsMainThread, eEntryScript)
, mWebIDLCallerPrincipal(nullptr)
{
MOZ_ASSERT(aGlobalObject);
if (aIsMainThread && gRunToCompletionListeners > 0) {
mDocShellEntryMonitor.emplace(cx(), aReason);
}
}
AutoEntryScript::AutoEntryScript(JSObject* aObject,
const char *aReason,
bool aIsMainThread)
: AutoEntryScript(xpc::NativeGlobal(aObject), aReason, aIsMainThread)
{
}
AutoEntryScript::~AutoEntryScript()
{
// GC when we pop a script entry point. This is a useful heuristic that helps
// us out on certain (flawed) benchmarks like sunspider, because it lets us
// avoid GCing during the timing loop.
JS_MaybeGC(cx());
}
AutoEntryScript::DocshellEntryMonitor::DocshellEntryMonitor(JSContext* aCx,
const char* aReason)
: JS::dbg::AutoEntryMonitor(aCx)
, mReason(aReason)
{
}
void
AutoEntryScript::DocshellEntryMonitor::Entry(JSContext* aCx, JSFunction* aFunction,
JSScript* aScript, JS::Handle<JS::Value> aAsyncStack,
const char* aAsyncCause)
{
JS::Rooted<JSFunction*> rootedFunction(aCx);
if (aFunction) {
rootedFunction = aFunction;
}
JS::Rooted<JSScript*> rootedScript(aCx);
if (aScript) {
rootedScript = aScript;
}
nsCOMPtr<nsPIDOMWindowInner> window =
do_QueryInterface(xpc::NativeGlobal(JS::CurrentGlobalOrNull(aCx)));
if (!window || !window->GetDocShell() ||
!window->GetDocShell()->GetRecordProfileTimelineMarkers()) {
return;
}
nsCOMPtr<nsIDocShell> docShellForJSRunToCompletion = window->GetDocShell();
nsString filename;
uint32_t lineNumber = 0;
js::AutoStableStringChars functionName(aCx);
if (rootedFunction) {
JS::Rooted<JSString*> displayId(aCx, JS_GetFunctionDisplayId(rootedFunction));
if (displayId) {
if (!functionName.initTwoByte(aCx, displayId)) {
JS_ClearPendingException(aCx);
return;
}
}
}
if (!rootedScript) {
rootedScript = JS_GetFunctionScript(aCx, rootedFunction);
}
if (rootedScript) {
filename = NS_ConvertUTF8toUTF16(JS_GetScriptFilename(rootedScript));
lineNumber = JS_GetScriptBaseLineNumber(aCx, rootedScript);
}
if (!filename.IsEmpty() || functionName.isTwoByte()) {
const char16_t* functionNameChars = functionName.isTwoByte() ?
functionName.twoByteChars() : nullptr;
docShellForJSRunToCompletion->NotifyJSRunToCompletionStart(mReason,
functionNameChars,
filename.BeginReading(),
lineNumber, aAsyncStack,
aAsyncCause);
}
}
void
AutoEntryScript::DocshellEntryMonitor::Exit(JSContext* aCx)
{
nsCOMPtr<nsPIDOMWindowInner> window =
do_QueryInterface(xpc::NativeGlobal(JS::CurrentGlobalOrNull(aCx)));
// Not really worth checking GetRecordProfileTimelineMarkers here.
if (window && window->GetDocShell()) {
nsCOMPtr<nsIDocShell> docShellForJSRunToCompletion = window->GetDocShell();
docShellForJSRunToCompletion->NotifyJSRunToCompletionStop();
}
}
AutoIncumbentScript::AutoIncumbentScript(nsIGlobalObject* aGlobalObject)
: ScriptSettingsStackEntry(aGlobalObject, eIncumbentScript)
, mCallerOverride(nsContentUtils::GetCurrentJSContextForThread())
{
ScriptSettingsStack::Push(this);
}
AutoIncumbentScript::~AutoIncumbentScript()
{
ScriptSettingsStack::Pop(this);
}
AutoNoJSAPI::AutoNoJSAPI()
: ScriptSettingsStackEntry(nullptr, eNoJSAPI)
{
ScriptSettingsStack::Push(this);
}
AutoNoJSAPI::~AutoNoJSAPI()
{
ScriptSettingsStack::Pop(this);
}
} // namespace dom
AutoJSContext::AutoJSContext(MOZ_GUARD_OBJECT_NOTIFIER_ONLY_PARAM_IN_IMPL)
: mCx(nullptr)
{
JS::AutoSuppressGCAnalysis nogc;
MOZ_ASSERT(!mCx, "mCx should not be initialized!");
MOZ_ASSERT(NS_IsMainThread());
MOZ_GUARD_OBJECT_NOTIFIER_INIT;
if (dom::IsJSAPIActive()) {
mCx = dom::danger::GetJSContext();
} else {
mJSAPI.Init();
mCx = mJSAPI.cx();
}
}
AutoJSContext::operator JSContext*() const
{
return mCx;
}
AutoSafeJSContext::AutoSafeJSContext(MOZ_GUARD_OBJECT_NOTIFIER_ONLY_PARAM_IN_IMPL)
: AutoJSAPI()
{
MOZ_ASSERT(NS_IsMainThread());
MOZ_GUARD_OBJECT_NOTIFIER_INIT;
DebugOnly<bool> ok = Init(xpc::UnprivilegedJunkScope());
MOZ_ASSERT(ok,
"This is quite odd. We should have crashed in the "
"xpc::NativeGlobal() call if xpc::UnprivilegedJunkScope() "
"returned null, and inited correctly otherwise!");
}
AutoSlowOperation::AutoSlowOperation(MOZ_GUARD_OBJECT_NOTIFIER_ONLY_PARAM_IN_IMPL)
: AutoJSAPI()
{
MOZ_ASSERT(NS_IsMainThread());
MOZ_GUARD_OBJECT_NOTIFIER_INIT;
Init();
}
void
AutoSlowOperation::CheckForInterrupt()
{
// JS_CheckForInterrupt expects us to be in a compartment.
JSAutoCompartment ac(cx(), xpc::UnprivilegedJunkScope());
JS_CheckForInterrupt(cx());
}
} // namespace mozilla

465
dom/script/ScriptSettings.h Normal file
View file

@ -0,0 +1,465 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/* Utilities for managing the script settings object stack defined in webapps */
#ifndef mozilla_dom_ScriptSettings_h
#define mozilla_dom_ScriptSettings_h
#include "MainThreadUtils.h"
#include "nsIGlobalObject.h"
#include "nsIPrincipal.h"
#include "mozilla/Maybe.h"
#include "jsapi.h"
#include "js/Debug.h"
class nsPIDOMWindowInner;
class nsGlobalWindow;
class nsIScriptContext;
class nsIDocument;
class nsIDocShell;
namespace mozilla {
namespace dom {
/*
* System-wide setup/teardown routines. Init and Destroy should be invoked
* once each, at startup and shutdown (respectively).
*/
void InitScriptSettings();
void DestroyScriptSettings();
bool ScriptSettingsInitialized();
/*
* Static helpers in ScriptSettings which track the number of listeners
* of Javascript RunToCompletion events. These should be used by the code in
* nsDocShell::SetRecordProfileTimelineMarkers to indicate to script
* settings that script run-to-completion needs to be monitored.
* SHOULD BE CALLED ONLY BY MAIN THREAD.
*/
void UseEntryScriptProfiling();
void UnuseEntryScriptProfiling();
// To implement a web-compatible browser, it is often necessary to obtain the
// global object that is "associated" with the currently-running code. This
// process is made more complicated by the fact that, historically, different
// algorithms have operated with different definitions of the "associated"
// global.
//
// HTML5 formalizes this into two concepts: the "incumbent global" and the
// "entry global". The incumbent global corresponds to the global of the
// current script being executed, whereas the entry global corresponds to the
// global of the script where the current JS execution began.
//
// There is also a potentially-distinct third global that is determined by the
// current compartment. This roughly corresponds with the notion of Realms in
// ECMAScript.
//
// Suppose some event triggers an event listener in window |A|, which invokes a
// scripted function in window |B|, which invokes the |window.location.href|
// setter in window |C|. The entry global would be |A|, the incumbent global
// would be |B|, and the current compartment would be that of |C|.
//
// In general, it's best to use to use the most-closely-associated global
// unless the spec says to do otherwise. In 95% of the cases, the global of
// the current compartment (GetCurrentGlobal()) is the right thing. For
// example, WebIDL constructors (new C.XMLHttpRequest()) are initialized with
// the global of the current compartment (i.e. |C|).
//
// The incumbent global is very similar, but differs in a few edge cases. For
// example, if window |B| does |C.location.href = "..."|, the incumbent global
// used for the navigation algorithm is B, because no script from |C| was ever run.
//
// The entry global is used for various things like computing base URIs, mostly
// for historical reasons.
//
// Note that all of these functions return bonafide global objects. This means
// that, for Windows, they always return the inner.
// Returns the global associated with the top-most Candidate Entry Point on
// the Script Settings Stack. See the HTML spec. This may be null.
nsIGlobalObject* GetEntryGlobal();
// If the entry global is a window, returns its extant document. Otherwise,
// returns null.
nsIDocument* GetEntryDocument();
// Returns the global associated with the top-most entry of the the Script
// Settings Stack. See the HTML spec. This may be null.
nsIGlobalObject* GetIncumbentGlobal();
// Returns the global associated with the current compartment. This may be null.
nsIGlobalObject* GetCurrentGlobal();
// JS-implemented WebIDL presents an interesting situation with respect to the
// subject principal. A regular C++-implemented API can simply examine the
// compartment of the most-recently-executed script, and use that to infer the
// responsible party. However, JS-implemented APIs are run with system
// principal, and thus clobber the subject principal of the script that
// invoked the API. So we have to do some extra work to keep track of this
// information.
//
// We therefore implement the following behavior:
// * Each Script Settings Object has an optional WebIDL Caller Principal field.
// This defaults to null.
// * When we push an Entry Point in preparation to run a JS-implemented WebIDL
// callback, we grab the subject principal at the time of invocation, and
// store that as the WebIDL Caller Principal.
// * When non-null, callers can query this principal from script via an API on
// Components.utils.
nsIPrincipal* GetWebIDLCallerPrincipal();
// This may be used by callers that know that their incumbent global is non-
// null (i.e. they know there have been no System Caller pushes since the
// inner-most script execution).
inline JSObject& IncumbentJSGlobal()
{
return *GetIncumbentGlobal()->GetGlobalJSObject();
}
// Returns whether JSAPI is active right now. If it is not, working with a
// JSContext you grab from somewhere random is not OK and you should be doing
// AutoJSAPI or AutoEntryScript to get yourself a properly set up JSContext.
bool IsJSAPIActive();
namespace danger {
// Get the JSContext for this thread. This is in the "danger" namespace because
// we generally want people using AutoJSAPI instead, unless they really know
// what they're doing.
JSContext* GetJSContext();
} // namespace danger
JS::RootingContext* RootingCx();
class ScriptSettingsStack;
class ScriptSettingsStackEntry {
friend class ScriptSettingsStack;
public:
~ScriptSettingsStackEntry();
bool NoJSAPI() const { return mType == eNoJSAPI; }
bool IsEntryCandidate() const {
return mType == eEntryScript || mType == eNoJSAPI;
}
bool IsIncumbentCandidate() { return mType != eJSAPI; }
bool IsIncumbentScript() { return mType == eIncumbentScript; }
protected:
enum Type {
eEntryScript,
eIncumbentScript,
eJSAPI,
eNoJSAPI
};
ScriptSettingsStackEntry(nsIGlobalObject *aGlobal,
Type aEntryType);
nsCOMPtr<nsIGlobalObject> mGlobalObject;
Type mType;
private:
ScriptSettingsStackEntry *mOlder;
};
/*
* For any interaction with JSAPI, an AutoJSAPI (or one of its subclasses)
* must be on the stack.
*
* This base class should be instantiated as-is when the caller wants to use
* JSAPI but doesn't expect to run script. The caller must then call one of its
* Init functions before being able to access the JSContext through cx().
* Its current duties are as-follows (see individual Init comments for details):
*
* * Grabbing an appropriate JSContext, and, on the main thread, pushing it onto
* the JSContext stack.
* * Entering an initial (possibly null) compartment, to ensure that the
* previously entered compartment for that JSContext is not used by mistake.
* * Reporting any exceptions left on the JSRuntime, unless the caller steals
* or silences them.
* * On main thread, entering a JSAutoRequest.
*
* Additionally, the following duties are planned, but not yet implemented:
*
* * De-poisoning the JSRuntime to allow manipulation of JSAPI. This requires
* implementing the poisoning first. For now, this de-poisoning
* effectively corresponds to having a non-null cx on the stack.
*
* In situations where the consumer expects to run script, AutoEntryScript
* should be used, which does additional manipulation of the script settings
* stack. In bug 991758, we'll add hard invariants to SpiderMonkey, such that
* any attempt to run script without an AutoEntryScript on the stack will
* fail. This prevents system code from accidentally triggering script
* execution at inopportune moments via surreptitious getters and proxies.
*/
class MOZ_STACK_CLASS AutoJSAPI : protected ScriptSettingsStackEntry {
public:
// Trivial constructor. One of the Init functions must be called before
// accessing the JSContext through cx().
AutoJSAPI();
~AutoJSAPI();
// This uses the SafeJSContext (or worker equivalent), and enters a null
// compartment, so that the consumer is forced to select a compartment to
// enter before manipulating objects.
//
// This variant will ensure that any errors reported by this AutoJSAPI as it
// comes off the stack will not fire error events or be associated with any
// particular web-visible global.
void Init();
// This uses the SafeJSContext (or worker equivalent), and enters the
// compartment of aGlobalObject.
// If aGlobalObject or its associated JS global are null then it returns
// false and use of cx() will cause an assertion.
//
// If aGlobalObject represents a web-visible global, errors reported by this
// AutoJSAPI as it comes off the stack will fire the relevant error events and
// show up in the corresponding web console.
MOZ_MUST_USE bool Init(nsIGlobalObject* aGlobalObject);
// This is a helper that grabs the native global associated with aObject and
// invokes the above Init() with that.
MOZ_MUST_USE bool Init(JSObject* aObject);
// Unsurprisingly, this uses aCx and enters the compartment of aGlobalObject.
// If aGlobalObject or its associated JS global are null then it returns
// false and use of cx() will cause an assertion.
// If aCx is null it will cause an assertion.
//
// If aGlobalObject represents a web-visible global, errors reported by this
// AutoJSAPI as it comes off the stack will fire the relevant error events and
// show up in the corresponding web console.
MOZ_MUST_USE bool Init(nsIGlobalObject* aGlobalObject, JSContext* aCx);
// Convenience functions to take an nsPIDOMWindow* or nsGlobalWindow*,
// when it is more easily available than an nsIGlobalObject.
MOZ_MUST_USE bool Init(nsPIDOMWindowInner* aWindow);
MOZ_MUST_USE bool Init(nsPIDOMWindowInner* aWindow, JSContext* aCx);
MOZ_MUST_USE bool Init(nsGlobalWindow* aWindow);
MOZ_MUST_USE bool Init(nsGlobalWindow* aWindow, JSContext* aCx);
JSContext* cx() const {
MOZ_ASSERT(mCx, "Must call Init before using an AutoJSAPI");
MOZ_ASSERT(IsStackTop());
return mCx;
}
#ifdef DEBUG
bool IsStackTop() const;
#endif
// If HasException, report it. Otherwise, a no-op.
void ReportException();
bool HasException() const {
MOZ_ASSERT(IsStackTop());
return JS_IsExceptionPending(cx());
};
// Transfers ownership of the current exception from the JS engine to the
// caller. Callers must ensure that HasException() is true, and that cx()
// is in a non-null compartment.
//
// Note that this fails if and only if we OOM while wrapping the exception
// into the current compartment.
MOZ_MUST_USE bool StealException(JS::MutableHandle<JS::Value> aVal);
// Peek the current exception from the JS engine, without stealing it.
// Callers must ensure that HasException() is true, and that cx() is in a
// non-null compartment.
//
// Note that this fails if and only if we OOM while wrapping the exception
// into the current compartment.
MOZ_MUST_USE bool PeekException(JS::MutableHandle<JS::Value> aVal);
void ClearException() {
MOZ_ASSERT(IsStackTop());
JS_ClearPendingException(cx());
}
protected:
// Protected constructor for subclasses. This constructor initialises the
// AutoJSAPI, so Init must NOT be called on subclasses that use this.
AutoJSAPI(nsIGlobalObject* aGlobalObject, bool aIsMainThread, Type aType);
private:
mozilla::Maybe<JSAutoRequest> mAutoRequest;
mozilla::Maybe<JSAutoNullableCompartment> mAutoNullableCompartment;
JSContext *mCx;
// Whether we're mainthread or not; set when we're initialized.
bool mIsMainThread;
Maybe<JS::WarningReporter> mOldWarningReporter;
void InitInternal(nsIGlobalObject* aGlobalObject, JSObject* aGlobal,
JSContext* aCx, bool aIsMainThread);
AutoJSAPI(const AutoJSAPI&) = delete;
AutoJSAPI& operator= (const AutoJSAPI&) = delete;
};
/*
* A class that represents a new script entry point.
*
* |aReason| should be a statically-allocated C string naming the reason we're
* invoking JavaScript code: "setTimeout", "event", and so on. The devtools use
* these strings to label JS execution in timeline and profiling displays.
*/
class MOZ_STACK_CLASS AutoEntryScript : public AutoJSAPI {
public:
AutoEntryScript(nsIGlobalObject* aGlobalObject,
const char *aReason,
bool aIsMainThread = NS_IsMainThread());
AutoEntryScript(JSObject* aObject, // Any object from the relevant global
const char *aReason,
bool aIsMainThread = NS_IsMainThread());
~AutoEntryScript();
void SetWebIDLCallerPrincipal(nsIPrincipal *aPrincipal) {
mWebIDLCallerPrincipal = aPrincipal;
}
private:
// A subclass of AutoEntryMonitor that notifies the docshell.
class DocshellEntryMonitor final : public JS::dbg::AutoEntryMonitor
{
public:
DocshellEntryMonitor(JSContext* aCx, const char* aReason);
// Please note that |aAsyncCause| here is owned by the caller, and its
// lifetime must outlive the lifetime of the DocshellEntryMonitor object.
// In practice, |aAsyncCause| is identical to |aReason| passed into
// the AutoEntryScript constructor, so the lifetime requirements are
// trivially satisfied by |aReason| being a statically allocated string.
void Entry(JSContext* aCx, JSFunction* aFunction,
JS::Handle<JS::Value> aAsyncStack,
const char* aAsyncCause) override
{
Entry(aCx, aFunction, nullptr, aAsyncStack, aAsyncCause);
}
void Entry(JSContext* aCx, JSScript* aScript,
JS::Handle<JS::Value> aAsyncStack,
const char* aAsyncCause) override
{
Entry(aCx, nullptr, aScript, aAsyncStack, aAsyncCause);
}
void Exit(JSContext* aCx) override;
private:
void Entry(JSContext* aCx, JSFunction* aFunction, JSScript* aScript,
JS::Handle<JS::Value> aAsyncStack,
const char* aAsyncCause);
const char* mReason;
};
// It's safe to make this a weak pointer, since it's the subject principal
// when we go on the stack, so can't go away until after we're gone. In
// particular, this is only used from the CallSetup constructor, and only in
// the aIsJSImplementedWebIDL case. And in that case, the subject principal
// is the principal of the callee function that is part of the CallArgs just a
// bit up the stack, and which will outlive us. So we know the principal
// can't go away until then either.
nsIPrincipal* MOZ_NON_OWNING_REF mWebIDLCallerPrincipal;
friend nsIPrincipal* GetWebIDLCallerPrincipal();
Maybe<DocshellEntryMonitor> mDocShellEntryMonitor;
};
/*
* A class that can be used to force a particular incumbent script on the stack.
*/
class AutoIncumbentScript : protected ScriptSettingsStackEntry {
public:
explicit AutoIncumbentScript(nsIGlobalObject* aGlobalObject);
~AutoIncumbentScript();
private:
JS::AutoHideScriptedCaller mCallerOverride;
};
/*
* A class to put the JS engine in an unusable state. The subject principal
* will become System, the information on the script settings stack is
* rendered inaccessible, and JSAPI may not be manipulated until the class is
* either popped or an AutoJSAPI instance is subsequently pushed.
*
* This class may not be instantiated if an exception is pending.
*/
class AutoNoJSAPI : protected ScriptSettingsStackEntry {
public:
explicit AutoNoJSAPI();
~AutoNoJSAPI();
};
} // namespace dom
/**
* Use AutoJSContext when you need a JS context on the stack but don't have one
* passed as a parameter. AutoJSContext will take care of finding the most
* appropriate JS context and release it when leaving the stack.
*/
class MOZ_RAII AutoJSContext {
public:
explicit AutoJSContext(MOZ_GUARD_OBJECT_NOTIFIER_ONLY_PARAM);
operator JSContext*() const;
protected:
JSContext* mCx;
dom::AutoJSAPI mJSAPI;
MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
};
/**
* AutoSafeJSContext is similar to AutoJSContext but will only return the safe
* JS context. That means it will never call nsContentUtils::GetCurrentJSContext().
*
* Note - This is deprecated. Please use AutoJSAPI instead.
*/
class MOZ_RAII AutoSafeJSContext : public dom::AutoJSAPI {
public:
explicit AutoSafeJSContext(MOZ_GUARD_OBJECT_NOTIFIER_ONLY_PARAM);
operator JSContext*() const
{
return cx();
}
private:
MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
};
/**
* Use AutoSlowOperation when native side calls many JS callbacks in a row
* and slow script dialog should be activated if too much time is spent going
* through those callbacks.
* AutoSlowOperation puts a JSAutoRequest on the stack so that we don't continue
* to reset the watchdog and CheckForInterrupt can be then used to check whether
* JS execution should be interrupted.
*/
class MOZ_RAII AutoSlowOperation : public dom::AutoJSAPI
{
public:
explicit AutoSlowOperation(MOZ_GUARD_OBJECT_NOTIFIER_ONLY_PARAM);
void CheckForInterrupt();
private:
MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
};
} // namespace mozilla
#endif // mozilla_dom_ScriptSettings_h

33
dom/script/moz.build Normal file
View file

@ -0,0 +1,33 @@
# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
XPIDL_SOURCES += [
'nsIScriptLoaderObserver.idl',
]
XPIDL_MODULE = 'dom'
EXPORTS += ['nsIScriptElement.h']
EXPORTS.mozilla.dom += [
'ScriptElement.h',
'ScriptLoader.h',
'ScriptSettings.h',
]
SOURCES += [
'ScriptElement.cpp',
'ScriptLoader.cpp',
'ScriptSettings.cpp',
]
LOCAL_INCLUDES += [
'/dom/base',
'/dom/workers',
]
include('/ipc/chromium/chromium-config.mozbuild')
FINAL_LIBRARY = 'xul'

View file

@ -0,0 +1,329 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef nsIScriptElement_h___
#define nsIScriptElement_h___
#include "nsISupports.h"
#include "nsIURI.h"
#include "nsCOMPtr.h"
#include "nsIScriptLoaderObserver.h"
#include "nsWeakPtr.h"
#include "nsIParser.h"
#include "nsContentCreatorFunctions.h"
#include "nsIDOMHTMLScriptElement.h"
#include "mozilla/CORSMode.h"
#define NS_ISCRIPTELEMENT_IID \
{ 0xe60fca9b, 0x1b96, 0x4e4e, \
{ 0xa9, 0xb4, 0xdc, 0x98, 0x4f, 0x88, 0x3f, 0x9c } }
/**
* Internal interface implemented by script elements
*/
class nsIScriptElement : public nsIScriptLoaderObserver {
public:
NS_DECLARE_STATIC_IID_ACCESSOR(NS_ISCRIPTELEMENT_IID)
explicit nsIScriptElement(mozilla::dom::FromParser aFromParser)
: mLineNumber(1),
mAlreadyStarted(false),
mMalformed(false),
mDoneAddingChildren(aFromParser == mozilla::dom::NOT_FROM_PARSER ||
aFromParser == mozilla::dom::FROM_PARSER_FRAGMENT),
mForceAsync(aFromParser == mozilla::dom::NOT_FROM_PARSER ||
aFromParser == mozilla::dom::FROM_PARSER_FRAGMENT),
mFrozen(false),
mDefer(false),
mAsync(false),
mExternal(false),
mParserCreated(aFromParser == mozilla::dom::FROM_PARSER_FRAGMENT ?
mozilla::dom::NOT_FROM_PARSER : aFromParser),
// Fragment parser-created scripts (if executable)
// behave like script-created scripts.
mCreatorParser(nullptr)
{
}
/**
* Content type identifying the scripting language. Can be empty, in
* which case javascript will be assumed.
* Return false if type attribute is not found.
*/
virtual bool GetScriptType(nsAString& type) = 0;
/**
* Location of script source text. Can return null, in which case
* this is assumed to be an inline script element.
*/
nsIURI* GetScriptURI()
{
NS_PRECONDITION(mFrozen, "Not ready for this call yet!");
return mUri;
}
/**
* Script source text for inline script elements.
*/
virtual void GetScriptText(nsAString& text) = 0;
virtual void GetScriptCharset(nsAString& charset) = 0;
/**
* Freezes the return values of GetScriptDeferred(), GetScriptAsync() and
* GetScriptURI() so that subsequent modifications to the attributes don't
* change execution behavior.
*/
virtual void FreezeUriAsyncDefer() = 0;
/**
* Is the script deferred. Currently only supported by HTML scripts.
*/
bool GetScriptDeferred()
{
NS_PRECONDITION(mFrozen, "Not ready for this call yet!");
return mDefer;
}
/**
* Is the script async. Currently only supported by HTML scripts.
*/
bool GetScriptAsync()
{
NS_PRECONDITION(mFrozen, "Not ready for this call yet!");
return mAsync;
}
/**
* Is the script an external script?
*/
bool GetScriptExternal()
{
NS_PRECONDITION(mFrozen, "Not ready for this call yet!");
return mExternal;
}
/**
* Returns how the element was created.
*/
mozilla::dom::FromParser GetParserCreated()
{
return mParserCreated;
}
void SetScriptLineNumber(uint32_t aLineNumber)
{
mLineNumber = aLineNumber;
}
uint32_t GetScriptLineNumber()
{
return mLineNumber;
}
void SetIsMalformed()
{
mMalformed = true;
}
bool IsMalformed()
{
return mMalformed;
}
void PreventExecution()
{
mAlreadyStarted = true;
}
void LoseParserInsertedness()
{
mFrozen = false;
mUri = nullptr;
mCreatorParser = nullptr;
mParserCreated = mozilla::dom::NOT_FROM_PARSER;
bool async = false;
nsCOMPtr<nsIDOMHTMLScriptElement> htmlScript = do_QueryInterface(this);
if (htmlScript) {
htmlScript->GetAsync(&async);
}
mForceAsync = !async;
}
void SetCreatorParser(nsIParser* aParser)
{
mCreatorParser = do_GetWeakReference(aParser);
}
/**
* Unblocks the creator parser
*/
void UnblockParser()
{
nsCOMPtr<nsIParser> parser = do_QueryReferent(mCreatorParser);
if (parser) {
parser->UnblockParser();
}
}
/**
* Attempts to resume parsing asynchronously
*/
void ContinueParserAsync()
{
nsCOMPtr<nsIParser> parser = do_QueryReferent(mCreatorParser);
if (parser) {
parser->ContinueInterruptedParsingAsync();
}
}
/**
* Informs the creator parser that the evaluation of this script is starting
*/
void BeginEvaluating()
{
nsCOMPtr<nsIParser> parser = do_QueryReferent(mCreatorParser);
if (parser) {
parser->PushDefinedInsertionPoint();
}
}
/**
* Informs the creator parser that the evaluation of this script is ending
*/
void EndEvaluating()
{
nsCOMPtr<nsIParser> parser = do_QueryReferent(mCreatorParser);
if (parser) {
parser->PopDefinedInsertionPoint();
}
}
/**
* Retrieves a pointer to the creator parser if this has one or null if not
*/
already_AddRefed<nsIParser> GetCreatorParser()
{
nsCOMPtr<nsIParser> parser = do_QueryReferent(mCreatorParser);
return parser.forget();
}
/**
* This method is called when the parser finishes creating the script
* element's children, if any are present.
*
* @return whether the parser will be blocked while this script is being
* loaded
*/
bool AttemptToExecute()
{
mDoneAddingChildren = true;
bool block = MaybeProcessScript();
if (!mAlreadyStarted) {
// Need to lose parser-insertedness here to allow another script to cause
// execution later.
LoseParserInsertedness();
}
return block;
}
/**
* Get the CORS mode of the script element
*/
virtual mozilla::CORSMode GetCORSMode() const
{
/* Default to no CORS */
return mozilla::CORS_NONE;
}
/**
* Fire an error event
*/
virtual nsresult FireErrorEvent() = 0;
protected:
/**
* Processes the script if it's in the document-tree and links to or
* contains a script. Once it has been evaluated there is no way to make it
* reevaluate the script, you'll have to create a new element. This also means
* that when adding a src attribute to an element that already contains an
* inline script, the script referenced by the src attribute will not be
* loaded.
*
* In order to be able to use multiple childNodes, or to use the
* fallback mechanism of using both inline script and linked script you have
* to add all attributes and childNodes before adding the element to the
* document-tree.
*
* @return whether the parser will be blocked while this script is being
* loaded
*/
virtual bool MaybeProcessScript() = 0;
/**
* The start line number of the script.
*/
uint32_t mLineNumber;
/**
* The "already started" flag per HTML5.
*/
bool mAlreadyStarted;
/**
* The script didn't have an end tag.
*/
bool mMalformed;
/**
* False if parser-inserted but the parser hasn't triggered running yet.
*/
bool mDoneAddingChildren;
/**
* If true, the .async property returns true instead of reflecting the
* content attribute.
*/
bool mForceAsync;
/**
* Whether src, defer and async are frozen.
*/
bool mFrozen;
/**
* The effective deferredness.
*/
bool mDefer;
/**
* The effective asyncness.
*/
bool mAsync;
/**
* The effective externalness. A script can be external with mUri being null
* if the src attribute contained an invalid URL string.
*/
bool mExternal;
/**
* Whether this element was parser-created.
*/
mozilla::dom::FromParser mParserCreated;
/**
* The effective src (or null if no src).
*/
nsCOMPtr<nsIURI> mUri;
/**
* The creator parser of a non-defer, non-async parser-inserted script.
*/
nsWeakPtr mCreatorParser;
};
NS_DEFINE_STATIC_IID_ACCESSOR(nsIScriptElement, NS_ISCRIPTELEMENT_IID)
#endif // nsIScriptElement_h___

View file

@ -0,0 +1,47 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "nsISupports.idl"
interface nsIScriptElement;
interface nsIURI;
[scriptable, uuid(7b787204-76fb-4764-96f1-fb7a666db4f4)]
interface nsIScriptLoaderObserver : nsISupports {
/**
* The script is available for evaluation. For inline scripts, this
* method will be called synchronously. For externally loaded scripts,
* this method will be called when the load completes.
*
* @param aResult A result code representing the result of loading
* a script. If this is a failure code, script evaluation
* will not occur.
* @param aElement The element being processed.
* @param aIsInline Is this an inline script or externally loaded?
* @param aURI What is the URI of the script (the document URI if
* it is inline).
* @param aLineNo At what line does the script appear (generally 1
* if it is a loaded script).
*/
void scriptAvailable(in nsresult aResult,
in nsIScriptElement aElement,
in boolean aIsInline,
in nsIURI aURI,
in int32_t aLineNo);
/**
* The script has been evaluated.
*
* @param aResult A result code representing the success or failure of
* the script evaluation.
* @param aElement The element being processed.
* @param aIsInline Is this an inline script or externally loaded?
*/
void scriptEvaluated(in nsresult aResult,
in nsIScriptElement aElement,
in boolean aIsInline);
};