mirror of
https://repo.dactyloidae.xyz/Dactyloidae/UXP.git
synced 2026-10-04 22:37:29 +09:00
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:
parent
179fd7df8a
commit
f85b274e94
50 changed files with 554 additions and 514 deletions
150
dom/script/ScriptElement.cpp
Normal file
150
dom/script/ScriptElement.cpp
Normal 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);
|
||||
}
|
||||
58
dom/script/ScriptElement.h
Normal file
58
dom/script/ScriptElement.h
Normal 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
3063
dom/script/ScriptLoader.cpp
Normal file
File diff suppressed because it is too large
Load diff
719
dom/script/ScriptLoader.h
Normal file
719
dom/script/ScriptLoader.h
Normal 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
|
||||
839
dom/script/ScriptSettings.cpp
Normal file
839
dom/script/ScriptSettings.cpp
Normal 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
465
dom/script/ScriptSettings.h
Normal 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
33
dom/script/moz.build
Normal 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'
|
||||
329
dom/script/nsIScriptElement.h
Normal file
329
dom/script/nsIScriptElement.h
Normal 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___
|
||||
47
dom/script/nsIScriptLoaderObserver.idl
Normal file
47
dom/script/nsIScriptLoaderObserver.idl
Normal 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);
|
||||
|
||||
};
|
||||
Loading…
Add table
Add a link
Reference in a new issue