зеркало из https://github.com/mozilla/gecko-dev.git
4144 строки
140 KiB
C++
4144 строки
140 KiB
C++
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
|
|
/* vim: set ts=4 sw=4 et 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/. */
|
|
|
|
/*
|
|
|
|
An implementation for the XUL document. This implementation serves
|
|
as the basis for generating an NGLayout content model.
|
|
|
|
Notes
|
|
-----
|
|
|
|
1. We do some monkey business in the document observer methods to
|
|
keep the element map in sync for HTML elements. Why don't we just
|
|
do it for _all_ elements? Well, in the case of XUL elements,
|
|
which may be lazily created during frame construction, the
|
|
document observer methods will never be called because we'll be
|
|
adding the XUL nodes into the content model "quietly".
|
|
|
|
*/
|
|
|
|
#include "mozilla/ArrayUtils.h"
|
|
|
|
#include "XULDocument.h"
|
|
|
|
#include "nsError.h"
|
|
#include "nsIBoxObject.h"
|
|
#include "nsIChromeRegistry.h"
|
|
#include "nsView.h"
|
|
#include "nsViewManager.h"
|
|
#include "nsIContentViewer.h"
|
|
#include "nsIDOMXULElement.h"
|
|
#include "nsIStreamListener.h"
|
|
#include "nsITimer.h"
|
|
#include "nsDocShell.h"
|
|
#include "nsGkAtoms.h"
|
|
#include "nsXMLContentSink.h"
|
|
#include "nsXULContentSink.h"
|
|
#include "nsXULContentUtils.h"
|
|
#include "nsIXULOverlayProvider.h"
|
|
#include "nsIStringEnumerator.h"
|
|
#include "nsNetUtil.h"
|
|
#include "nsParserCIID.h"
|
|
#include "nsPIBoxObject.h"
|
|
#include "mozilla/dom/BoxObject.h"
|
|
#include "nsString.h"
|
|
#include "nsPIDOMWindow.h"
|
|
#include "nsPIWindowRoot.h"
|
|
#include "nsXULCommandDispatcher.h"
|
|
#include "nsXULElement.h"
|
|
#include "nsXULPrototypeCache.h"
|
|
#include "mozilla/Logging.h"
|
|
#include "nsIFrame.h"
|
|
#include "nsXBLService.h"
|
|
#include "nsCExternalHandlerService.h"
|
|
#include "nsMimeTypes.h"
|
|
#include "nsIObjectInputStream.h"
|
|
#include "nsIObjectOutputStream.h"
|
|
#include "nsContentList.h"
|
|
#include "nsISimpleEnumerator.h"
|
|
#include "nsIScriptGlobalObject.h"
|
|
#include "nsIScriptSecurityManager.h"
|
|
#include "nsNodeInfoManager.h"
|
|
#include "nsContentCreatorFunctions.h"
|
|
#include "nsContentUtils.h"
|
|
#include "nsIParser.h"
|
|
#include "nsCharsetSource.h"
|
|
#include "mozilla/StyleSheetInlines.h"
|
|
#include "mozilla/css/Loader.h"
|
|
#include "nsIScriptError.h"
|
|
#include "nsIStyleSheetLinkingElement.h"
|
|
#include "nsIObserverService.h"
|
|
#include "nsNodeUtils.h"
|
|
#include "nsIDocShellTreeOwner.h"
|
|
#include "nsIXULWindow.h"
|
|
#include "nsXULPopupManager.h"
|
|
#include "nsCCUncollectableMarker.h"
|
|
#include "nsURILoader.h"
|
|
#include "mozilla/AddonPathService.h"
|
|
#include "mozilla/BasicEvents.h"
|
|
#include "mozilla/dom/Element.h"
|
|
#include "mozilla/dom/NodeInfoInlines.h"
|
|
#include "mozilla/dom/ProcessingInstruction.h"
|
|
#include "mozilla/dom/ScriptSettings.h"
|
|
#include "mozilla/dom/XULDocumentBinding.h"
|
|
#include "mozilla/EventDispatcher.h"
|
|
#include "mozilla/LoadInfo.h"
|
|
#include "mozilla/Preferences.h"
|
|
#include "nsTextNode.h"
|
|
#include "nsJSUtils.h"
|
|
#include "mozilla/dom/URL.h"
|
|
#include "nsIContentPolicy.h"
|
|
#include "mozAutoDocUpdate.h"
|
|
#include "xpcpublic.h"
|
|
#include "mozilla/StyleSheet.h"
|
|
#include "mozilla/StyleSheetInlines.h"
|
|
|
|
using namespace mozilla;
|
|
using namespace mozilla::dom;
|
|
|
|
//----------------------------------------------------------------------
|
|
//
|
|
// CIDs
|
|
//
|
|
|
|
static NS_DEFINE_CID(kParserCID, NS_PARSER_CID);
|
|
|
|
static bool IsOverlayAllowed(nsIURI* aURI)
|
|
{
|
|
bool canOverlay = false;
|
|
if (NS_SUCCEEDED(aURI->SchemeIs("about", &canOverlay)) && canOverlay)
|
|
return true;
|
|
if (NS_SUCCEEDED(aURI->SchemeIs("chrome", &canOverlay)) && canOverlay)
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
//----------------------------------------------------------------------
|
|
//
|
|
// Miscellaneous Constants
|
|
//
|
|
|
|
const nsForwardReference::Phase nsForwardReference::kPasses[] = {
|
|
nsForwardReference::eConstruction,
|
|
nsForwardReference::eHookup,
|
|
nsForwardReference::eDone
|
|
};
|
|
|
|
//----------------------------------------------------------------------
|
|
//
|
|
// Statics
|
|
//
|
|
|
|
int32_t XULDocument::gRefCnt = 0;
|
|
|
|
LazyLogModule XULDocument::gXULLog("XULDocument");
|
|
|
|
//----------------------------------------------------------------------
|
|
|
|
struct BroadcastListener {
|
|
nsWeakPtr mListener;
|
|
RefPtr<nsAtom> mAttribute;
|
|
};
|
|
|
|
struct BroadcasterMapEntry : public PLDHashEntryHdr
|
|
{
|
|
Element* mBroadcaster; // [WEAK]
|
|
nsTArray<BroadcastListener*> mListeners; // [OWNING] of BroadcastListener objects
|
|
};
|
|
|
|
//----------------------------------------------------------------------
|
|
//
|
|
// ctors & dtors
|
|
//
|
|
|
|
namespace mozilla {
|
|
namespace dom {
|
|
|
|
XULDocument::XULDocument(void)
|
|
: XMLDocument("application/vnd.mozilla.xul+xml"),
|
|
mNextSrcLoadWaiter(nullptr),
|
|
mApplyingPersistedAttrs(false),
|
|
mIsWritingFastLoad(false),
|
|
mDocumentLoaded(false),
|
|
mStillWalking(false),
|
|
mRestrictPersistence(false),
|
|
mPendingSheets(0),
|
|
mDocLWTheme(Doc_Theme_Uninitialized),
|
|
mState(eState_Master),
|
|
mCurrentScriptProto(nullptr),
|
|
mOffThreadCompiling(false),
|
|
mOffThreadCompileStringBuf(nullptr),
|
|
mOffThreadCompileStringLength(0),
|
|
mResolutionPhase(nsForwardReference::eStart),
|
|
mBroadcasterMap(nullptr),
|
|
mInitialLayoutComplete(false),
|
|
mHandlingDelayedAttrChange(false),
|
|
mHandlingDelayedBroadcasters(false)
|
|
{
|
|
// Override the default in nsDocument
|
|
mCharacterSet = UTF_8_ENCODING;
|
|
|
|
mDefaultElementType = kNameSpaceID_XUL;
|
|
mType = eXUL;
|
|
|
|
mDelayFrameLoaderInitialization = true;
|
|
|
|
mAllowXULXBL = eTriTrue;
|
|
}
|
|
|
|
XULDocument::~XULDocument()
|
|
{
|
|
NS_ASSERTION(mNextSrcLoadWaiter == nullptr,
|
|
"unreferenced document still waiting for script source to load?");
|
|
|
|
// In case we failed somewhere early on and the forward observer
|
|
// decls never got resolved.
|
|
mForwardReferences.Clear();
|
|
// Likewise for any references we have to IDs where we might
|
|
// look for persisted data:
|
|
mPersistenceIds.Clear();
|
|
|
|
// Destroy our broadcaster map.
|
|
delete mBroadcasterMap;
|
|
|
|
Preferences::UnregisterCallback(XULDocument::DirectionChanged,
|
|
"intl.uidirection", this);
|
|
|
|
if (mOffThreadCompileStringBuf) {
|
|
js_free(mOffThreadCompileStringBuf);
|
|
}
|
|
}
|
|
|
|
} // namespace dom
|
|
} // namespace mozilla
|
|
|
|
nsresult
|
|
NS_NewXULDocument(nsIDocument** result)
|
|
{
|
|
NS_PRECONDITION(result != nullptr, "null ptr");
|
|
if (! result)
|
|
return NS_ERROR_NULL_POINTER;
|
|
|
|
RefPtr<XULDocument> doc = new XULDocument();
|
|
|
|
nsresult rv;
|
|
if (NS_FAILED(rv = doc->Init())) {
|
|
return rv;
|
|
}
|
|
|
|
doc.forget(result);
|
|
return NS_OK;
|
|
}
|
|
|
|
|
|
namespace mozilla {
|
|
namespace dom {
|
|
|
|
//----------------------------------------------------------------------
|
|
//
|
|
// nsISupports interface
|
|
//
|
|
|
|
NS_IMPL_CYCLE_COLLECTION_CLASS(XULDocument)
|
|
|
|
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(XULDocument, XMLDocument)
|
|
NS_ASSERTION(!nsCCUncollectableMarker::InGeneration(cb, tmp->GetMarkedCCGeneration()),
|
|
"Shouldn't traverse XULDocument!");
|
|
// XXX tmp->mForwardReferences?
|
|
// XXX tmp->mContextStack?
|
|
|
|
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCurrentPrototype)
|
|
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mMasterPrototype)
|
|
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCommandDispatcher)
|
|
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPrototypes)
|
|
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mLocalStore)
|
|
|
|
if (tmp->mOverlayLoadObservers) {
|
|
for (auto iter = tmp->mOverlayLoadObservers->Iter();
|
|
!iter.Done();
|
|
iter.Next()) {
|
|
NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mOverlayLoadObservers value");
|
|
cb.NoteXPCOMChild(iter.Data());
|
|
}
|
|
}
|
|
if (tmp->mPendingOverlayLoadNotifications) {
|
|
for (auto iter = tmp->mPendingOverlayLoadNotifications->Iter();
|
|
!iter.Done();
|
|
iter.Next()) {
|
|
NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mPendingOverlayLoadNotifications value");
|
|
cb.NoteXPCOMChild(iter.Data());
|
|
}
|
|
}
|
|
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
|
|
|
|
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(XULDocument, XMLDocument)
|
|
NS_IMPL_CYCLE_COLLECTION_UNLINK(mCommandDispatcher)
|
|
NS_IMPL_CYCLE_COLLECTION_UNLINK(mLocalStore)
|
|
//XXX We should probably unlink all the objects we traverse.
|
|
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
|
|
|
|
NS_IMPL_ISUPPORTS_CYCLE_COLLECTION_INHERITED(XULDocument,
|
|
XMLDocument,
|
|
nsIStreamLoaderObserver,
|
|
nsICSSLoaderObserver,
|
|
nsIOffThreadScriptReceiver)
|
|
|
|
|
|
//----------------------------------------------------------------------
|
|
//
|
|
// nsIDocument interface
|
|
//
|
|
|
|
void
|
|
XULDocument::Reset(nsIChannel* aChannel, nsILoadGroup* aLoadGroup)
|
|
{
|
|
NS_NOTREACHED("Reset");
|
|
}
|
|
|
|
void
|
|
XULDocument::ResetToURI(nsIURI* aURI, nsILoadGroup* aLoadGroup,
|
|
nsIPrincipal* aPrincipal)
|
|
{
|
|
NS_NOTREACHED("ResetToURI");
|
|
}
|
|
|
|
void
|
|
XULDocument::SetContentType(const nsAString& aContentType)
|
|
{
|
|
NS_ASSERTION(aContentType.EqualsLiteral("application/vnd.mozilla.xul+xml"),
|
|
"xul-documents always has content-type application/vnd.mozilla.xul+xml");
|
|
// Don't do anything, xul always has the mimetype
|
|
// application/vnd.mozilla.xul+xml
|
|
}
|
|
|
|
// This is called when the master document begins loading, whether it's
|
|
// being cached or not.
|
|
nsresult
|
|
XULDocument::StartDocumentLoad(const char* aCommand, nsIChannel* aChannel,
|
|
nsILoadGroup* aLoadGroup,
|
|
nsISupports* aContainer,
|
|
nsIStreamListener **aDocListener,
|
|
bool aReset, nsIContentSink* aSink)
|
|
{
|
|
if (MOZ_LOG_TEST(gXULLog, LogLevel::Warning)) {
|
|
|
|
nsCOMPtr<nsIURI> uri;
|
|
nsresult rv = aChannel->GetOriginalURI(getter_AddRefs(uri));
|
|
if (NS_SUCCEEDED(rv)) {
|
|
nsAutoCString urlspec;
|
|
rv = uri->GetSpec(urlspec);
|
|
if (NS_SUCCEEDED(rv)) {
|
|
MOZ_LOG(gXULLog, LogLevel::Warning,
|
|
("xul: load document '%s'", urlspec.get()));
|
|
}
|
|
}
|
|
}
|
|
// NOTE: If this ever starts calling nsDocument::StartDocumentLoad
|
|
// we'll possibly need to reset our content type afterwards.
|
|
mStillWalking = true;
|
|
mMayStartLayout = false;
|
|
mDocumentLoadGroup = do_GetWeakReference(aLoadGroup);
|
|
|
|
mChannel = aChannel;
|
|
|
|
// Get the URI. Note that this should match nsDocShell::OnLoadingSite
|
|
nsresult rv =
|
|
NS_GetFinalChannelURI(aChannel, getter_AddRefs(mDocumentURI));
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
mOriginalURI = mDocumentURI;
|
|
|
|
// Get the document's principal
|
|
nsCOMPtr<nsIPrincipal> principal;
|
|
nsContentUtils::GetSecurityManager()->
|
|
GetChannelResultPrincipal(mChannel, getter_AddRefs(principal));
|
|
principal = MaybeDowngradePrincipal(principal);
|
|
|
|
ResetStylesheetsToURI(mDocumentURI);
|
|
|
|
RetrieveRelevantHeaders(aChannel);
|
|
|
|
// Look in the chrome cache: we've got this puppy loaded
|
|
// already.
|
|
nsXULPrototypeDocument* proto = IsChromeURI(mDocumentURI) ?
|
|
nsXULPrototypeCache::GetInstance()->GetPrototype(mDocumentURI) :
|
|
nullptr;
|
|
|
|
// Same comment as nsChromeProtocolHandler::NewChannel and
|
|
// XULDocument::ResumeWalk
|
|
// - Ben Goodger
|
|
//
|
|
// We don't abort on failure here because there are too many valid
|
|
// cases that can return failure, and the null-ness of |proto| is enough
|
|
// to trigger the fail-safe parse-from-disk solution. Example failure cases
|
|
// (for reference) include:
|
|
//
|
|
// NS_ERROR_NOT_AVAILABLE: the URI cannot be found in the startup cache,
|
|
// parse from disk
|
|
// other: the startup cache file could not be found, probably
|
|
// due to being accessed before a profile has been selected (e.g.
|
|
// loading chrome for the profile manager itself). This must be
|
|
// parsed from disk.
|
|
|
|
if (proto) {
|
|
// If we're racing with another document to load proto, wait till the
|
|
// load has finished loading before trying to add cloned style sheets.
|
|
// XULDocument::EndLoad will call proto->NotifyLoadDone, which will
|
|
// find all racing documents and notify them via OnPrototypeLoadDone,
|
|
// which will add style sheet clones to each document.
|
|
bool loaded;
|
|
rv = proto->AwaitLoadDone(this, &loaded);
|
|
if (NS_FAILED(rv)) return rv;
|
|
|
|
mMasterPrototype = mCurrentPrototype = proto;
|
|
|
|
// Set up the right principal on ourselves.
|
|
SetPrincipal(proto->DocumentPrincipal());
|
|
|
|
// We need a listener, even if proto is not yet loaded, in which
|
|
// event the listener's OnStopRequest method does nothing, and all
|
|
// the interesting work happens below XULDocument::EndLoad, from
|
|
// the call there to mCurrentPrototype->NotifyLoadDone().
|
|
*aDocListener = new CachedChromeStreamListener(this, loaded);
|
|
}
|
|
else {
|
|
bool useXULCache = nsXULPrototypeCache::GetInstance()->IsEnabled();
|
|
bool fillXULCache = (useXULCache && IsChromeURI(mDocumentURI));
|
|
|
|
|
|
// It's just a vanilla document load. Create a parser to deal
|
|
// with the stream n' stuff.
|
|
|
|
nsCOMPtr<nsIParser> parser;
|
|
rv = PrepareToLoadPrototype(mDocumentURI, aCommand, principal,
|
|
getter_AddRefs(parser));
|
|
if (NS_FAILED(rv)) return rv;
|
|
|
|
// Predicate mIsWritingFastLoad on the XUL cache being enabled,
|
|
// so we don't have to re-check whether the cache is enabled all
|
|
// the time.
|
|
mIsWritingFastLoad = useXULCache;
|
|
|
|
nsCOMPtr<nsIStreamListener> listener = do_QueryInterface(parser, &rv);
|
|
NS_ASSERTION(NS_SUCCEEDED(rv), "parser doesn't support nsIStreamListener");
|
|
if (NS_FAILED(rv)) return rv;
|
|
|
|
*aDocListener = listener;
|
|
|
|
parser->Parse(mDocumentURI);
|
|
|
|
// Put the current prototype, created under PrepareToLoad, into the
|
|
// XUL prototype cache now. We can't do this under PrepareToLoad or
|
|
// overlay loading will break; search for PutPrototype in ResumeWalk
|
|
// and see the comment there.
|
|
if (fillXULCache) {
|
|
nsXULPrototypeCache::GetInstance()->PutPrototype(mCurrentPrototype);
|
|
}
|
|
}
|
|
|
|
NS_IF_ADDREF(*aDocListener);
|
|
return NS_OK;
|
|
}
|
|
|
|
// This gets invoked after a prototype for this document or one of
|
|
// its overlays is fully built in the content sink.
|
|
void
|
|
XULDocument::EndLoad()
|
|
{
|
|
// This can happen if an overlay fails to load
|
|
if (!mCurrentPrototype)
|
|
return;
|
|
|
|
nsresult rv;
|
|
|
|
// Whack the prototype document into the cache so that the next
|
|
// time somebody asks for it, they don't need to load it by hand.
|
|
|
|
nsCOMPtr<nsIURI> uri = mCurrentPrototype->GetURI();
|
|
bool isChrome = IsChromeURI(uri);
|
|
|
|
// Remember if the XUL cache is on
|
|
bool useXULCache = nsXULPrototypeCache::GetInstance()->IsEnabled();
|
|
|
|
// If the current prototype is an overlay document (non-master prototype)
|
|
// and we're filling the FastLoad disk cache, tell the cache we're done
|
|
// loading it, and write the prototype. The master prototype is put into
|
|
// the cache earlier in XULDocument::StartDocumentLoad.
|
|
if (useXULCache && mIsWritingFastLoad && isChrome &&
|
|
mMasterPrototype != mCurrentPrototype) {
|
|
nsXULPrototypeCache::GetInstance()->WritePrototype(mCurrentPrototype);
|
|
}
|
|
|
|
if (IsOverlayAllowed(uri)) {
|
|
nsCOMPtr<nsIXULOverlayProvider> reg =
|
|
mozilla::services::GetXULOverlayProviderService();
|
|
|
|
if (reg) {
|
|
nsCOMPtr<nsISimpleEnumerator> overlays;
|
|
rv = reg->GetStyleOverlays(uri, getter_AddRefs(overlays));
|
|
if (NS_FAILED(rv)) return;
|
|
|
|
bool moreSheets;
|
|
nsCOMPtr<nsISupports> next;
|
|
nsCOMPtr<nsIURI> sheetURI;
|
|
|
|
while (NS_SUCCEEDED(rv = overlays->HasMoreElements(&moreSheets)) &&
|
|
moreSheets) {
|
|
overlays->GetNext(getter_AddRefs(next));
|
|
|
|
sheetURI = do_QueryInterface(next);
|
|
if (!sheetURI) {
|
|
NS_ERROR("Chrome registry handed me a non-nsIURI object!");
|
|
continue;
|
|
}
|
|
|
|
if (IsChromeURI(sheetURI)) {
|
|
mCurrentPrototype->AddStyleSheetReference(sheetURI);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (isChrome && useXULCache) {
|
|
// If it's a chrome prototype document, then notify any
|
|
// documents that raced to load the prototype, and awaited
|
|
// its load completion via proto->AwaitLoadDone().
|
|
rv = mCurrentPrototype->NotifyLoadDone();
|
|
if (NS_FAILED(rv)) return;
|
|
}
|
|
}
|
|
|
|
OnPrototypeLoadDone(true);
|
|
if (MOZ_LOG_TEST(gXULLog, LogLevel::Warning)) {
|
|
nsAutoCString urlspec;
|
|
rv = uri->GetSpec(urlspec);
|
|
if (NS_SUCCEEDED(rv)) {
|
|
MOZ_LOG(gXULLog, LogLevel::Warning,
|
|
("xul: Finished loading document '%s'", urlspec.get()));
|
|
}
|
|
}
|
|
}
|
|
|
|
nsresult
|
|
XULDocument::OnPrototypeLoadDone(bool aResumeWalk)
|
|
{
|
|
nsresult rv;
|
|
|
|
// Add the style overlays from chrome registry, if any.
|
|
rv = AddPrototypeSheets();
|
|
if (NS_FAILED(rv)) return rv;
|
|
|
|
rv = PrepareToWalk();
|
|
NS_ASSERTION(NS_SUCCEEDED(rv), "unable to prepare for walk");
|
|
if (NS_FAILED(rv)) return rv;
|
|
|
|
if (aResumeWalk) {
|
|
rv = ResumeWalk();
|
|
}
|
|
return rv;
|
|
}
|
|
|
|
// called when an error occurs parsing a document
|
|
bool
|
|
XULDocument::OnDocumentParserError()
|
|
{
|
|
// don't report errors that are from overlays
|
|
if (mCurrentPrototype && mMasterPrototype != mCurrentPrototype) {
|
|
nsCOMPtr<nsIURI> uri = mCurrentPrototype->GetURI();
|
|
if (IsChromeURI(uri)) {
|
|
nsCOMPtr<nsIObserverService> os =
|
|
mozilla::services::GetObserverService();
|
|
if (os)
|
|
os->NotifyObservers(uri, "xul-overlay-parsererror",
|
|
EmptyString().get());
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static void
|
|
ClearBroadcasterMapEntry(PLDHashTable* aTable, PLDHashEntryHdr* aEntry)
|
|
{
|
|
BroadcasterMapEntry* entry =
|
|
static_cast<BroadcasterMapEntry*>(aEntry);
|
|
for (size_t i = entry->mListeners.Length() - 1; i != (size_t)-1; --i) {
|
|
delete entry->mListeners[i];
|
|
}
|
|
entry->mListeners.Clear();
|
|
|
|
// N.B. that we need to manually run the dtor because we
|
|
// constructed the nsTArray object in-place.
|
|
entry->mListeners.~nsTArray<BroadcastListener*>();
|
|
}
|
|
|
|
static bool
|
|
CanBroadcast(int32_t aNameSpaceID, nsAtom* aAttribute)
|
|
{
|
|
// Don't push changes to the |id|, |persist|, |command| or
|
|
// |observes| attribute.
|
|
if (aNameSpaceID == kNameSpaceID_None) {
|
|
if ((aAttribute == nsGkAtoms::id) ||
|
|
(aAttribute == nsGkAtoms::persist) ||
|
|
(aAttribute == nsGkAtoms::command) ||
|
|
(aAttribute == nsGkAtoms::observes)) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
struct nsAttrNameInfo
|
|
{
|
|
nsAttrNameInfo(int32_t aNamespaceID, nsAtom* aName, nsAtom* aPrefix) :
|
|
mNamespaceID(aNamespaceID), mName(aName), mPrefix(aPrefix) {}
|
|
nsAttrNameInfo(const nsAttrNameInfo& aOther) :
|
|
mNamespaceID(aOther.mNamespaceID), mName(aOther.mName),
|
|
mPrefix(aOther.mPrefix) {}
|
|
int32_t mNamespaceID;
|
|
RefPtr<nsAtom> mName;
|
|
RefPtr<nsAtom> mPrefix;
|
|
};
|
|
|
|
void
|
|
XULDocument::SynchronizeBroadcastListener(Element *aBroadcaster,
|
|
Element *aListener,
|
|
const nsAString &aAttr)
|
|
{
|
|
if (!nsContentUtils::IsSafeToRunScript()) {
|
|
nsDelayedBroadcastUpdate delayedUpdate(aBroadcaster, aListener,
|
|
aAttr);
|
|
mDelayedBroadcasters.AppendElement(delayedUpdate);
|
|
MaybeBroadcast();
|
|
return;
|
|
}
|
|
bool notify = mDocumentLoaded || mHandlingDelayedBroadcasters;
|
|
|
|
if (aAttr.EqualsLiteral("*")) {
|
|
uint32_t count = aBroadcaster->GetAttrCount();
|
|
nsTArray<nsAttrNameInfo> attributes(count);
|
|
for (uint32_t i = 0; i < count; ++i) {
|
|
const nsAttrName* attrName = aBroadcaster->GetAttrNameAt(i);
|
|
int32_t nameSpaceID = attrName->NamespaceID();
|
|
nsAtom* name = attrName->LocalName();
|
|
|
|
// _Don't_ push the |id|, |ref|, or |persist| attribute's value!
|
|
if (! CanBroadcast(nameSpaceID, name))
|
|
continue;
|
|
|
|
attributes.AppendElement(nsAttrNameInfo(nameSpaceID, name,
|
|
attrName->GetPrefix()));
|
|
}
|
|
|
|
count = attributes.Length();
|
|
while (count-- > 0) {
|
|
int32_t nameSpaceID = attributes[count].mNamespaceID;
|
|
nsAtom* name = attributes[count].mName;
|
|
nsAutoString value;
|
|
if (aBroadcaster->GetAttr(nameSpaceID, name, value)) {
|
|
aListener->SetAttr(nameSpaceID, name, attributes[count].mPrefix,
|
|
value, notify);
|
|
}
|
|
|
|
#if 0
|
|
// XXX we don't fire the |onbroadcast| handler during
|
|
// initial hookup: doing so would potentially run the
|
|
// |onbroadcast| handler before the |onload| handler,
|
|
// which could define JS properties that mask XBL
|
|
// properties, etc.
|
|
ExecuteOnBroadcastHandlerFor(aBroadcaster, aListener, name);
|
|
#endif
|
|
}
|
|
}
|
|
else {
|
|
// Find out if the attribute is even present at all.
|
|
RefPtr<nsAtom> name = NS_Atomize(aAttr);
|
|
|
|
nsAutoString value;
|
|
if (aBroadcaster->GetAttr(kNameSpaceID_None, name, value)) {
|
|
aListener->SetAttr(kNameSpaceID_None, name, value, notify);
|
|
} else {
|
|
aListener->UnsetAttr(kNameSpaceID_None, name, notify);
|
|
}
|
|
|
|
#if 0
|
|
// XXX we don't fire the |onbroadcast| handler during initial
|
|
// hookup: doing so would potentially run the |onbroadcast|
|
|
// handler before the |onload| handler, which could define JS
|
|
// properties that mask XBL properties, etc.
|
|
ExecuteOnBroadcastHandlerFor(aBroadcaster, aListener, name);
|
|
#endif
|
|
}
|
|
}
|
|
|
|
void
|
|
XULDocument::AddBroadcastListenerFor(Element& aBroadcaster, Element& aListener,
|
|
const nsAString& aAttr, ErrorResult& aRv)
|
|
{
|
|
nsresult rv =
|
|
nsContentUtils::CheckSameOrigin(this, &aBroadcaster);
|
|
|
|
if (NS_FAILED(rv)) {
|
|
aRv.Throw(rv);
|
|
return;
|
|
}
|
|
|
|
rv = nsContentUtils::CheckSameOrigin(this, &aListener);
|
|
|
|
if (NS_FAILED(rv)) {
|
|
aRv.Throw(rv);
|
|
return;
|
|
}
|
|
|
|
static const PLDHashTableOps gOps = {
|
|
PLDHashTable::HashVoidPtrKeyStub,
|
|
PLDHashTable::MatchEntryStub,
|
|
PLDHashTable::MoveEntryStub,
|
|
ClearBroadcasterMapEntry,
|
|
nullptr
|
|
};
|
|
|
|
if (! mBroadcasterMap) {
|
|
mBroadcasterMap = new PLDHashTable(&gOps, sizeof(BroadcasterMapEntry));
|
|
}
|
|
|
|
auto entry = static_cast<BroadcasterMapEntry*>
|
|
(mBroadcasterMap->Search(&aBroadcaster));
|
|
if (!entry) {
|
|
entry = static_cast<BroadcasterMapEntry*>
|
|
(mBroadcasterMap->Add(&aBroadcaster, fallible));
|
|
|
|
if (! entry) {
|
|
aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
|
|
return;
|
|
}
|
|
|
|
entry->mBroadcaster = &aBroadcaster;
|
|
|
|
// N.B. placement new to construct the nsTArray object in-place
|
|
new (&entry->mListeners) nsTArray<BroadcastListener*>();
|
|
}
|
|
|
|
// Only add the listener if it's not there already!
|
|
RefPtr<nsAtom> attr = NS_Atomize(aAttr);
|
|
|
|
for (size_t i = entry->mListeners.Length() - 1; i != (size_t)-1; --i) {
|
|
BroadcastListener* bl = entry->mListeners[i];
|
|
nsCOMPtr<Element> blListener = do_QueryReferent(bl->mListener);
|
|
|
|
if (blListener == &aListener && bl->mAttribute == attr)
|
|
return;
|
|
}
|
|
|
|
BroadcastListener* bl = new BroadcastListener;
|
|
bl->mListener = do_GetWeakReference(&aListener);
|
|
bl->mAttribute = attr;
|
|
|
|
entry->mListeners.AppendElement(bl);
|
|
|
|
SynchronizeBroadcastListener(&aBroadcaster, &aListener, aAttr);
|
|
}
|
|
|
|
void
|
|
XULDocument::RemoveBroadcastListenerFor(Element& aBroadcaster,
|
|
Element& aListener,
|
|
const nsAString& aAttr)
|
|
{
|
|
// If we haven't added any broadcast listeners, then there sure
|
|
// aren't any to remove.
|
|
if (! mBroadcasterMap)
|
|
return;
|
|
|
|
auto entry = static_cast<BroadcasterMapEntry*>
|
|
(mBroadcasterMap->Search(&aBroadcaster));
|
|
if (entry) {
|
|
RefPtr<nsAtom> attr = NS_Atomize(aAttr);
|
|
for (size_t i = entry->mListeners.Length() - 1; i != (size_t)-1; --i) {
|
|
BroadcastListener* bl = entry->mListeners[i];
|
|
nsCOMPtr<Element> blListener = do_QueryReferent(bl->mListener);
|
|
|
|
if (blListener == &aListener && bl->mAttribute == attr) {
|
|
entry->mListeners.RemoveElementAt(i);
|
|
delete bl;
|
|
|
|
if (entry->mListeners.IsEmpty())
|
|
mBroadcasterMap->RemoveEntry(entry);
|
|
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
nsresult
|
|
XULDocument::ExecuteOnBroadcastHandlerFor(Element* aBroadcaster,
|
|
Element* aListener,
|
|
nsAtom* aAttr)
|
|
{
|
|
// Now we execute the onchange handler in the context of the
|
|
// observer. We need to find the observer in order to
|
|
// execute the handler.
|
|
|
|
for (nsIContent* child = aListener->GetFirstChild();
|
|
child;
|
|
child = child->GetNextSibling()) {
|
|
|
|
// Look for an <observes> element beneath the listener. This
|
|
// ought to have an |element| attribute that refers to
|
|
// aBroadcaster, and an |attribute| element that tells us what
|
|
// attriubtes we're listening for.
|
|
if (!child->IsXULElement(nsGkAtoms::observes))
|
|
continue;
|
|
|
|
// Is this the element that was listening to us?
|
|
nsAutoString listeningToID;
|
|
child->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::element, listeningToID);
|
|
|
|
nsAutoString broadcasterID;
|
|
aBroadcaster->GetAttr(kNameSpaceID_None, nsGkAtoms::id, broadcasterID);
|
|
|
|
if (listeningToID != broadcasterID)
|
|
continue;
|
|
|
|
// We are observing the broadcaster, but is this the right
|
|
// attribute?
|
|
nsAutoString listeningToAttribute;
|
|
child->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::attribute,
|
|
listeningToAttribute);
|
|
|
|
if (!aAttr->Equals(listeningToAttribute) &&
|
|
!listeningToAttribute.EqualsLiteral("*")) {
|
|
continue;
|
|
}
|
|
|
|
// This is the right <observes> element. Execute the
|
|
// |onbroadcast| event handler
|
|
WidgetEvent event(true, eXULBroadcast);
|
|
|
|
RefPtr<nsPresContext> presContext = GetPresContext();
|
|
if (presContext) {
|
|
// Handle the DOM event
|
|
nsEventStatus status = nsEventStatus_eIgnore;
|
|
EventDispatcher::Dispatch(child, presContext, &event, nullptr,
|
|
&status);
|
|
}
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
static bool
|
|
ShouldPersistAttribute(Element* aElement, nsAtom* aAttribute)
|
|
{
|
|
if (aElement->IsXULElement(nsGkAtoms::window)) {
|
|
// This is not an element of the top document, its owner is
|
|
// not an nsXULWindow. Persist it.
|
|
if (aElement->OwnerDoc()->GetParentDocument()) {
|
|
return true;
|
|
}
|
|
// The following attributes of xul:window should be handled in
|
|
// nsXULWindow::SavePersistentAttributes instead of here.
|
|
if (aAttribute == nsGkAtoms::screenX ||
|
|
aAttribute == nsGkAtoms::screenY ||
|
|
aAttribute == nsGkAtoms::width ||
|
|
aAttribute == nsGkAtoms::height ||
|
|
aAttribute == nsGkAtoms::sizemode) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void
|
|
XULDocument::AttributeChanged(Element* aElement, int32_t aNameSpaceID,
|
|
nsAtom* aAttribute, int32_t aModType,
|
|
const nsAttrValue* aOldValue)
|
|
{
|
|
NS_ASSERTION(aElement->OwnerDoc() == this, "unexpected doc");
|
|
|
|
// Might not need this, but be safe for now.
|
|
nsCOMPtr<nsIMutationObserver> kungFuDeathGrip(this);
|
|
|
|
// Synchronize broadcast listeners
|
|
if (mBroadcasterMap &&
|
|
CanBroadcast(aNameSpaceID, aAttribute)) {
|
|
auto entry = static_cast<BroadcasterMapEntry*>
|
|
(mBroadcasterMap->Search(aElement));
|
|
|
|
if (entry) {
|
|
// We've got listeners: push the value.
|
|
nsAutoString value;
|
|
bool attrSet = aElement->GetAttr(kNameSpaceID_None, aAttribute, value);
|
|
|
|
for (size_t i = entry->mListeners.Length() - 1; i != (size_t)-1; --i) {
|
|
BroadcastListener* bl = entry->mListeners[i];
|
|
if ((bl->mAttribute == aAttribute) ||
|
|
(bl->mAttribute == nsGkAtoms::_asterisk)) {
|
|
nsCOMPtr<Element> listenerEl
|
|
= do_QueryReferent(bl->mListener);
|
|
if (listenerEl) {
|
|
nsAutoString currentValue;
|
|
bool hasAttr = listenerEl->GetAttr(kNameSpaceID_None,
|
|
aAttribute,
|
|
currentValue);
|
|
// We need to update listener only if we're
|
|
// (1) removing an existing attribute,
|
|
// (2) adding a new attribute or
|
|
// (3) changing the value of an attribute.
|
|
bool needsAttrChange =
|
|
attrSet != hasAttr || !value.Equals(currentValue);
|
|
nsDelayedBroadcastUpdate delayedUpdate(aElement,
|
|
listenerEl,
|
|
aAttribute,
|
|
value,
|
|
attrSet,
|
|
needsAttrChange);
|
|
|
|
size_t index =
|
|
mDelayedAttrChangeBroadcasts.IndexOf(delayedUpdate,
|
|
0, nsDelayedBroadcastUpdate::Comparator());
|
|
if (index != mDelayedAttrChangeBroadcasts.NoIndex) {
|
|
if (mHandlingDelayedAttrChange) {
|
|
NS_WARNING("Broadcasting loop!");
|
|
continue;
|
|
}
|
|
mDelayedAttrChangeBroadcasts.RemoveElementAt(index);
|
|
}
|
|
|
|
mDelayedAttrChangeBroadcasts.AppendElement(delayedUpdate);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// checks for modifications in broadcasters
|
|
bool listener, resolved;
|
|
CheckBroadcasterHookup(aElement, &listener, &resolved);
|
|
|
|
// See if there is anything we need to persist in the localstore.
|
|
//
|
|
// XXX Namespace handling broken :-(
|
|
nsAutoString persist;
|
|
aElement->GetAttr(kNameSpaceID_None, nsGkAtoms::persist, persist);
|
|
// Persistence of attributes of xul:window is handled in nsXULWindow.
|
|
if (ShouldPersistAttribute(aElement, aAttribute) && !persist.IsEmpty() &&
|
|
// XXXldb This should check that it's a token, not just a substring.
|
|
persist.Find(nsDependentAtomString(aAttribute)) >= 0) {
|
|
nsContentUtils::AddScriptRunner(
|
|
NewRunnableMethod<Element*, int32_t, nsAtom*>(
|
|
"dom::XULDocument::DoPersist",
|
|
this,
|
|
&XULDocument::DoPersist,
|
|
aElement,
|
|
kNameSpaceID_None,
|
|
aAttribute));
|
|
}
|
|
}
|
|
|
|
void
|
|
XULDocument::ContentAppended(nsIContent* aFirstNewContent)
|
|
{
|
|
NS_ASSERTION(aFirstNewContent->OwnerDoc() == this, "unexpected doc");
|
|
|
|
// Might not need this, but be safe for now.
|
|
nsCOMPtr<nsIMutationObserver> kungFuDeathGrip(this);
|
|
|
|
// Update our element map
|
|
nsresult rv = NS_OK;
|
|
for (nsIContent* cur = aFirstNewContent; cur && NS_SUCCEEDED(rv);
|
|
cur = cur->GetNextSibling()) {
|
|
rv = AddSubtreeToDocument(cur);
|
|
}
|
|
}
|
|
|
|
void
|
|
XULDocument::ContentInserted(nsIContent* aChild)
|
|
{
|
|
NS_ASSERTION(aChild->OwnerDoc() == this, "unexpected doc");
|
|
|
|
// Might not need this, but be safe for now.
|
|
nsCOMPtr<nsIMutationObserver> kungFuDeathGrip(this);
|
|
|
|
AddSubtreeToDocument(aChild);
|
|
}
|
|
|
|
void
|
|
XULDocument::ContentRemoved(nsIContent* aChild, nsIContent* aPreviousSibling)
|
|
{
|
|
NS_ASSERTION(aChild->OwnerDoc() == this, "unexpected doc");
|
|
|
|
// Might not need this, but be safe for now.
|
|
nsCOMPtr<nsIMutationObserver> kungFuDeathGrip(this);
|
|
|
|
RemoveSubtreeFromDocument(aChild);
|
|
}
|
|
|
|
nsresult
|
|
XULDocument::AddForwardReference(nsForwardReference* aRef)
|
|
{
|
|
if (mResolutionPhase < aRef->GetPhase()) {
|
|
if (!mForwardReferences.AppendElement(aRef)) {
|
|
delete aRef;
|
|
return NS_ERROR_OUT_OF_MEMORY;
|
|
}
|
|
}
|
|
else {
|
|
NS_ERROR("forward references have already been resolved");
|
|
delete aRef;
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult
|
|
XULDocument::ResolveForwardReferences()
|
|
{
|
|
if (mResolutionPhase == nsForwardReference::eDone)
|
|
return NS_OK;
|
|
|
|
NS_ASSERTION(mResolutionPhase == nsForwardReference::eStart,
|
|
"nested ResolveForwardReferences()");
|
|
|
|
// Resolve each outstanding 'forward' reference. We iterate
|
|
// through the list of forward references until no more forward
|
|
// references can be resolved. This annealing process is
|
|
// guaranteed to converge because we've "closed the gate" to new
|
|
// forward references.
|
|
|
|
const nsForwardReference::Phase* pass = nsForwardReference::kPasses;
|
|
while ((mResolutionPhase = *pass) != nsForwardReference::eDone) {
|
|
uint32_t previous = 0;
|
|
while (mForwardReferences.Length() &&
|
|
mForwardReferences.Length() != previous) {
|
|
previous = mForwardReferences.Length();
|
|
|
|
for (uint32_t i = 0; i < mForwardReferences.Length(); ++i) {
|
|
nsForwardReference* fwdref = mForwardReferences[i];
|
|
|
|
if (fwdref->GetPhase() == *pass) {
|
|
nsForwardReference::Result result = fwdref->Resolve();
|
|
|
|
switch (result) {
|
|
case nsForwardReference::eResolve_Succeeded:
|
|
case nsForwardReference::eResolve_Error:
|
|
mForwardReferences.RemoveElementAt(i);
|
|
|
|
// fixup because we removed from list
|
|
--i;
|
|
break;
|
|
|
|
case nsForwardReference::eResolve_Later:
|
|
// do nothing. we'll try again later
|
|
;
|
|
}
|
|
|
|
if (mResolutionPhase == nsForwardReference::eStart) {
|
|
// Resolve() loaded a dynamic overlay,
|
|
// (see XULDocument::LoadOverlayInternal()).
|
|
// Return for now, we will be called again.
|
|
return NS_OK;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
++pass;
|
|
}
|
|
|
|
mForwardReferences.Clear();
|
|
return NS_OK;
|
|
}
|
|
|
|
//----------------------------------------------------------------------
|
|
//
|
|
// nsIDOMDocument interface
|
|
//
|
|
|
|
already_AddRefed<nsINodeList>
|
|
XULDocument::GetElementsByAttribute(const nsAString& aAttribute,
|
|
const nsAString& aValue)
|
|
{
|
|
RefPtr<nsAtom> attrAtom(NS_Atomize(aAttribute));
|
|
nsAutoPtr<nsString> attrValue(new nsString(aValue));
|
|
RefPtr<nsContentList> list = new nsContentList(this,
|
|
MatchAttribute,
|
|
nsContentUtils::DestroyMatchString,
|
|
attrValue.forget(),
|
|
true,
|
|
attrAtom,
|
|
kNameSpaceID_Unknown);
|
|
|
|
return list.forget();
|
|
}
|
|
|
|
already_AddRefed<nsINodeList>
|
|
XULDocument::GetElementsByAttributeNS(const nsAString& aNamespaceURI,
|
|
const nsAString& aAttribute,
|
|
const nsAString& aValue,
|
|
ErrorResult& aRv)
|
|
{
|
|
RefPtr<nsAtom> attrAtom(NS_Atomize(aAttribute));
|
|
nsAutoPtr<nsString> attrValue(new nsString(aValue));
|
|
|
|
int32_t nameSpaceId = kNameSpaceID_Wildcard;
|
|
if (!aNamespaceURI.EqualsLiteral("*")) {
|
|
nsresult rv =
|
|
nsContentUtils::NameSpaceManager()->RegisterNameSpace(aNamespaceURI,
|
|
nameSpaceId);
|
|
if (NS_FAILED(rv)) {
|
|
aRv.Throw(rv);
|
|
return nullptr;
|
|
}
|
|
}
|
|
|
|
RefPtr<nsContentList> list = new nsContentList(this,
|
|
MatchAttribute,
|
|
nsContentUtils::DestroyMatchString,
|
|
attrValue.forget(),
|
|
true,
|
|
attrAtom,
|
|
nameSpaceId);
|
|
return list.forget();
|
|
}
|
|
|
|
void
|
|
XULDocument::Persist(const nsAString& aID,
|
|
const nsAString& aAttr,
|
|
ErrorResult& aRv)
|
|
{
|
|
// If we're currently reading persisted attributes out of the
|
|
// localstore, _don't_ re-enter and try to set them again!
|
|
if (mApplyingPersistedAttrs) {
|
|
return;
|
|
}
|
|
|
|
Element* element = nsDocument::GetElementById(aID);
|
|
if (!element) {
|
|
return;
|
|
}
|
|
|
|
RefPtr<nsAtom> tag;
|
|
int32_t nameSpaceID;
|
|
|
|
RefPtr<mozilla::dom::NodeInfo> ni = element->GetExistingAttrNameFromQName(aAttr);
|
|
nsresult rv;
|
|
if (ni) {
|
|
tag = ni->NameAtom();
|
|
nameSpaceID = ni->NamespaceID();
|
|
}
|
|
else {
|
|
// Make sure that this QName is going to be valid.
|
|
const char16_t *colon;
|
|
rv = nsContentUtils::CheckQName(PromiseFlatString(aAttr), true, &colon);
|
|
|
|
if (NS_FAILED(rv)) {
|
|
// There was an invalid character or it was malformed.
|
|
aRv.Throw(NS_ERROR_INVALID_ARG);
|
|
return;
|
|
}
|
|
|
|
if (colon) {
|
|
// We don't really handle namespace qualifiers in attribute names.
|
|
aRv.Throw(NS_ERROR_NOT_IMPLEMENTED);
|
|
return;
|
|
}
|
|
|
|
tag = NS_Atomize(aAttr);
|
|
if (NS_WARN_IF(!tag)) {
|
|
aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
|
|
return;
|
|
}
|
|
|
|
nameSpaceID = kNameSpaceID_None;
|
|
}
|
|
|
|
aRv = Persist(element, nameSpaceID, tag);
|
|
}
|
|
|
|
nsresult
|
|
XULDocument::Persist(Element* aElement, int32_t aNameSpaceID,
|
|
nsAtom* aAttribute)
|
|
{
|
|
// For non-chrome documents, persistance is simply broken
|
|
if (!nsContentUtils::IsSystemPrincipal(NodePrincipal()))
|
|
return NS_ERROR_NOT_AVAILABLE;
|
|
|
|
if (!mLocalStore) {
|
|
mLocalStore = do_GetService("@mozilla.org/xul/xulstore;1");
|
|
if (NS_WARN_IF(!mLocalStore)) {
|
|
return NS_ERROR_NOT_INITIALIZED;
|
|
}
|
|
}
|
|
|
|
nsAutoString id;
|
|
|
|
aElement->GetAttr(kNameSpaceID_None, nsGkAtoms::id, id);
|
|
nsAtomString attrstr(aAttribute);
|
|
|
|
nsAutoString valuestr;
|
|
aElement->GetAttr(kNameSpaceID_None, aAttribute, valuestr);
|
|
|
|
nsAutoCString utf8uri;
|
|
nsresult rv = mDocumentURI->GetSpec(utf8uri);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
NS_ConvertUTF8toUTF16 uri(utf8uri);
|
|
|
|
bool hasAttr;
|
|
rv = mLocalStore->HasValue(uri, id, attrstr, &hasAttr);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
if (hasAttr && valuestr.IsEmpty()) {
|
|
return mLocalStore->RemoveValue(uri, id, attrstr);
|
|
} else {
|
|
return mLocalStore->SetValue(uri, id, attrstr, valuestr);
|
|
}
|
|
}
|
|
|
|
|
|
nsresult
|
|
XULDocument::GetViewportSize(int32_t* aWidth,
|
|
int32_t* aHeight)
|
|
{
|
|
*aWidth = *aHeight = 0;
|
|
|
|
FlushPendingNotifications(FlushType::Layout);
|
|
|
|
nsIPresShell *shell = GetShell();
|
|
NS_ENSURE_TRUE(shell, NS_ERROR_FAILURE);
|
|
|
|
nsIFrame* frame = shell->GetRootFrame();
|
|
NS_ENSURE_TRUE(frame, NS_ERROR_FAILURE);
|
|
|
|
nsSize size = frame->GetSize();
|
|
|
|
*aWidth = nsPresContext::AppUnitsToIntCSSPixels(size.width);
|
|
*aHeight = nsPresContext::AppUnitsToIntCSSPixels(size.height);
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
int32_t
|
|
XULDocument::GetWidth(ErrorResult& aRv)
|
|
{
|
|
int32_t width = 0;
|
|
int32_t height = 0;
|
|
aRv = GetViewportSize(&width, &height);
|
|
return width;
|
|
}
|
|
|
|
int32_t
|
|
XULDocument::GetHeight(ErrorResult& aRv)
|
|
{
|
|
int32_t width = 0;
|
|
int32_t height = 0;
|
|
aRv = GetViewportSize(&width, &height);
|
|
return height;
|
|
}
|
|
|
|
static JSObject*
|
|
GetScopeObjectOfNode(nsINode* node)
|
|
{
|
|
MOZ_ASSERT(node, "Must not be called with null.");
|
|
|
|
// Window root occasionally keeps alive a node of a document whose
|
|
// window is already dead. If in this brief period someone calls
|
|
// GetPopupNode and we return that node, nsNodeSH::PreCreate will throw,
|
|
// because it will not know which scope this node belongs to. Returning
|
|
// an orphan node like that to JS would be a bug anyway, so to avoid
|
|
// this, let's do the same check as nsNodeSH::PreCreate does to
|
|
// determine the scope and if it fails let's just return null in
|
|
// XULDocument::GetPopupNode.
|
|
nsIDocument* doc = node->OwnerDoc();
|
|
MOZ_ASSERT(doc, "This should never happen.");
|
|
|
|
nsIGlobalObject* global = doc->GetScopeObject();
|
|
return global ? global->GetGlobalJSObject() : nullptr;
|
|
}
|
|
|
|
already_AddRefed<nsINode>
|
|
XULDocument::GetPopupNode()
|
|
{
|
|
nsCOMPtr<nsINode> node;
|
|
nsCOMPtr<nsPIWindowRoot> rootWin = GetWindowRoot();
|
|
if (rootWin) {
|
|
node = rootWin->GetPopupNode(); // addref happens here
|
|
}
|
|
|
|
if (!node) {
|
|
nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
|
|
if (pm) {
|
|
node = pm->GetLastTriggerPopupNode(this);
|
|
}
|
|
}
|
|
|
|
if (node && nsContentUtils::CanCallerAccess(node)
|
|
&& GetScopeObjectOfNode(node)) {
|
|
return node.forget();
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
void
|
|
XULDocument::SetPopupNode(nsINode* aNode)
|
|
{
|
|
nsCOMPtr<nsPIWindowRoot> rootWin = GetWindowRoot();
|
|
if (rootWin) {
|
|
rootWin->SetPopupNode(aNode);
|
|
}
|
|
}
|
|
|
|
// Returns the rangeOffset element from the XUL Popup Manager. This is for
|
|
// chrome callers only.
|
|
nsINode*
|
|
XULDocument::GetPopupRangeParent(ErrorResult& aRv)
|
|
{
|
|
nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
|
|
if (!pm) {
|
|
aRv.Throw(NS_ERROR_FAILURE);
|
|
return nullptr;
|
|
}
|
|
|
|
nsINode* rangeParent = pm->GetMouseLocationParent();
|
|
if (rangeParent && !nsContentUtils::CanCallerAccess(rangeParent)) {
|
|
aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
|
|
return nullptr;
|
|
}
|
|
|
|
return rangeParent;
|
|
}
|
|
|
|
// Returns the rangeOffset element from the XUL Popup Manager. We check the
|
|
// rangeParent to determine if the caller has rights to access to the data.
|
|
int32_t
|
|
XULDocument::GetPopupRangeOffset(ErrorResult& aRv)
|
|
{
|
|
nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
|
|
if (!pm) {
|
|
aRv.Throw(NS_ERROR_FAILURE);
|
|
return 0;
|
|
}
|
|
|
|
nsINode* rangeParent = pm->GetMouseLocationParent();
|
|
if (rangeParent && !nsContentUtils::CanCallerAccess(rangeParent)) {
|
|
aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
|
|
return 0;
|
|
}
|
|
|
|
return pm->MouseLocationOffset();
|
|
}
|
|
|
|
already_AddRefed<nsINode>
|
|
XULDocument::GetTooltipNode()
|
|
{
|
|
nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
|
|
if (pm) {
|
|
nsCOMPtr<nsINode> node = pm->GetLastTriggerTooltipNode(this);
|
|
if (node && nsContentUtils::CanCallerAccess(node)) {
|
|
return node.forget();
|
|
}
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
nsresult
|
|
XULDocument::AddElementToDocumentPre(Element* aElement)
|
|
{
|
|
// Do a bunch of work that's necessary when an element gets added
|
|
// to the XUL Document.
|
|
nsresult rv;
|
|
|
|
// 1. Add the element to the id map, since it seems this can be
|
|
// called when creating elements from prototypes.
|
|
nsAtom* id = aElement->GetID();
|
|
if (id) {
|
|
// FIXME: Shouldn't BindToTree take care of this?
|
|
nsAutoScriptBlocker scriptBlocker;
|
|
AddToIdTable(aElement, id);
|
|
}
|
|
|
|
// 2. If the element is a 'command updater' (i.e., has a
|
|
// "commandupdater='true'" attribute), then add the element to the
|
|
// document's command dispatcher
|
|
if (aElement->AttrValueIs(kNameSpaceID_None, nsGkAtoms::commandupdater,
|
|
nsGkAtoms::_true, eCaseMatters)) {
|
|
rv = nsXULContentUtils::SetCommandUpdater(this, aElement);
|
|
if (NS_FAILED(rv)) return rv;
|
|
}
|
|
|
|
// 3. Check for a broadcaster hookup attribute, in which case
|
|
// we'll hook the node up as a listener on a broadcaster.
|
|
bool listener, resolved;
|
|
rv = CheckBroadcasterHookup(aElement, &listener, &resolved);
|
|
if (NS_FAILED(rv)) return rv;
|
|
|
|
// If it's not there yet, we may be able to defer hookup until
|
|
// later.
|
|
if (listener && !resolved && (mResolutionPhase != nsForwardReference::eDone)) {
|
|
BroadcasterHookup* hookup = new BroadcasterHookup(this, aElement);
|
|
rv = AddForwardReference(hookup);
|
|
if (NS_FAILED(rv)) return rv;
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult
|
|
XULDocument::AddElementToDocumentPost(Element* aElement)
|
|
{
|
|
if (aElement == GetRootElement()) {
|
|
ResetDocumentDirection();
|
|
}
|
|
|
|
// We need to pay special attention to the keyset tag to set up a listener
|
|
if (aElement->NodeInfo()->Equals(nsGkAtoms::keyset, kNameSpaceID_XUL)) {
|
|
// Create our XUL key listener and hook it up.
|
|
nsXBLService::AttachGlobalKeyHandler(aElement);
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult
|
|
XULDocument::AddSubtreeToDocument(nsIContent* aContent)
|
|
{
|
|
NS_ASSERTION(aContent->GetUncomposedDoc() == this, "Element not in doc!");
|
|
// From here on we only care about elements.
|
|
if (!aContent->IsElement()) {
|
|
return NS_OK;
|
|
}
|
|
|
|
Element* aElement = aContent->AsElement();
|
|
|
|
// Do pre-order addition magic
|
|
nsresult rv = AddElementToDocumentPre(aElement);
|
|
if (NS_FAILED(rv)) return rv;
|
|
|
|
// Recurse to children
|
|
for (nsIContent* child = aElement->GetLastChild();
|
|
child;
|
|
child = child->GetPreviousSibling()) {
|
|
|
|
rv = AddSubtreeToDocument(child);
|
|
if (NS_FAILED(rv))
|
|
return rv;
|
|
}
|
|
|
|
// Do post-order addition magic
|
|
return AddElementToDocumentPost(aElement);
|
|
}
|
|
|
|
nsresult
|
|
XULDocument::RemoveSubtreeFromDocument(nsIContent* aContent)
|
|
{
|
|
// From here on we only care about elements.
|
|
if (!aContent->IsElement()) {
|
|
return NS_OK;
|
|
}
|
|
|
|
Element* aElement = aContent->AsElement();
|
|
|
|
// Do a bunch of cleanup to remove an element from the XUL
|
|
// document.
|
|
nsresult rv;
|
|
|
|
if (aElement->NodeInfo()->Equals(nsGkAtoms::keyset, kNameSpaceID_XUL)) {
|
|
nsXBLService::DetachGlobalKeyHandler(aElement);
|
|
}
|
|
|
|
// 1. Remove any children from the document.
|
|
for (nsIContent* child = aElement->GetLastChild();
|
|
child;
|
|
child = child->GetPreviousSibling()) {
|
|
|
|
rv = RemoveSubtreeFromDocument(child);
|
|
if (NS_FAILED(rv))
|
|
return rv;
|
|
}
|
|
|
|
// Remove the element from the id map, since we added it in
|
|
// AddElementToDocumentPre().
|
|
nsAtom* id = aElement->GetID();
|
|
if (id) {
|
|
// FIXME: Shouldn't UnbindFromTree take care of this?
|
|
nsAutoScriptBlocker scriptBlocker;
|
|
RemoveFromIdTable(aElement, id);
|
|
}
|
|
|
|
// 3. If the element is a 'command updater', then remove the
|
|
// element from the document's command dispatcher.
|
|
if (aElement->AttrValueIs(kNameSpaceID_None, nsGkAtoms::commandupdater,
|
|
nsGkAtoms::_true, eCaseMatters)) {
|
|
nsCOMPtr<nsIDOMElement> domelement = do_QueryInterface(aElement);
|
|
NS_ASSERTION(domelement != nullptr, "not a DOM element");
|
|
if (! domelement)
|
|
return NS_ERROR_UNEXPECTED;
|
|
|
|
rv = mCommandDispatcher->RemoveCommandUpdater(domelement);
|
|
if (NS_FAILED(rv)) return rv;
|
|
}
|
|
|
|
// 4. Remove the element from our broadcaster map, since it is no longer
|
|
// in the document.
|
|
nsCOMPtr<Element> broadcaster, listener;
|
|
nsAutoString attribute, broadcasterID;
|
|
rv = FindBroadcaster(aElement, getter_AddRefs(listener),
|
|
broadcasterID, attribute, getter_AddRefs(broadcaster));
|
|
if (rv == NS_FINDBROADCASTER_FOUND) {
|
|
RemoveBroadcastListenerFor(*broadcaster, *listener, attribute);
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
//----------------------------------------------------------------------
|
|
//
|
|
// nsIDOMNode interface
|
|
//
|
|
|
|
nsresult
|
|
XULDocument::Clone(mozilla::dom::NodeInfo *aNodeInfo, nsINode **aResult,
|
|
bool aPreallocateChildren) const
|
|
{
|
|
// We don't allow cloning of a XUL document
|
|
*aResult = nullptr;
|
|
return NS_ERROR_DOM_NOT_SUPPORTED_ERR;
|
|
}
|
|
|
|
|
|
//----------------------------------------------------------------------
|
|
//
|
|
// Implementation methods
|
|
//
|
|
|
|
nsresult
|
|
XULDocument::Init()
|
|
{
|
|
nsresult rv = XMLDocument::Init();
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
// Create our command dispatcher and hook it up.
|
|
mCommandDispatcher = new nsXULCommandDispatcher(this);
|
|
|
|
if (gRefCnt++ == 0) {
|
|
// ensure that the XUL prototype cache is instantiated successfully,
|
|
// so that we can use nsXULPrototypeCache::GetInstance() without
|
|
// null-checks in the rest of the class.
|
|
nsXULPrototypeCache* cache = nsXULPrototypeCache::GetInstance();
|
|
if (!cache) {
|
|
NS_ERROR("Could not instantiate nsXULPrototypeCache");
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
}
|
|
|
|
Preferences::RegisterCallback(XULDocument::DirectionChanged,
|
|
"intl.uidirection", this);
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
|
|
nsresult
|
|
XULDocument::StartLayout(void)
|
|
{
|
|
mMayStartLayout = true;
|
|
nsCOMPtr<nsIPresShell> shell = GetShell();
|
|
if (shell) {
|
|
// Resize-reflow this time
|
|
nsPresContext *cx = shell->GetPresContext();
|
|
NS_ASSERTION(cx != nullptr, "no pres context");
|
|
if (! cx)
|
|
return NS_ERROR_UNEXPECTED;
|
|
|
|
nsCOMPtr<nsIDocShell> docShell = cx->GetDocShell();
|
|
NS_ASSERTION(docShell != nullptr, "container is not a docshell");
|
|
if (! docShell)
|
|
return NS_ERROR_UNEXPECTED;
|
|
|
|
nsresult rv = shell->Initialize();
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
/* static */
|
|
bool
|
|
XULDocument::MatchAttribute(Element* aElement,
|
|
int32_t aNamespaceID,
|
|
nsAtom* aAttrName,
|
|
void* aData)
|
|
{
|
|
NS_PRECONDITION(aElement, "Must have content node to work with!");
|
|
nsString* attrValue = static_cast<nsString*>(aData);
|
|
if (aNamespaceID != kNameSpaceID_Unknown &&
|
|
aNamespaceID != kNameSpaceID_Wildcard) {
|
|
return attrValue->EqualsLiteral("*") ?
|
|
aElement->HasAttr(aNamespaceID, aAttrName) :
|
|
aElement->AttrValueIs(aNamespaceID, aAttrName, *attrValue,
|
|
eCaseMatters);
|
|
}
|
|
|
|
// Qualified name match. This takes more work.
|
|
|
|
uint32_t count = aElement->GetAttrCount();
|
|
for (uint32_t i = 0; i < count; ++i) {
|
|
const nsAttrName* name = aElement->GetAttrNameAt(i);
|
|
bool nameMatch;
|
|
if (name->IsAtom()) {
|
|
nameMatch = name->Atom() == aAttrName;
|
|
} else if (aNamespaceID == kNameSpaceID_Wildcard) {
|
|
nameMatch = name->NodeInfo()->Equals(aAttrName);
|
|
} else {
|
|
nameMatch = name->NodeInfo()->QualifiedNameEquals(aAttrName);
|
|
}
|
|
|
|
if (nameMatch) {
|
|
return attrValue->EqualsLiteral("*") ||
|
|
aElement->AttrValueIs(name->NamespaceID(), name->LocalName(),
|
|
*attrValue, eCaseMatters);
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
nsresult
|
|
XULDocument::PrepareToLoadPrototype(nsIURI* aURI, const char* aCommand,
|
|
nsIPrincipal* aDocumentPrincipal,
|
|
nsIParser** aResult)
|
|
{
|
|
nsresult rv;
|
|
|
|
// Create a new prototype document.
|
|
rv = NS_NewXULPrototypeDocument(getter_AddRefs(mCurrentPrototype));
|
|
if (NS_FAILED(rv)) return rv;
|
|
|
|
rv = mCurrentPrototype->InitPrincipal(aURI, aDocumentPrincipal);
|
|
if (NS_FAILED(rv)) {
|
|
mCurrentPrototype = nullptr;
|
|
return rv;
|
|
}
|
|
|
|
// Bootstrap the master document prototype.
|
|
if (! mMasterPrototype) {
|
|
mMasterPrototype = mCurrentPrototype;
|
|
// Set our principal based on the master proto.
|
|
SetPrincipal(aDocumentPrincipal);
|
|
}
|
|
|
|
// Create a XUL content sink, a parser, and kick off a load for
|
|
// the overlay.
|
|
RefPtr<XULContentSinkImpl> sink = new XULContentSinkImpl();
|
|
|
|
rv = sink->Init(this, mCurrentPrototype);
|
|
NS_ASSERTION(NS_SUCCEEDED(rv), "Unable to initialize datasource sink");
|
|
if (NS_FAILED(rv)) return rv;
|
|
|
|
nsCOMPtr<nsIParser> parser = do_CreateInstance(kParserCID, &rv);
|
|
NS_ASSERTION(NS_SUCCEEDED(rv), "unable to create parser");
|
|
if (NS_FAILED(rv)) return rv;
|
|
|
|
parser->SetCommand(nsCRT::strcmp(aCommand, "view-source") ? eViewNormal :
|
|
eViewSource);
|
|
|
|
parser->SetDocumentCharset(UTF_8_ENCODING,
|
|
kCharsetFromDocTypeDefault);
|
|
parser->SetContentSink(sink); // grabs a reference to the parser
|
|
|
|
parser.forget(aResult);
|
|
return NS_OK;
|
|
}
|
|
|
|
|
|
nsresult
|
|
XULDocument::ApplyPersistentAttributes()
|
|
{
|
|
// For non-chrome documents, persistance is simply broken
|
|
if (!nsContentUtils::IsSystemPrincipal(NodePrincipal()))
|
|
return NS_ERROR_NOT_AVAILABLE;
|
|
|
|
// Add all of the 'persisted' attributes into the content
|
|
// model.
|
|
if (!mLocalStore) {
|
|
mLocalStore = do_GetService("@mozilla.org/xul/xulstore;1");
|
|
if (NS_WARN_IF(!mLocalStore)) {
|
|
return NS_ERROR_NOT_INITIALIZED;
|
|
}
|
|
}
|
|
|
|
mApplyingPersistedAttrs = true;
|
|
ApplyPersistentAttributesInternal();
|
|
mApplyingPersistedAttrs = false;
|
|
|
|
// After we've applied persistence once, we should only reapply
|
|
// it to nodes created by overlays
|
|
mRestrictPersistence = true;
|
|
mPersistenceIds.Clear();
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
|
|
nsresult
|
|
XULDocument::ApplyPersistentAttributesInternal()
|
|
{
|
|
nsCOMArray<Element> elements;
|
|
|
|
nsAutoCString utf8uri;
|
|
nsresult rv = mDocumentURI->GetSpec(utf8uri);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
NS_ConvertUTF8toUTF16 uri(utf8uri);
|
|
|
|
// Get a list of element IDs for which persisted values are available
|
|
nsCOMPtr<nsIStringEnumerator> ids;
|
|
rv = mLocalStore->GetIDsEnumerator(uri, getter_AddRefs(ids));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
while (1) {
|
|
bool hasmore = false;
|
|
ids->HasMore(&hasmore);
|
|
if (!hasmore) {
|
|
break;
|
|
}
|
|
|
|
nsAutoString id;
|
|
ids->GetNext(id);
|
|
|
|
if (mRestrictPersistence && !mPersistenceIds.Contains(id)) {
|
|
continue;
|
|
}
|
|
|
|
nsIdentifierMapEntry* entry = mIdentifierMap.GetEntry(id);
|
|
if (!entry) {
|
|
continue;
|
|
}
|
|
|
|
// We want to hold strong refs to the elements while applying
|
|
// persistent attributes, just in case.
|
|
elements.Clear();
|
|
elements.SetCapacity(entry->GetIdElements().Length());
|
|
for (Element* element : entry->GetIdElements()) {
|
|
elements.AppendObject(element);
|
|
}
|
|
if (elements.IsEmpty()) {
|
|
continue;
|
|
}
|
|
|
|
rv = ApplyPersistentAttributesToElements(id, elements);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
|
|
nsresult
|
|
XULDocument::ApplyPersistentAttributesToElements(const nsAString &aID,
|
|
nsCOMArray<Element>& aElements)
|
|
{
|
|
nsAutoCString utf8uri;
|
|
nsresult rv = mDocumentURI->GetSpec(utf8uri);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
NS_ConvertUTF8toUTF16 uri(utf8uri);
|
|
|
|
// Get a list of attributes for which persisted values are available
|
|
nsCOMPtr<nsIStringEnumerator> attrs;
|
|
rv = mLocalStore->GetAttributeEnumerator(uri, aID, getter_AddRefs(attrs));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
while (1) {
|
|
bool hasmore = PR_FALSE;
|
|
attrs->HasMore(&hasmore);
|
|
if (!hasmore) {
|
|
break;
|
|
}
|
|
|
|
nsAutoString attrstr;
|
|
attrs->GetNext(attrstr);
|
|
|
|
nsAutoString value;
|
|
rv = mLocalStore->GetValue(uri, aID, attrstr, value);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
RefPtr<nsAtom> attr = NS_Atomize(attrstr);
|
|
if (NS_WARN_IF(!attr)) {
|
|
return NS_ERROR_OUT_OF_MEMORY;
|
|
}
|
|
|
|
uint32_t cnt = aElements.Count();
|
|
|
|
for (int32_t i = int32_t(cnt) - 1; i >= 0; --i) {
|
|
RefPtr<Element> element = aElements.SafeObjectAt(i);
|
|
if (!element) {
|
|
continue;
|
|
}
|
|
|
|
Unused << element->SetAttr(kNameSpaceID_None, attr, value, true);
|
|
}
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
void
|
|
XULDocument::TraceProtos(JSTracer* aTrc)
|
|
{
|
|
uint32_t i, count = mPrototypes.Length();
|
|
for (i = 0; i < count; ++i) {
|
|
mPrototypes[i]->TraceProtos(aTrc);
|
|
}
|
|
|
|
if (mCurrentPrototype) {
|
|
mCurrentPrototype->TraceProtos(aTrc);
|
|
}
|
|
}
|
|
|
|
//----------------------------------------------------------------------
|
|
//
|
|
// XULDocument::ContextStack
|
|
//
|
|
|
|
XULDocument::ContextStack::ContextStack()
|
|
: mTop(nullptr), mDepth(0)
|
|
{
|
|
}
|
|
|
|
XULDocument::ContextStack::~ContextStack()
|
|
{
|
|
while (mTop) {
|
|
Entry* doomed = mTop;
|
|
mTop = mTop->mNext;
|
|
NS_IF_RELEASE(doomed->mElement);
|
|
delete doomed;
|
|
}
|
|
}
|
|
|
|
nsresult
|
|
XULDocument::ContextStack::Push(nsXULPrototypeElement* aPrototype,
|
|
nsIContent* aElement)
|
|
{
|
|
Entry* entry = new Entry;
|
|
entry->mPrototype = aPrototype;
|
|
entry->mElement = aElement;
|
|
NS_IF_ADDREF(entry->mElement);
|
|
entry->mIndex = 0;
|
|
|
|
entry->mNext = mTop;
|
|
mTop = entry;
|
|
|
|
++mDepth;
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult
|
|
XULDocument::ContextStack::Pop()
|
|
{
|
|
if (mDepth == 0)
|
|
return NS_ERROR_UNEXPECTED;
|
|
|
|
Entry* doomed = mTop;
|
|
mTop = mTop->mNext;
|
|
--mDepth;
|
|
|
|
NS_IF_RELEASE(doomed->mElement);
|
|
delete doomed;
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult
|
|
XULDocument::ContextStack::Peek(nsXULPrototypeElement** aPrototype,
|
|
nsIContent** aElement,
|
|
int32_t* aIndex)
|
|
{
|
|
if (mDepth == 0)
|
|
return NS_ERROR_UNEXPECTED;
|
|
|
|
*aPrototype = mTop->mPrototype;
|
|
*aElement = mTop->mElement;
|
|
NS_IF_ADDREF(*aElement);
|
|
*aIndex = mTop->mIndex;
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
|
|
nsresult
|
|
XULDocument::ContextStack::SetTopIndex(int32_t aIndex)
|
|
{
|
|
if (mDepth == 0)
|
|
return NS_ERROR_UNEXPECTED;
|
|
|
|
mTop->mIndex = aIndex;
|
|
return NS_OK;
|
|
}
|
|
|
|
|
|
//----------------------------------------------------------------------
|
|
//
|
|
// Content model walking routines
|
|
//
|
|
|
|
nsresult
|
|
XULDocument::PrepareToWalk()
|
|
{
|
|
// Prepare to walk the mCurrentPrototype
|
|
nsresult rv;
|
|
|
|
// Keep an owning reference to the prototype document so that its
|
|
// elements aren't yanked from beneath us.
|
|
mPrototypes.AppendElement(mCurrentPrototype);
|
|
|
|
// Get the prototype's root element and initialize the context
|
|
// stack for the prototype walk.
|
|
nsXULPrototypeElement* proto = mCurrentPrototype->GetRootElement();
|
|
|
|
if (! proto) {
|
|
if (MOZ_LOG_TEST(gXULLog, LogLevel::Error)) {
|
|
nsCOMPtr<nsIURI> url = mCurrentPrototype->GetURI();
|
|
|
|
nsAutoCString urlspec;
|
|
rv = url->GetSpec(urlspec);
|
|
if (NS_FAILED(rv)) return rv;
|
|
|
|
MOZ_LOG(gXULLog, LogLevel::Error,
|
|
("xul: error parsing '%s'", urlspec.get()));
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
nsINode* nodeToInsertBefore = nsINode::GetFirstChild();
|
|
if (mState != eState_Master) {
|
|
nodeToInsertBefore = GetRootElement();
|
|
}
|
|
|
|
const nsTArray<RefPtr<nsXULPrototypePI> >& processingInstructions =
|
|
mCurrentPrototype->GetProcessingInstructions();
|
|
|
|
uint32_t total = processingInstructions.Length();
|
|
for (uint32_t i = 0; i < total; ++i) {
|
|
rv = CreateAndInsertPI(processingInstructions[i],
|
|
this, nodeToInsertBefore);
|
|
if (NS_FAILED(rv)) return rv;
|
|
}
|
|
|
|
// Now check the chrome registry for any additional overlays.
|
|
rv = AddChromeOverlays();
|
|
if (NS_FAILED(rv)) return rv;
|
|
|
|
// Do one-time initialization if we're preparing to walk the
|
|
// master document's prototype.
|
|
RefPtr<Element> root;
|
|
|
|
if (mState == eState_Master) {
|
|
// Add the root element
|
|
rv = CreateElementFromPrototype(proto, getter_AddRefs(root), true);
|
|
if (NS_FAILED(rv)) return rv;
|
|
|
|
rv = AppendChildTo(root, false);
|
|
if (NS_FAILED(rv)) return rv;
|
|
|
|
// Block onload until we've finished building the complete
|
|
// document content model.
|
|
BlockOnload();
|
|
}
|
|
|
|
// There'd better not be anything on the context stack at this
|
|
// point! This is the basis case for our "induction" in
|
|
// ResumeWalk(), below, which'll assume that there's always a
|
|
// content element on the context stack if either 1) we're in the
|
|
// "master" document, or 2) we're in an overlay, and we've got
|
|
// more than one prototype element (the single, root "overlay"
|
|
// element) on the stack.
|
|
NS_ASSERTION(mContextStack.Depth() == 0, "something's on the context stack already");
|
|
if (mContextStack.Depth() != 0)
|
|
return NS_ERROR_UNEXPECTED;
|
|
|
|
rv = mContextStack.Push(proto, root);
|
|
if (NS_FAILED(rv)) return rv;
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult
|
|
XULDocument::CreateAndInsertPI(const nsXULPrototypePI* aProtoPI,
|
|
nsINode* aParent, nsINode* aBeforeThis)
|
|
{
|
|
NS_PRECONDITION(aProtoPI, "null ptr");
|
|
NS_PRECONDITION(aParent, "null ptr");
|
|
|
|
RefPtr<ProcessingInstruction> node =
|
|
NS_NewXMLProcessingInstruction(mNodeInfoManager, aProtoPI->mTarget,
|
|
aProtoPI->mData);
|
|
|
|
nsresult rv;
|
|
if (aProtoPI->mTarget.EqualsLiteral("xml-stylesheet")) {
|
|
rv = InsertXMLStylesheetPI(aProtoPI, aParent, aBeforeThis, node);
|
|
} else if (aProtoPI->mTarget.EqualsLiteral("xul-overlay")) {
|
|
rv = InsertXULOverlayPI(aProtoPI, aParent, aBeforeThis, node);
|
|
} else {
|
|
// No special processing, just add the PI to the document.
|
|
rv = aParent->InsertChildBefore(node->AsContent(),
|
|
aBeforeThis
|
|
? aBeforeThis->AsContent() : nullptr,
|
|
false);
|
|
}
|
|
|
|
return rv;
|
|
}
|
|
|
|
nsresult
|
|
XULDocument::InsertXMLStylesheetPI(const nsXULPrototypePI* aProtoPI,
|
|
nsINode* aParent,
|
|
nsINode* aBeforeThis,
|
|
nsIContent* aPINode)
|
|
{
|
|
nsCOMPtr<nsIStyleSheetLinkingElement> ssle(do_QueryInterface(aPINode));
|
|
NS_ASSERTION(ssle, "passed XML Stylesheet node does not "
|
|
"implement nsIStyleSheetLinkingElement!");
|
|
|
|
nsresult rv;
|
|
|
|
ssle->InitStyleLinkElement(false);
|
|
// We want to be notified when the style sheet finishes loading, so
|
|
// disable style sheet loading for now.
|
|
ssle->SetEnableUpdates(false);
|
|
ssle->OverrideBaseURI(mCurrentPrototype->GetURI());
|
|
|
|
rv = aParent->InsertChildBefore(aPINode->AsContent(),
|
|
aBeforeThis
|
|
? aBeforeThis->AsContent() : nullptr,
|
|
false);
|
|
if (NS_FAILED(rv)) return rv;
|
|
|
|
ssle->SetEnableUpdates(true);
|
|
|
|
// load the stylesheet if necessary, passing ourselves as
|
|
// nsICSSObserver
|
|
bool willNotify;
|
|
bool isAlternate;
|
|
rv = ssle->UpdateStyleSheet(this, &willNotify, &isAlternate);
|
|
if (NS_SUCCEEDED(rv) && willNotify && !isAlternate) {
|
|
++mPendingSheets;
|
|
}
|
|
|
|
// Ignore errors from UpdateStyleSheet; we don't want failure to
|
|
// do that to break the XUL document load. But do propagate out
|
|
// NS_ERROR_OUT_OF_MEMORY.
|
|
if (rv == NS_ERROR_OUT_OF_MEMORY) {
|
|
return rv;
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult
|
|
XULDocument::InsertXULOverlayPI(const nsXULPrototypePI* aProtoPI,
|
|
nsINode* aParent,
|
|
nsINode* aBeforeThis,
|
|
nsIContent* aPINode)
|
|
{
|
|
nsresult rv;
|
|
|
|
rv = aParent->InsertChildBefore(aPINode->AsContent(),
|
|
aBeforeThis
|
|
? aBeforeThis->AsContent() : nullptr,
|
|
false);
|
|
if (NS_FAILED(rv)) return rv;
|
|
|
|
// xul-overlay PI is special only in prolog
|
|
if (!nsContentUtils::InProlog(aPINode)) {
|
|
return NS_OK;
|
|
}
|
|
|
|
nsAutoString href;
|
|
nsContentUtils::GetPseudoAttributeValue(aProtoPI->mData,
|
|
nsGkAtoms::href,
|
|
href);
|
|
|
|
// If there was no href, we can't do anything with this PI
|
|
if (href.IsEmpty()) {
|
|
return NS_OK;
|
|
}
|
|
|
|
// Add the overlay to our list of overlays that need to be processed.
|
|
nsCOMPtr<nsIURI> uri;
|
|
|
|
rv = NS_NewURI(getter_AddRefs(uri), href, nullptr,
|
|
mCurrentPrototype->GetURI());
|
|
if (NS_SUCCEEDED(rv)) {
|
|
// We insert overlays into mUnloadedOverlays at the same index in
|
|
// document order, so they end up in the reverse of the document
|
|
// order in mUnloadedOverlays.
|
|
// This is needed because the code in ResumeWalk loads the overlays
|
|
// by processing the last item of mUnloadedOverlays and removing it
|
|
// from the array.
|
|
mUnloadedOverlays.InsertElementAt(0, uri);
|
|
rv = NS_OK;
|
|
} else if (rv == NS_ERROR_MALFORMED_URI) {
|
|
// The URL is bad, move along. Don't propagate for now.
|
|
// XXX report this to the Error Console (bug 359846)
|
|
rv = NS_OK;
|
|
}
|
|
|
|
return rv;
|
|
}
|
|
|
|
nsresult
|
|
XULDocument::AddChromeOverlays()
|
|
{
|
|
nsresult rv;
|
|
|
|
nsCOMPtr<nsIURI> docUri = mCurrentPrototype->GetURI();
|
|
|
|
/* overlays only apply to chrome or about URIs */
|
|
if (!IsOverlayAllowed(docUri)) return NS_OK;
|
|
|
|
nsCOMPtr<nsIXULOverlayProvider> chromeReg =
|
|
mozilla::services::GetXULOverlayProviderService();
|
|
// In embedding situations, the chrome registry may not provide overlays,
|
|
// or even exist at all; that's OK.
|
|
NS_ENSURE_TRUE(chromeReg, NS_OK);
|
|
|
|
nsCOMPtr<nsISimpleEnumerator> overlays;
|
|
rv = chromeReg->GetXULOverlays(docUri, getter_AddRefs(overlays));
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
bool moreOverlays;
|
|
nsCOMPtr<nsISupports> next;
|
|
nsCOMPtr<nsIURI> uri;
|
|
|
|
while (NS_SUCCEEDED(rv = overlays->HasMoreElements(&moreOverlays)) &&
|
|
moreOverlays) {
|
|
|
|
rv = overlays->GetNext(getter_AddRefs(next));
|
|
if (NS_FAILED(rv) || !next) break;
|
|
|
|
uri = do_QueryInterface(next);
|
|
if (!uri) {
|
|
NS_ERROR("Chrome registry handed me a non-nsIURI object!");
|
|
continue;
|
|
}
|
|
|
|
// Same comment as in XULDocument::InsertXULOverlayPI
|
|
mUnloadedOverlays.InsertElementAt(0, uri);
|
|
}
|
|
|
|
return rv;
|
|
}
|
|
|
|
void
|
|
XULDocument::LoadOverlay(const nsAString& aURL, nsIObserver* aObserver,
|
|
ErrorResult& aRv)
|
|
{
|
|
nsresult rv;
|
|
|
|
nsCOMPtr<nsIURI> uri;
|
|
rv = NS_NewURI(getter_AddRefs(uri), aURL, nullptr);
|
|
if (NS_FAILED(rv)) {
|
|
aRv.Throw(rv);
|
|
return;
|
|
}
|
|
|
|
if (aObserver) {
|
|
nsIObserver* obs = nullptr;
|
|
if (!mOverlayLoadObservers) {
|
|
mOverlayLoadObservers = new nsInterfaceHashtable<nsURIHashKey,nsIObserver>;
|
|
}
|
|
obs = mOverlayLoadObservers->GetWeak(uri);
|
|
|
|
if (obs) {
|
|
// We don't support loading the same overlay twice into the same
|
|
// document - that doesn't make sense anyway.
|
|
aRv.Throw(NS_ERROR_FAILURE);
|
|
return;
|
|
}
|
|
mOverlayLoadObservers->Put(uri, aObserver);
|
|
}
|
|
bool shouldReturn, failureFromContent;
|
|
rv = LoadOverlayInternal(uri, true, &shouldReturn, &failureFromContent);
|
|
if (NS_FAILED(rv)) {
|
|
// remove the observer if LoadOverlayInternal generated an error
|
|
if (mOverlayLoadObservers) {
|
|
mOverlayLoadObservers->Remove(uri);
|
|
}
|
|
aRv.Throw(rv);
|
|
return;
|
|
}
|
|
}
|
|
|
|
nsresult
|
|
XULDocument::LoadOverlayInternal(nsIURI* aURI, bool aIsDynamic,
|
|
bool* aShouldReturn,
|
|
bool* aFailureFromContent)
|
|
{
|
|
nsresult rv;
|
|
|
|
*aShouldReturn = false;
|
|
*aFailureFromContent = false;
|
|
|
|
if (MOZ_LOG_TEST(gXULLog, LogLevel::Debug)) {
|
|
nsCOMPtr<nsIURI> uri;
|
|
mChannel->GetOriginalURI(getter_AddRefs(uri));
|
|
|
|
MOZ_LOG(gXULLog, LogLevel::Debug,
|
|
("xul: %s loading overlay %s",
|
|
uri ? uri->GetSpecOrDefault().get() : "",
|
|
aURI->GetSpecOrDefault().get()));
|
|
}
|
|
|
|
if (aIsDynamic)
|
|
mResolutionPhase = nsForwardReference::eStart;
|
|
|
|
// Look in the prototype cache for the prototype document with
|
|
// the specified overlay URI. Only use the cache if the containing
|
|
// document is chrome otherwise it may not have a system principal and
|
|
// the cached document will, see bug 565610.
|
|
bool overlayIsChrome = IsChromeURI(aURI);
|
|
bool documentIsChrome = IsChromeURI(mDocumentURI);
|
|
mCurrentPrototype = overlayIsChrome && documentIsChrome ?
|
|
nsXULPrototypeCache::GetInstance()->GetPrototype(aURI) : nullptr;
|
|
|
|
// Same comment as nsChromeProtocolHandler::NewChannel and
|
|
// XULDocument::StartDocumentLoad
|
|
// - Ben Goodger
|
|
//
|
|
// We don't abort on failure here because there are too many valid
|
|
// cases that can return failure, and the null-ness of |proto| is
|
|
// enough to trigger the fail-safe parse-from-disk solution.
|
|
// Example failure cases (for reference) include:
|
|
//
|
|
// NS_ERROR_NOT_AVAILABLE: the URI was not found in the FastLoad file,
|
|
// parse from disk
|
|
// other: the FastLoad file, XUL.mfl, could not be found, probably
|
|
// due to being accessed before a profile has been selected
|
|
// (e.g. loading chrome for the profile manager itself).
|
|
// The .xul file must be parsed from disk.
|
|
|
|
bool useXULCache = nsXULPrototypeCache::GetInstance()->IsEnabled();
|
|
if (useXULCache && mCurrentPrototype) {
|
|
bool loaded;
|
|
rv = mCurrentPrototype->AwaitLoadDone(this, &loaded);
|
|
if (NS_FAILED(rv)) return rv;
|
|
|
|
if (! loaded) {
|
|
// Return to the main event loop and eagerly await the
|
|
// prototype overlay load's completion. When the content
|
|
// sink completes, it will trigger an EndLoad(), which'll
|
|
// wind us back up here, in ResumeWalk().
|
|
*aShouldReturn = true;
|
|
return NS_OK;
|
|
}
|
|
|
|
MOZ_LOG(gXULLog, LogLevel::Debug, ("xul: overlay was cached"));
|
|
|
|
// Found the overlay's prototype in the cache, fully loaded. If
|
|
// this is a dynamic overlay, this will call ResumeWalk.
|
|
// Otherwise, we'll return to ResumeWalk, which called us.
|
|
return OnPrototypeLoadDone(aIsDynamic);
|
|
}
|
|
else {
|
|
// Not there. Initiate a load.
|
|
MOZ_LOG(gXULLog, LogLevel::Debug, ("xul: overlay was not cached"));
|
|
|
|
if (mIsGoingAway) {
|
|
MOZ_LOG(gXULLog, LogLevel::Debug, ("xul: ...and document already destroyed"));
|
|
return NS_ERROR_NOT_AVAILABLE;
|
|
}
|
|
|
|
// We'll set the right principal on the proto doc when we get
|
|
// OnStartRequest from the parser, so just pass in a null principal for
|
|
// now.
|
|
nsCOMPtr<nsIParser> parser;
|
|
rv = PrepareToLoadPrototype(aURI, "view", nullptr, getter_AddRefs(parser));
|
|
if (NS_FAILED(rv)) return rv;
|
|
|
|
// Predicate mIsWritingFastLoad on the XUL cache being enabled,
|
|
// so we don't have to re-check whether the cache is enabled all
|
|
// the time.
|
|
mIsWritingFastLoad = useXULCache;
|
|
|
|
nsCOMPtr<nsIStreamListener> listener = do_QueryInterface(parser);
|
|
if (! listener)
|
|
return NS_ERROR_UNEXPECTED;
|
|
|
|
// Add an observer to the parser; this'll get called when
|
|
// Necko fires its On[Start|Stop]Request() notifications,
|
|
// and will let us recover from a missing overlay.
|
|
RefPtr<ParserObserver> parserObserver =
|
|
new ParserObserver(this, mCurrentPrototype);
|
|
parser->Parse(aURI, parserObserver);
|
|
parserObserver = nullptr;
|
|
|
|
nsCOMPtr<nsILoadGroup> group = do_QueryReferent(mDocumentLoadGroup);
|
|
nsCOMPtr<nsIChannel> channel;
|
|
// Set the owner of the channel to be our principal so
|
|
// that the overlay's JSObjects etc end up being created
|
|
// with the right principal and in the correct
|
|
// compartment.
|
|
rv = NS_NewChannel(getter_AddRefs(channel),
|
|
aURI,
|
|
NodePrincipal(),
|
|
nsILoadInfo::SEC_REQUIRE_SAME_ORIGIN_DATA_INHERITS |
|
|
nsILoadInfo::SEC_FORCE_INHERIT_PRINCIPAL,
|
|
nsIContentPolicy::TYPE_OTHER,
|
|
nullptr, // PerformanceStorage
|
|
group);
|
|
|
|
if (NS_SUCCEEDED(rv)) {
|
|
rv = channel->AsyncOpen2(listener);
|
|
}
|
|
|
|
if (NS_FAILED(rv)) {
|
|
// Abandon this prototype
|
|
mCurrentPrototype = nullptr;
|
|
|
|
// The parser won't get an OnStartRequest and
|
|
// OnStopRequest, so it needs a Terminate.
|
|
parser->Terminate();
|
|
|
|
// Just move on to the next overlay.
|
|
ReportMissingOverlay(aURI);
|
|
|
|
// XXX the error could indicate an internal error as well...
|
|
*aFailureFromContent = true;
|
|
return rv;
|
|
}
|
|
|
|
// If it's a 'chrome:' prototype document, then put it into
|
|
// the prototype cache; other XUL documents will be reloaded
|
|
// each time. We must do this after AsyncOpen,
|
|
// or chrome code will wrongly create a cached chrome channel
|
|
// instead of a real one. Prototypes are only cached when the
|
|
// document to be overlayed is chrome to avoid caching overlay
|
|
// scripts with incorrect principals, see bug 565610.
|
|
if (useXULCache && overlayIsChrome && documentIsChrome) {
|
|
nsXULPrototypeCache::GetInstance()->PutPrototype(mCurrentPrototype);
|
|
}
|
|
|
|
// Return to the main event loop and eagerly await the
|
|
// overlay load's completion. When the content sink
|
|
// completes, it will trigger an EndLoad(), which'll wind
|
|
// us back in ResumeWalk().
|
|
if (!aIsDynamic)
|
|
*aShouldReturn = true;
|
|
}
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult
|
|
XULDocument::ResumeWalk()
|
|
{
|
|
// Walk the prototype and build the delegate content model. The
|
|
// walk is performed in a top-down, left-to-right fashion. That
|
|
// is, a parent is built before any of its children; a node is
|
|
// only built after all of its siblings to the left are fully
|
|
// constructed.
|
|
//
|
|
// It is interruptable so that transcluded documents (e.g.,
|
|
// <html:script src="..." />) can be properly re-loaded if the
|
|
// cached copy of the document becomes stale.
|
|
nsresult rv;
|
|
nsCOMPtr<nsIURI> overlayURI =
|
|
mCurrentPrototype ? mCurrentPrototype->GetURI() : nullptr;
|
|
|
|
while (1) {
|
|
// Begin (or resume) walking the current prototype.
|
|
|
|
while (mContextStack.Depth() > 0) {
|
|
// Look at the top of the stack to determine what we're
|
|
// currently working on.
|
|
// This will always be a node already constructed and
|
|
// inserted to the actual document.
|
|
nsXULPrototypeElement* proto;
|
|
nsCOMPtr<nsIContent> element;
|
|
int32_t indx; // all children of proto before indx (not
|
|
// inclusive) have already been constructed
|
|
rv = mContextStack.Peek(&proto, getter_AddRefs(element), &indx);
|
|
if (NS_FAILED(rv)) return rv;
|
|
|
|
if (indx >= (int32_t)proto->mChildren.Length()) {
|
|
if (element) {
|
|
// We've processed all of the prototype's children. If
|
|
// we're in the master prototype, do post-order
|
|
// document-level hookup. (An overlay will get its
|
|
// document hookup done when it's successfully
|
|
// resolved.)
|
|
if (mState == eState_Master) {
|
|
AddElementToDocumentPost(element->AsElement());
|
|
|
|
if (element->NodeInfo()->Equals(nsGkAtoms::style,
|
|
kNameSpaceID_XHTML) ||
|
|
element->NodeInfo()->Equals(nsGkAtoms::style,
|
|
kNameSpaceID_SVG)) {
|
|
// XXX sucks that we have to do this -
|
|
// see bug 370111
|
|
nsCOMPtr<nsIStyleSheetLinkingElement> ssle =
|
|
do_QueryInterface(element);
|
|
NS_ASSERTION(ssle, "<html:style> doesn't implement "
|
|
"nsIStyleSheetLinkingElement?");
|
|
bool willNotify;
|
|
bool isAlternate;
|
|
ssle->UpdateStyleSheet(nullptr, &willNotify,
|
|
&isAlternate);
|
|
}
|
|
}
|
|
}
|
|
// Now pop the context stack back up to the parent
|
|
// element and continue the prototype walk.
|
|
mContextStack.Pop();
|
|
continue;
|
|
}
|
|
|
|
// Grab the next child, and advance the current context stack
|
|
// to the next sibling to our right.
|
|
nsXULPrototypeNode* childproto = proto->mChildren[indx];
|
|
mContextStack.SetTopIndex(++indx);
|
|
|
|
// Whether we're in the "first ply" of an overlay:
|
|
// the "hookup" nodes. In the case !processingOverlayHookupNodes,
|
|
// we're in the master document -or- we're in an overlay, and far
|
|
// enough down into the overlay's content that we can simply build
|
|
// the delegates and attach them to the parent node.
|
|
bool processingOverlayHookupNodes = (mState == eState_Overlay) &&
|
|
(mContextStack.Depth() == 1);
|
|
|
|
NS_ASSERTION(element || processingOverlayHookupNodes,
|
|
"no element on context stack");
|
|
|
|
switch (childproto->mType) {
|
|
case nsXULPrototypeNode::eType_Element: {
|
|
// An 'element', which may contain more content.
|
|
nsXULPrototypeElement* protoele =
|
|
static_cast<nsXULPrototypeElement*>(childproto);
|
|
|
|
RefPtr<Element> child;
|
|
|
|
if (!processingOverlayHookupNodes) {
|
|
rv = CreateElementFromPrototype(protoele,
|
|
getter_AddRefs(child),
|
|
false);
|
|
if (NS_FAILED(rv)) return rv;
|
|
|
|
// ...and append it to the content model.
|
|
rv = element->AppendChildTo(child, false);
|
|
if (NS_FAILED(rv)) return rv;
|
|
|
|
// If we're only restoring persisted things on
|
|
// some elements, store the ID here to do that.
|
|
if (mRestrictPersistence) {
|
|
nsAtom* id = child->GetID();
|
|
if (id) {
|
|
mPersistenceIds.PutEntry(nsDependentAtomString(id));
|
|
}
|
|
}
|
|
|
|
// do pre-order document-level hookup, but only if
|
|
// we're in the master document. For an overlay,
|
|
// this will happen when the overlay is
|
|
// successfully resolved.
|
|
if (mState == eState_Master)
|
|
AddElementToDocumentPre(child);
|
|
}
|
|
else {
|
|
// We're in the "first ply" of an overlay: the
|
|
// "hookup" nodes. Create an 'overlay' element so
|
|
// that we can continue to build content, and
|
|
// enter a forward reference so we can hook it up
|
|
// later.
|
|
rv = CreateOverlayElement(protoele, getter_AddRefs(child));
|
|
if (NS_FAILED(rv)) return rv;
|
|
}
|
|
|
|
// If it has children, push the element onto the context
|
|
// stack and begin to process them.
|
|
if (protoele->mChildren.Length() > 0) {
|
|
rv = mContextStack.Push(protoele, child);
|
|
if (NS_FAILED(rv)) return rv;
|
|
}
|
|
else {
|
|
if (mState == eState_Master) {
|
|
// If there are no children, and we're in the
|
|
// master document, do post-order document hookup
|
|
// immediately.
|
|
AddElementToDocumentPost(child);
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
|
|
case nsXULPrototypeNode::eType_Script: {
|
|
// A script reference. Execute the script immediately;
|
|
// this may have side effects in the content model.
|
|
nsXULPrototypeScript* scriptproto =
|
|
static_cast<nsXULPrototypeScript*>(childproto);
|
|
|
|
if (scriptproto->mSrcURI) {
|
|
// A transcluded script reference; this may
|
|
// "block" our prototype walk if the script isn't
|
|
// cached, or the cached copy of the script is
|
|
// stale and must be reloaded.
|
|
bool blocked;
|
|
rv = LoadScript(scriptproto, &blocked);
|
|
// If the script cannot be loaded, just keep going!
|
|
|
|
if (NS_SUCCEEDED(rv) && blocked)
|
|
return NS_OK;
|
|
}
|
|
else if (scriptproto->HasScriptObject()) {
|
|
// An inline script
|
|
rv = ExecuteScript(scriptproto);
|
|
if (NS_FAILED(rv)) return rv;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case nsXULPrototypeNode::eType_Text: {
|
|
// A simple text node.
|
|
|
|
if (!processingOverlayHookupNodes) {
|
|
// This does mean that text nodes that are direct children
|
|
// of <overlay> get ignored.
|
|
|
|
RefPtr<nsTextNode> text =
|
|
new nsTextNode(mNodeInfoManager);
|
|
|
|
nsXULPrototypeText* textproto =
|
|
static_cast<nsXULPrototypeText*>(childproto);
|
|
text->SetText(textproto->mValue, false);
|
|
|
|
rv = element->AppendChildTo(text, false);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case nsXULPrototypeNode::eType_PI: {
|
|
nsXULPrototypePI* piProto =
|
|
static_cast<nsXULPrototypePI*>(childproto);
|
|
|
|
// <?xul-overlay?> and <?xml-stylesheet?> don't have effect
|
|
// outside the prolog, like they used to. Issue a warning.
|
|
|
|
if (piProto->mTarget.EqualsLiteral("xml-stylesheet") ||
|
|
piProto->mTarget.EqualsLiteral("xul-overlay")) {
|
|
|
|
const char16_t* params[] = { piProto->mTarget.get() };
|
|
|
|
nsContentUtils::ReportToConsole(
|
|
nsIScriptError::warningFlag,
|
|
NS_LITERAL_CSTRING("XUL Document"), nullptr,
|
|
nsContentUtils::eXUL_PROPERTIES,
|
|
"PINotInProlog",
|
|
params, ArrayLength(params),
|
|
overlayURI);
|
|
}
|
|
|
|
nsIContent* parent = processingOverlayHookupNodes ?
|
|
GetRootElement() : element.get();
|
|
|
|
if (parent) {
|
|
// an inline script could have removed the root element
|
|
rv = CreateAndInsertPI(piProto, parent, nullptr);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
}
|
|
}
|
|
break;
|
|
|
|
default:
|
|
NS_NOTREACHED("Unexpected nsXULPrototypeNode::Type value");
|
|
}
|
|
}
|
|
|
|
// Once we get here, the context stack will have been
|
|
// depleted. That means that the entire prototype has been
|
|
// walked and content has been constructed.
|
|
|
|
// If we're not already, mark us as now processing overlays.
|
|
mState = eState_Overlay;
|
|
|
|
// If there are no overlay URIs, then we're done.
|
|
uint32_t count = mUnloadedOverlays.Length();
|
|
if (! count)
|
|
break;
|
|
|
|
nsCOMPtr<nsIURI> uri = mUnloadedOverlays[count-1];
|
|
mUnloadedOverlays.RemoveElementAt(count - 1);
|
|
|
|
bool shouldReturn, failureFromContent;
|
|
rv = LoadOverlayInternal(uri, false, &shouldReturn,
|
|
&failureFromContent);
|
|
if (failureFromContent)
|
|
// The failure |rv| was the result of a problem in the content
|
|
// rather than an unexpected problem in our implementation, so
|
|
// just continue with the next overlay.
|
|
continue;
|
|
if (NS_FAILED(rv))
|
|
return rv;
|
|
if (mOverlayLoadObservers) {
|
|
nsIObserver *obs = mOverlayLoadObservers->GetWeak(overlayURI);
|
|
if (obs) {
|
|
// This overlay has an unloaded overlay, so it will never
|
|
// notify. The best we can do is to notify for the unloaded
|
|
// overlay instead, assuming nobody is already notifiable
|
|
// for it. Note that this will confuse the observer.
|
|
if (!mOverlayLoadObservers->GetWeak(uri))
|
|
mOverlayLoadObservers->Put(uri, obs);
|
|
mOverlayLoadObservers->Remove(overlayURI);
|
|
}
|
|
}
|
|
if (shouldReturn)
|
|
return NS_OK;
|
|
overlayURI.swap(uri);
|
|
}
|
|
|
|
// If we get here, there is nothing left for us to walk. The content
|
|
// model is built and ready for layout.
|
|
rv = ResolveForwardReferences();
|
|
if (NS_FAILED(rv)) return rv;
|
|
|
|
ApplyPersistentAttributes();
|
|
|
|
mStillWalking = false;
|
|
if (mPendingSheets == 0) {
|
|
rv = DoneWalking();
|
|
}
|
|
return rv;
|
|
}
|
|
|
|
nsresult
|
|
XULDocument::DoneWalking()
|
|
{
|
|
NS_PRECONDITION(mPendingSheets == 0, "there are sheets to be loaded");
|
|
NS_PRECONDITION(!mStillWalking, "walk not done");
|
|
|
|
// XXXldb This is where we should really be setting the chromehidden
|
|
// attribute.
|
|
|
|
{
|
|
mozAutoDocUpdate updateBatch(this, UPDATE_STYLE, true);
|
|
uint32_t count = mOverlaySheets.Length();
|
|
for (uint32_t i = 0; i < count; ++i) {
|
|
AddStyleSheet(mOverlaySheets[i]);
|
|
}
|
|
}
|
|
|
|
mOverlaySheets.Clear();
|
|
|
|
if (!mDocumentLoaded) {
|
|
// Make sure we don't reenter here from StartLayout(). Note that
|
|
// setting mDocumentLoaded to true here means that if StartLayout()
|
|
// causes ResumeWalk() to be reentered, we'll take the other branch of
|
|
// the |if (!mDocumentLoaded)| check above and since
|
|
// mInitialLayoutComplete will be false will follow the else branch
|
|
// there too. See the big comment there for how such reentry can
|
|
// happen.
|
|
mDocumentLoaded = true;
|
|
|
|
NotifyPossibleTitleChange(false);
|
|
|
|
// Before starting layout, check whether we're a toplevel chrome
|
|
// window. If we are, set our chrome flags now, so that we don't have
|
|
// to restyle the whole frame tree after StartLayout.
|
|
nsCOMPtr<nsIDocShellTreeItem> item = GetDocShell();
|
|
if (item) {
|
|
nsCOMPtr<nsIDocShellTreeOwner> owner;
|
|
item->GetTreeOwner(getter_AddRefs(owner));
|
|
nsCOMPtr<nsIXULWindow> xulWin = do_GetInterface(owner);
|
|
if (xulWin) {
|
|
nsCOMPtr<nsIDocShell> xulWinShell;
|
|
xulWin->GetDocShell(getter_AddRefs(xulWinShell));
|
|
if (SameCOMIdentity(xulWinShell, item)) {
|
|
// We're the chrome document! Apply our chrome flags now.
|
|
xulWin->ApplyChromeFlags();
|
|
}
|
|
}
|
|
}
|
|
|
|
nsContentUtils::DispatchTrustedEvent(this,
|
|
static_cast<nsIDocument*>(this),
|
|
NS_LITERAL_STRING("MozBeforeInitialXULLayout"),
|
|
true,
|
|
false);
|
|
|
|
StartLayout();
|
|
|
|
if (mIsWritingFastLoad && IsChromeURI(mDocumentURI))
|
|
nsXULPrototypeCache::GetInstance()->WritePrototype(mMasterPrototype);
|
|
|
|
NS_ASSERTION(mDelayFrameLoaderInitialization,
|
|
"mDelayFrameLoaderInitialization should be true!");
|
|
mDelayFrameLoaderInitialization = false;
|
|
NS_WARNING_ASSERTION(
|
|
mUpdateNestLevel == 0,
|
|
"Constructing XUL document in middle of an update?");
|
|
if (mUpdateNestLevel == 0) {
|
|
MaybeInitializeFinalizeFrameLoaders();
|
|
}
|
|
|
|
NS_DOCUMENT_NOTIFY_OBSERVERS(EndLoad, (this));
|
|
|
|
// DispatchContentLoadedEvents undoes the onload-blocking we
|
|
// did in PrepareToWalk().
|
|
DispatchContentLoadedEvents();
|
|
|
|
mInitialLayoutComplete = true;
|
|
|
|
// Walk the set of pending load notifications and notify any observers.
|
|
// See below for detail.
|
|
if (mPendingOverlayLoadNotifications) {
|
|
nsInterfaceHashtable<nsURIHashKey,nsIObserver>* observers =
|
|
mOverlayLoadObservers.get();
|
|
for (auto iter = mPendingOverlayLoadNotifications->Iter();
|
|
!iter.Done();
|
|
iter.Next()) {
|
|
nsIURI* aKey = iter.Key();
|
|
iter.Data()->Observe(aKey, "xul-overlay-merged",
|
|
EmptyString().get());
|
|
|
|
if (observers) {
|
|
observers->Remove(aKey);
|
|
}
|
|
|
|
iter.Remove();
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
if (mOverlayLoadObservers) {
|
|
nsCOMPtr<nsIURI> overlayURI = mCurrentPrototype->GetURI();
|
|
nsCOMPtr<nsIObserver> obs;
|
|
if (mInitialLayoutComplete) {
|
|
// We have completed initial layout, so just send the notification.
|
|
mOverlayLoadObservers->Get(overlayURI, getter_AddRefs(obs));
|
|
if (obs)
|
|
obs->Observe(overlayURI, "xul-overlay-merged", EmptyString().get());
|
|
mOverlayLoadObservers->Remove(overlayURI);
|
|
}
|
|
else {
|
|
// If we have not yet displayed the document for the first time
|
|
// (i.e. we came in here as the result of a dynamic overlay load
|
|
// which was spawned by a binding-attached event caused by
|
|
// StartLayout() on the master prototype - we must remember that
|
|
// this overlay has been merged and tell the listeners after
|
|
// StartLayout() is completely finished rather than doing so
|
|
// immediately - otherwise we may be executing code that needs to
|
|
// access XBL Binding implementations on nodes for which frames
|
|
// have not yet been constructed because their bindings have not
|
|
// yet been attached. This can be a race condition because dynamic
|
|
// overlay loading can take varying amounts of time depending on
|
|
// whether or not the overlay prototype is in the XUL cache. The
|
|
// most likely effect of this bug is odd UI initialization due to
|
|
// methods and properties that do not work.
|
|
// XXXbz really, we shouldn't be firing binding constructors
|
|
// until after StartLayout returns!
|
|
|
|
if (!mPendingOverlayLoadNotifications) {
|
|
mPendingOverlayLoadNotifications =
|
|
new nsInterfaceHashtable<nsURIHashKey,nsIObserver>;
|
|
}
|
|
|
|
mPendingOverlayLoadNotifications->Get(overlayURI, getter_AddRefs(obs));
|
|
if (!obs) {
|
|
mOverlayLoadObservers->Get(overlayURI, getter_AddRefs(obs));
|
|
NS_ASSERTION(obs, "null overlay load observer?");
|
|
mPendingOverlayLoadNotifications->Put(overlayURI, obs);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
XULDocument::StyleSheetLoaded(StyleSheet* aSheet,
|
|
bool aWasAlternate,
|
|
nsresult aStatus)
|
|
{
|
|
if (!aWasAlternate) {
|
|
// Don't care about when alternate sheets finish loading
|
|
|
|
NS_ASSERTION(mPendingSheets > 0,
|
|
"Unexpected StyleSheetLoaded notification");
|
|
|
|
--mPendingSheets;
|
|
|
|
if (!mStillWalking && mPendingSheets == 0) {
|
|
return DoneWalking();
|
|
}
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
void
|
|
XULDocument::MaybeBroadcast()
|
|
{
|
|
// Only broadcast when not in an update and when safe to run scripts.
|
|
if (mUpdateNestLevel == 0 &&
|
|
(mDelayedAttrChangeBroadcasts.Length() ||
|
|
mDelayedBroadcasters.Length())) {
|
|
if (!nsContentUtils::IsSafeToRunScript()) {
|
|
if (!mInDestructor) {
|
|
nsContentUtils::AddScriptRunner(
|
|
NewRunnableMethod("dom::XULDocument::MaybeBroadcast",
|
|
this,
|
|
&XULDocument::MaybeBroadcast));
|
|
}
|
|
return;
|
|
}
|
|
if (!mHandlingDelayedAttrChange) {
|
|
mHandlingDelayedAttrChange = true;
|
|
for (uint32_t i = 0; i < mDelayedAttrChangeBroadcasts.Length(); ++i) {
|
|
nsAtom* attrName = mDelayedAttrChangeBroadcasts[i].mAttrName;
|
|
if (mDelayedAttrChangeBroadcasts[i].mNeedsAttrChange) {
|
|
nsCOMPtr<Element> listener =
|
|
do_QueryInterface(mDelayedAttrChangeBroadcasts[i].mListener);
|
|
const nsString& value = mDelayedAttrChangeBroadcasts[i].mAttr;
|
|
if (mDelayedAttrChangeBroadcasts[i].mSetAttr) {
|
|
listener->SetAttr(kNameSpaceID_None, attrName, value,
|
|
true);
|
|
} else {
|
|
listener->UnsetAttr(kNameSpaceID_None, attrName,
|
|
true);
|
|
}
|
|
}
|
|
ExecuteOnBroadcastHandlerFor(mDelayedAttrChangeBroadcasts[i].mBroadcaster,
|
|
mDelayedAttrChangeBroadcasts[i].mListener,
|
|
attrName);
|
|
}
|
|
mDelayedAttrChangeBroadcasts.Clear();
|
|
mHandlingDelayedAttrChange = false;
|
|
}
|
|
|
|
uint32_t length = mDelayedBroadcasters.Length();
|
|
if (length) {
|
|
bool oldValue = mHandlingDelayedBroadcasters;
|
|
mHandlingDelayedBroadcasters = true;
|
|
nsTArray<nsDelayedBroadcastUpdate> delayedBroadcasters;
|
|
mDelayedBroadcasters.SwapElements(delayedBroadcasters);
|
|
for (uint32_t i = 0; i < length; ++i) {
|
|
SynchronizeBroadcastListener(delayedBroadcasters[i].mBroadcaster,
|
|
delayedBroadcasters[i].mListener,
|
|
delayedBroadcasters[i].mAttr);
|
|
}
|
|
mHandlingDelayedBroadcasters = oldValue;
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
XULDocument::EndUpdate(nsUpdateType aUpdateType)
|
|
{
|
|
XMLDocument::EndUpdate(aUpdateType);
|
|
|
|
MaybeBroadcast();
|
|
}
|
|
|
|
void
|
|
XULDocument::ReportMissingOverlay(nsIURI* aURI)
|
|
{
|
|
NS_PRECONDITION(aURI, "Must have a URI");
|
|
|
|
NS_ConvertUTF8toUTF16 utfSpec(aURI->GetSpecOrDefault());
|
|
const char16_t* params[] = { utfSpec.get() };
|
|
nsContentUtils::ReportToConsole(nsIScriptError::warningFlag,
|
|
NS_LITERAL_CSTRING("XUL Document"), this,
|
|
nsContentUtils::eXUL_PROPERTIES,
|
|
"MissingOverlay",
|
|
params, ArrayLength(params));
|
|
}
|
|
|
|
nsresult
|
|
XULDocument::LoadScript(nsXULPrototypeScript* aScriptProto, bool* aBlock)
|
|
{
|
|
// Load a transcluded script
|
|
nsresult rv;
|
|
|
|
bool isChromeDoc = IsChromeURI(mDocumentURI);
|
|
|
|
if (isChromeDoc && aScriptProto->HasScriptObject()) {
|
|
rv = ExecuteScript(aScriptProto);
|
|
|
|
// Ignore return value from execution, and don't block
|
|
*aBlock = false;
|
|
return NS_OK;
|
|
}
|
|
|
|
// Try the XUL script cache, in case two XUL documents source the same
|
|
// .js file (e.g., strres.js from navigator.xul and utilityOverlay.xul).
|
|
// XXXbe the cache relies on aScriptProto's GC root!
|
|
bool useXULCache = nsXULPrototypeCache::GetInstance()->IsEnabled();
|
|
|
|
if (isChromeDoc && useXULCache) {
|
|
JSScript* newScriptObject =
|
|
nsXULPrototypeCache::GetInstance()->GetScript(
|
|
aScriptProto->mSrcURI);
|
|
if (newScriptObject) {
|
|
// The script language for a proto must remain constant - we
|
|
// can't just change it for this unexpected language.
|
|
aScriptProto->Set(newScriptObject);
|
|
}
|
|
|
|
if (aScriptProto->HasScriptObject()) {
|
|
rv = ExecuteScript(aScriptProto);
|
|
|
|
// Ignore return value from execution, and don't block
|
|
*aBlock = false;
|
|
return NS_OK;
|
|
}
|
|
}
|
|
|
|
// Release script objects from FastLoad since we decided against using them
|
|
aScriptProto->UnlinkJSObjects();
|
|
|
|
// Set the current script prototype so that OnStreamComplete can report
|
|
// the right file if there are errors in the script.
|
|
NS_ASSERTION(!mCurrentScriptProto,
|
|
"still loading a script when starting another load?");
|
|
mCurrentScriptProto = aScriptProto;
|
|
|
|
if (isChromeDoc && aScriptProto->mSrcLoading) {
|
|
// Another XULDocument load has started, which is still in progress.
|
|
// Remember to ResumeWalk this document when the load completes.
|
|
mNextSrcLoadWaiter = aScriptProto->mSrcLoadWaiters;
|
|
aScriptProto->mSrcLoadWaiters = this;
|
|
NS_ADDREF_THIS();
|
|
}
|
|
else {
|
|
nsCOMPtr<nsILoadGroup> group = do_QueryReferent(mDocumentLoadGroup);
|
|
|
|
// Note: the loader will keep itself alive while it's loading.
|
|
nsCOMPtr<nsIStreamLoader> loader;
|
|
rv = NS_NewStreamLoader(getter_AddRefs(loader),
|
|
aScriptProto->mSrcURI,
|
|
this, // aObserver
|
|
this, // aRequestingContext
|
|
nsILoadInfo::SEC_REQUIRE_SAME_ORIGIN_DATA_INHERITS,
|
|
nsIContentPolicy::TYPE_INTERNAL_SCRIPT,
|
|
group);
|
|
|
|
if (NS_FAILED(rv)) {
|
|
mCurrentScriptProto = nullptr;
|
|
return rv;
|
|
}
|
|
|
|
aScriptProto->mSrcLoading = true;
|
|
}
|
|
|
|
// Block until OnStreamComplete resumes us.
|
|
*aBlock = true;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
XULDocument::OnStreamComplete(nsIStreamLoader* aLoader,
|
|
nsISupports* context,
|
|
nsresult aStatus,
|
|
uint32_t stringLen,
|
|
const uint8_t* string)
|
|
{
|
|
nsCOMPtr<nsIRequest> request;
|
|
aLoader->GetRequest(getter_AddRefs(request));
|
|
nsCOMPtr<nsIChannel> channel = do_QueryInterface(request);
|
|
|
|
#ifdef DEBUG
|
|
// print a load error on bad status
|
|
if (NS_FAILED(aStatus)) {
|
|
if (channel) {
|
|
nsCOMPtr<nsIURI> uri;
|
|
channel->GetURI(getter_AddRefs(uri));
|
|
if (uri) {
|
|
printf("Failed to load %s\n", uri->GetSpecOrDefault().get());
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
// This is the completion routine that will be called when a
|
|
// transcluded script completes. Compile and execute the script
|
|
// if the load was successful, then continue building content
|
|
// from the prototype.
|
|
nsresult rv = aStatus;
|
|
|
|
NS_ASSERTION(mCurrentScriptProto && mCurrentScriptProto->mSrcLoading,
|
|
"script source not loading on unichar stream complete?");
|
|
if (!mCurrentScriptProto) {
|
|
// XXX Wallpaper for bug 270042
|
|
return NS_OK;
|
|
}
|
|
|
|
if (NS_SUCCEEDED(aStatus)) {
|
|
// If the including XUL document is a FastLoad document, and we're
|
|
// compiling an out-of-line script (one with src=...), then we must
|
|
// be writing a new FastLoad file. If we were reading this script
|
|
// from the FastLoad file, XULContentSinkImpl::OpenScript (over in
|
|
// nsXULContentSink.cpp) would have already deserialized a non-null
|
|
// script->mScriptObject, causing control flow at the top of LoadScript
|
|
// not to reach here.
|
|
nsCOMPtr<nsIURI> uri = mCurrentScriptProto->mSrcURI;
|
|
|
|
// XXX should also check nsIHttpChannel::requestSucceeded
|
|
|
|
MOZ_ASSERT(!mOffThreadCompiling && (mOffThreadCompileStringLength == 0 &&
|
|
!mOffThreadCompileStringBuf),
|
|
"XULDocument can't load multiple scripts at once");
|
|
|
|
rv = ScriptLoader::ConvertToUTF16(channel, string, stringLen,
|
|
EmptyString(), this,
|
|
mOffThreadCompileStringBuf,
|
|
mOffThreadCompileStringLength);
|
|
if (NS_SUCCEEDED(rv)) {
|
|
// Attempt to give ownership of the buffer to the JS engine. If
|
|
// we hit offthread compilation, however, we will have to take it
|
|
// back below in order to keep the memory alive until compilation
|
|
// completes.
|
|
JS::SourceBufferHolder srcBuf(mOffThreadCompileStringBuf,
|
|
mOffThreadCompileStringLength,
|
|
JS::SourceBufferHolder::GiveOwnership);
|
|
mOffThreadCompileStringBuf = nullptr;
|
|
mOffThreadCompileStringLength = 0;
|
|
|
|
rv = mCurrentScriptProto->Compile(srcBuf, uri, 1, this, this);
|
|
if (NS_SUCCEEDED(rv) && !mCurrentScriptProto->HasScriptObject()) {
|
|
// We will be notified via OnOffThreadCompileComplete when the
|
|
// compile finishes. Keep the contents of the compiled script
|
|
// alive until the compilation finishes.
|
|
mOffThreadCompiling = true;
|
|
// If the JS engine did not take the source buffer, then take
|
|
// it back here to ensure it remains alive.
|
|
mOffThreadCompileStringBuf = srcBuf.take();
|
|
if (mOffThreadCompileStringBuf) {
|
|
mOffThreadCompileStringLength = srcBuf.length();
|
|
}
|
|
BlockOnload();
|
|
return NS_OK;
|
|
}
|
|
}
|
|
}
|
|
|
|
return OnScriptCompileComplete(mCurrentScriptProto->GetScriptObject(), rv);
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
XULDocument::OnScriptCompileComplete(JSScript* aScript, nsresult aStatus)
|
|
{
|
|
// When compiling off thread the script will not have been attached to the
|
|
// script proto yet.
|
|
if (aScript && !mCurrentScriptProto->HasScriptObject())
|
|
mCurrentScriptProto->Set(aScript);
|
|
|
|
// Allow load events to be fired once off thread compilation finishes.
|
|
if (mOffThreadCompiling) {
|
|
mOffThreadCompiling = false;
|
|
UnblockOnload(false);
|
|
}
|
|
|
|
// After compilation finishes the script's characters are no longer needed.
|
|
if (mOffThreadCompileStringBuf) {
|
|
js_free(mOffThreadCompileStringBuf);
|
|
mOffThreadCompileStringBuf = nullptr;
|
|
mOffThreadCompileStringLength = 0;
|
|
}
|
|
|
|
// Clear mCurrentScriptProto now, but save it first for use below in
|
|
// the execute code, and in the while loop that resumes walks of other
|
|
// documents that raced to load this script.
|
|
nsXULPrototypeScript* scriptProto = mCurrentScriptProto;
|
|
mCurrentScriptProto = nullptr;
|
|
|
|
// Clear the prototype's loading flag before executing the script or
|
|
// resuming document walks, in case any of those control flows starts a
|
|
// new script load.
|
|
scriptProto->mSrcLoading = false;
|
|
|
|
nsresult rv = aStatus;
|
|
if (NS_SUCCEEDED(rv)) {
|
|
rv = ExecuteScript(scriptProto);
|
|
|
|
// If the XUL cache is enabled, save the script object there in
|
|
// case different XUL documents source the same script.
|
|
//
|
|
// But don't save the script in the cache unless the master XUL
|
|
// document URL is a chrome: URL. It is valid for a URL such as
|
|
// about:config to translate into a master document URL, whose
|
|
// prototype document nodes -- including prototype scripts that
|
|
// hold GC roots protecting their mJSObject pointers -- are not
|
|
// cached in the XUL prototype cache. See StartDocumentLoad,
|
|
// the fillXULCache logic.
|
|
//
|
|
// A document such as about:config is free to load a script via
|
|
// a URL such as chrome://global/content/config.js, and we must
|
|
// not cache that script object without a prototype cache entry
|
|
// containing a companion nsXULPrototypeScript node that owns a
|
|
// GC root protecting the script object. Otherwise, the script
|
|
// cache entry will dangle once the uncached prototype document
|
|
// is released when its owning XULDocument is unloaded.
|
|
//
|
|
// (See http://bugzilla.mozilla.org/show_bug.cgi?id=98207 for
|
|
// the true crime story.)
|
|
bool useXULCache = nsXULPrototypeCache::GetInstance()->IsEnabled();
|
|
|
|
if (useXULCache && IsChromeURI(mDocumentURI) && scriptProto->HasScriptObject()) {
|
|
JS::Rooted<JSScript*> script(RootingCx(), scriptProto->GetScriptObject());
|
|
nsXULPrototypeCache::GetInstance()->PutScript(
|
|
scriptProto->mSrcURI, script);
|
|
}
|
|
|
|
if (mIsWritingFastLoad && mCurrentPrototype != mMasterPrototype) {
|
|
// If we are loading an overlay script, try to serialize
|
|
// it to the FastLoad file here. Master scripts will be
|
|
// serialized when the master prototype document gets
|
|
// written, at the bottom of ResumeWalk. That way, master
|
|
// out-of-line scripts are serialized in the same order that
|
|
// they'll be read, in the FastLoad file, which reduces the
|
|
// number of seeks that dump the underlying stream's buffer.
|
|
//
|
|
// Ignore the return value, as we don't need to propagate
|
|
// a failure to write to the FastLoad file, because this
|
|
// method aborts that whole process on error.
|
|
scriptProto->SerializeOutOfLine(nullptr, mCurrentPrototype);
|
|
}
|
|
// ignore any evaluation errors
|
|
}
|
|
|
|
rv = ResumeWalk();
|
|
|
|
// Load a pointer to the prototype-script's list of XULDocuments who
|
|
// raced to load the same script
|
|
XULDocument** docp = &scriptProto->mSrcLoadWaiters;
|
|
|
|
// Resume walking other documents that waited for this one's load, first
|
|
// executing the script we just compiled, in each doc's script context
|
|
XULDocument* doc;
|
|
while ((doc = *docp) != nullptr) {
|
|
NS_ASSERTION(doc->mCurrentScriptProto == scriptProto,
|
|
"waiting for wrong script to load?");
|
|
doc->mCurrentScriptProto = nullptr;
|
|
|
|
// Unlink doc from scriptProto's list before executing and resuming
|
|
*docp = doc->mNextSrcLoadWaiter;
|
|
doc->mNextSrcLoadWaiter = nullptr;
|
|
|
|
// Execute only if we loaded and compiled successfully, then resume
|
|
if (NS_SUCCEEDED(aStatus) && scriptProto->HasScriptObject()) {
|
|
doc->ExecuteScript(scriptProto);
|
|
}
|
|
doc->ResumeWalk();
|
|
NS_RELEASE(doc);
|
|
}
|
|
|
|
return rv;
|
|
}
|
|
|
|
nsresult
|
|
XULDocument::ExecuteScript(nsXULPrototypeScript *aScript)
|
|
{
|
|
NS_PRECONDITION(aScript != nullptr, "null ptr");
|
|
NS_ENSURE_TRUE(aScript, NS_ERROR_NULL_POINTER);
|
|
NS_ENSURE_TRUE(mScriptGlobalObject, NS_ERROR_NOT_INITIALIZED);
|
|
|
|
nsresult rv;
|
|
rv = mScriptGlobalObject->EnsureScriptEnvironment();
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
// Execute the precompiled script with the given version
|
|
nsAutoMicroTask mt;
|
|
|
|
// We're about to run script via JS::CloneAndExecuteScript, so we need an
|
|
// AutoEntryScript. This is Gecko specific and not in any spec.
|
|
AutoEntryScript aes(mScriptGlobalObject, "precompiled XUL <script> element");
|
|
JSContext* cx = aes.cx();
|
|
|
|
JS::Rooted<JSScript*> scriptObject(cx, aScript->GetScriptObject());
|
|
NS_ENSURE_TRUE(scriptObject, NS_ERROR_UNEXPECTED);
|
|
|
|
JS::Rooted<JSObject*> baseGlobal(cx, JS::CurrentGlobalOrNull(cx));
|
|
NS_ENSURE_TRUE(xpc::Scriptability::Get(baseGlobal).Allowed(), NS_OK);
|
|
|
|
JSAddonId* addonId = mCurrentPrototype ? MapURIToAddonID(mCurrentPrototype->GetURI()) : nullptr;
|
|
JS::Rooted<JSObject*> global(cx, xpc::GetAddonScope(cx, baseGlobal, addonId));
|
|
NS_ENSURE_TRUE(global, NS_ERROR_FAILURE);
|
|
|
|
JS::ExposeObjectToActiveJS(global);
|
|
JSAutoCompartment ac(cx, global);
|
|
|
|
// The script is in the compilation scope. Clone it into the target scope
|
|
// and execute it. On failure, ~AutoScriptEntry will handle exceptions, so
|
|
// there is no need to manually check the return value.
|
|
JS::RootedValue rval(cx);
|
|
JS::CloneAndExecuteScript(cx, scriptObject, &rval);
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
|
|
nsresult
|
|
XULDocument::CreateElementFromPrototype(nsXULPrototypeElement* aPrototype,
|
|
Element** aResult,
|
|
bool aIsRoot)
|
|
{
|
|
// Create a content model element from a prototype element.
|
|
NS_PRECONDITION(aPrototype != nullptr, "null ptr");
|
|
if (! aPrototype)
|
|
return NS_ERROR_NULL_POINTER;
|
|
|
|
*aResult = nullptr;
|
|
nsresult rv = NS_OK;
|
|
|
|
if (MOZ_LOG_TEST(gXULLog, LogLevel::Debug)) {
|
|
MOZ_LOG(gXULLog, LogLevel::Debug,
|
|
("xul: creating <%s> from prototype",
|
|
NS_ConvertUTF16toUTF8(aPrototype->mNodeInfo->QualifiedName()).get()));
|
|
}
|
|
|
|
RefPtr<Element> result;
|
|
|
|
if (aPrototype->mNodeInfo->NamespaceEquals(kNameSpaceID_XUL)) {
|
|
// If it's a XUL element, it'll be lightweight until somebody
|
|
// monkeys with it.
|
|
rv = nsXULElement::Create(aPrototype, this, true, aIsRoot, getter_AddRefs(result));
|
|
if (NS_FAILED(rv)) return rv;
|
|
}
|
|
else {
|
|
// If it's not a XUL element, it's gonna be heavyweight no matter
|
|
// what. So we need to copy everything out of the prototype
|
|
// into the element. Get a nodeinfo from our nodeinfo manager
|
|
// for this node.
|
|
RefPtr<mozilla::dom::NodeInfo> newNodeInfo;
|
|
newNodeInfo = mNodeInfoManager->GetNodeInfo(aPrototype->mNodeInfo->NameAtom(),
|
|
aPrototype->mNodeInfo->GetPrefixAtom(),
|
|
aPrototype->mNodeInfo->NamespaceID(),
|
|
ELEMENT_NODE);
|
|
if (!newNodeInfo) return NS_ERROR_OUT_OF_MEMORY;
|
|
RefPtr<mozilla::dom::NodeInfo> xtfNi = newNodeInfo;
|
|
rv = NS_NewElement(getter_AddRefs(result), newNodeInfo.forget(),
|
|
NOT_FROM_PARSER);
|
|
if (NS_FAILED(rv))
|
|
return rv;
|
|
|
|
rv = AddAttributes(aPrototype, result);
|
|
if (NS_FAILED(rv)) return rv;
|
|
}
|
|
|
|
result.forget(aResult);
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult
|
|
XULDocument::CreateOverlayElement(nsXULPrototypeElement* aPrototype,
|
|
Element** aResult)
|
|
{
|
|
nsresult rv;
|
|
|
|
RefPtr<Element> element;
|
|
rv = CreateElementFromPrototype(aPrototype, getter_AddRefs(element), false);
|
|
if (NS_FAILED(rv)) return rv;
|
|
|
|
OverlayForwardReference* fwdref =
|
|
new OverlayForwardReference(this, element);
|
|
|
|
// transferring ownership to ya...
|
|
rv = AddForwardReference(fwdref);
|
|
if (NS_FAILED(rv)) return rv;
|
|
|
|
element.forget(aResult);
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult
|
|
XULDocument::AddAttributes(nsXULPrototypeElement* aPrototype,
|
|
Element* aElement)
|
|
{
|
|
nsresult rv;
|
|
|
|
for (uint32_t i = 0; i < aPrototype->mNumAttributes; ++i) {
|
|
nsXULPrototypeAttribute* protoattr = &(aPrototype->mAttributes[i]);
|
|
nsAutoString valueStr;
|
|
protoattr->mValue.ToString(valueStr);
|
|
|
|
rv = aElement->SetAttr(protoattr->mName.NamespaceID(),
|
|
protoattr->mName.LocalName(),
|
|
protoattr->mName.GetPrefix(),
|
|
valueStr,
|
|
false);
|
|
if (NS_FAILED(rv)) return rv;
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
|
|
nsresult
|
|
XULDocument::AddPrototypeSheets()
|
|
{
|
|
nsresult rv;
|
|
|
|
const nsCOMArray<nsIURI>& sheets = mCurrentPrototype->GetStyleSheetReferences();
|
|
|
|
for (int32_t i = 0; i < sheets.Count(); i++) {
|
|
nsCOMPtr<nsIURI> uri = sheets[i];
|
|
|
|
RefPtr<StyleSheet> incompleteSheet;
|
|
rv = CSSLoader()->LoadSheet(
|
|
uri, mCurrentPrototype->DocumentPrincipal(), this, &incompleteSheet);
|
|
|
|
// XXXldb We need to prevent bogus sheets from being held in the
|
|
// prototype's list, but until then, don't propagate the failure
|
|
// from LoadSheet (and thus exit the loop).
|
|
if (NS_SUCCEEDED(rv)) {
|
|
++mPendingSheets;
|
|
if (!mOverlaySheets.AppendElement(incompleteSheet)) {
|
|
return NS_ERROR_OUT_OF_MEMORY;
|
|
}
|
|
}
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
|
|
//----------------------------------------------------------------------
|
|
//
|
|
// XULDocument::OverlayForwardReference
|
|
//
|
|
|
|
nsForwardReference::Result
|
|
XULDocument::OverlayForwardReference::Resolve()
|
|
{
|
|
// Resolve a forward reference from an overlay element; attempt to
|
|
// hook it up into the main document.
|
|
nsresult rv;
|
|
RefPtr<Element> target;
|
|
|
|
nsIPresShell *shell = mDocument->GetShell();
|
|
bool notify = shell && shell->DidInitialize();
|
|
|
|
nsAutoString id;
|
|
mOverlay->GetAttr(kNameSpaceID_None, nsGkAtoms::id, id);
|
|
if (id.IsEmpty()) {
|
|
// mOverlay is a direct child of <overlay> and has no id.
|
|
// Insert it under the root element in the base document.
|
|
Element* root = mDocument->GetRootElement();
|
|
if (!root) {
|
|
return eResolve_Error;
|
|
}
|
|
|
|
rv = mDocument->InsertElement(root, mOverlay, notify);
|
|
if (NS_FAILED(rv)) return eResolve_Error;
|
|
|
|
target = mOverlay;
|
|
}
|
|
else {
|
|
// The hook-up element has an id, try to match it with an element
|
|
// with the same id in the base document.
|
|
target = mDocument->GetElementById(id);
|
|
|
|
// If we can't find the element in the document, defer the hookup
|
|
// until later.
|
|
if (!target)
|
|
return eResolve_Later;
|
|
|
|
rv = Merge(target, mOverlay, notify);
|
|
if (NS_FAILED(rv)) return eResolve_Error;
|
|
}
|
|
|
|
// Check if 'target' is still in our document --- it might not be!
|
|
if (!notify && target->GetUncomposedDoc() == mDocument) {
|
|
// Add child and any descendants to the element map
|
|
// XXX this is bogus, the content in 'target' might already be
|
|
// in the document
|
|
rv = mDocument->AddSubtreeToDocument(target);
|
|
if (NS_FAILED(rv)) return eResolve_Error;
|
|
}
|
|
|
|
if (MOZ_LOG_TEST(gXULLog, LogLevel::Debug)) {
|
|
nsAutoCString idC;
|
|
LossyCopyUTF16toASCII(id, idC);
|
|
MOZ_LOG(gXULLog, LogLevel::Debug,
|
|
("xul: overlay resolved '%s'",
|
|
idC.get()));
|
|
}
|
|
|
|
mResolved = true;
|
|
return eResolve_Succeeded;
|
|
}
|
|
|
|
|
|
|
|
nsresult
|
|
XULDocument::OverlayForwardReference::Merge(Element* aTargetElement,
|
|
Element* aOverlayElement,
|
|
bool aNotify)
|
|
{
|
|
// This function is given:
|
|
// aTargetElement: the element in the document whose 'id' attribute
|
|
// matches a toplevel node in our overlay.
|
|
// aOverlayElement: the element in the overlay document that matches
|
|
// an element in the actual document.
|
|
// aNotify: whether or not content manipulation methods should
|
|
// use the aNotify parameter. After the initial
|
|
// reflow (i.e. in the dynamic overlay merge case),
|
|
// we want all the content manipulation methods we
|
|
// call to notify so that frames are constructed
|
|
// etc. Otherwise do not, since that's during initial
|
|
// document construction before StartLayout has been
|
|
// called which will do everything for us.
|
|
//
|
|
// This function merges the tree from the overlay into the tree in
|
|
// the document, overwriting attributes and appending child content
|
|
// nodes appropriately. (See XUL overlay reference for details)
|
|
|
|
nsresult rv;
|
|
|
|
// Merge attributes from the overlay content node to that of the
|
|
// actual document.
|
|
uint32_t i;
|
|
const nsAttrName* name;
|
|
for (i = 0; (name = aOverlayElement->GetAttrNameAt(i)); ++i) {
|
|
// We don't want to swap IDs, they should be the same.
|
|
if (name->Equals(nsGkAtoms::id))
|
|
continue;
|
|
|
|
// In certain cases merging command or observes is unsafe, so don't.
|
|
if (!aNotify) {
|
|
if (aTargetElement->NodeInfo()->Equals(nsGkAtoms::observes,
|
|
kNameSpaceID_XUL))
|
|
continue;
|
|
|
|
if (name->Equals(nsGkAtoms::observes) &&
|
|
aTargetElement->HasAttr(kNameSpaceID_None, nsGkAtoms::observes))
|
|
continue;
|
|
|
|
if (name->Equals(nsGkAtoms::command) &&
|
|
aTargetElement->HasAttr(kNameSpaceID_None, nsGkAtoms::command) &&
|
|
!aTargetElement->NodeInfo()->Equals(nsGkAtoms::key,
|
|
kNameSpaceID_XUL) &&
|
|
!aTargetElement->NodeInfo()->Equals(nsGkAtoms::menuitem,
|
|
kNameSpaceID_XUL))
|
|
continue;
|
|
}
|
|
|
|
int32_t nameSpaceID = name->NamespaceID();
|
|
nsAtom* attr = name->LocalName();
|
|
nsAtom* prefix = name->GetPrefix();
|
|
|
|
nsAutoString value;
|
|
aOverlayElement->GetAttr(nameSpaceID, attr, value);
|
|
|
|
// Element in the overlay has the 'removeelement' attribute set
|
|
// so remove it from the actual document.
|
|
if (attr == nsGkAtoms::removeelement && value.EqualsLiteral("true")) {
|
|
nsCOMPtr<nsINode> parent = aTargetElement->GetParentNode();
|
|
if (!parent) return NS_ERROR_FAILURE;
|
|
parent->RemoveChildNode(aTargetElement, true);
|
|
return NS_OK;
|
|
}
|
|
|
|
rv = aTargetElement->SetAttr(nameSpaceID, attr, prefix, value, aNotify);
|
|
if (!NS_FAILED(rv) && !aNotify) {
|
|
rv = mDocument->BroadcastAttributeChangeFromOverlay(
|
|
aTargetElement, nameSpaceID, attr, prefix, value);
|
|
}
|
|
if (NS_FAILED(rv)) return rv;
|
|
}
|
|
|
|
|
|
// Walk our child nodes, looking for elements that have the 'id'
|
|
// attribute set. If we find any, we must do a parent check in the
|
|
// actual document to ensure that the structure matches that of
|
|
// the actual document. If it does, we can call ourselves and attempt
|
|
// to merge inside that subtree. If not, we just append the tree to
|
|
// the parent like any other.
|
|
|
|
uint32_t childCount = aOverlayElement->GetChildCount();
|
|
|
|
// This must be a strong reference since it will be the only
|
|
// reference to a content object during part of this loop.
|
|
nsCOMPtr<nsIContent> currContent;
|
|
|
|
for (i = 0; i < childCount; ++i) {
|
|
currContent = aOverlayElement->GetFirstChild();
|
|
|
|
nsAtom *idAtom = currContent->GetID();
|
|
|
|
Element* elementInDocument = nullptr;
|
|
if (idAtom) {
|
|
nsDependentAtomString id(idAtom);
|
|
|
|
if (!id.IsEmpty()) {
|
|
nsIDocument *doc = aTargetElement->GetUncomposedDoc();
|
|
//XXXsmaug should we use ShadowRoot::GetElementById()
|
|
// if doc is null?
|
|
if (!doc) return NS_ERROR_FAILURE;
|
|
|
|
elementInDocument = doc->GetElementById(id);
|
|
}
|
|
}
|
|
|
|
// The item has an 'id' attribute set, and we need to check with
|
|
// the actual document to see if an item with this id exists at
|
|
// this locale. If so, we want to merge the subtree under that
|
|
// node. Otherwise, we just do an append as if the element had
|
|
// no id attribute.
|
|
if (elementInDocument) {
|
|
// Given two parents, aTargetElement and aOverlayElement, we want
|
|
// to call merge on currContent if we find an associated
|
|
// node in the document with the same id as currContent that
|
|
// also has aTargetNode as its parent.
|
|
|
|
nsIContent* elementParent = elementInDocument->GetParent();
|
|
|
|
nsAtom *parentID = elementParent->GetID();
|
|
if (parentID && aTargetElement->GetID() == parentID) {
|
|
// The element matches. "Go Deep!"
|
|
//
|
|
// Note that currContent is necessarily an element, because
|
|
// elementInDocument can only be non-null when currContent has a
|
|
// non-null ID.
|
|
rv = Merge(elementInDocument, currContent->AsElement(), aNotify);
|
|
if (NS_FAILED(rv)) return rv;
|
|
nsIContent* firstChild = aOverlayElement->GetFirstChild();
|
|
if (firstChild) {
|
|
aOverlayElement->RemoveChildNode(firstChild, false);
|
|
}
|
|
|
|
continue;
|
|
}
|
|
}
|
|
|
|
nsIContent* firstChild = aOverlayElement->GetFirstChild();
|
|
if (firstChild) {
|
|
aOverlayElement->RemoveChildNode(firstChild, false);
|
|
}
|
|
|
|
rv = InsertElement(aTargetElement, currContent, aNotify);
|
|
if (NS_FAILED(rv)) return rv;
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
|
|
|
|
XULDocument::OverlayForwardReference::~OverlayForwardReference()
|
|
{
|
|
if (MOZ_LOG_TEST(gXULLog, LogLevel::Warning) && !mResolved) {
|
|
nsAutoString id;
|
|
mOverlay->GetAttr(kNameSpaceID_None, nsGkAtoms::id, id);
|
|
|
|
nsAutoCString idC;
|
|
LossyCopyUTF16toASCII(id, idC);
|
|
|
|
nsIURI *protoURI = mDocument->mCurrentPrototype->GetURI();
|
|
|
|
nsCOMPtr<nsIURI> docURI;
|
|
mDocument->mChannel->GetOriginalURI(getter_AddRefs(docURI));
|
|
|
|
MOZ_LOG(gXULLog, LogLevel::Warning,
|
|
("xul: %s overlay failed to resolve '%s' in %s",
|
|
protoURI->GetSpecOrDefault().get(), idC.get(),
|
|
docURI ? docURI->GetSpecOrDefault().get() : ""));
|
|
}
|
|
}
|
|
|
|
|
|
//----------------------------------------------------------------------
|
|
//
|
|
// XULDocument::BroadcasterHookup
|
|
//
|
|
|
|
nsForwardReference::Result
|
|
XULDocument::BroadcasterHookup::Resolve()
|
|
{
|
|
nsresult rv;
|
|
|
|
bool listener;
|
|
rv = mDocument->CheckBroadcasterHookup(mObservesElement, &listener, &mResolved);
|
|
if (NS_FAILED(rv)) return eResolve_Error;
|
|
|
|
return mResolved ? eResolve_Succeeded : eResolve_Later;
|
|
}
|
|
|
|
|
|
XULDocument::BroadcasterHookup::~BroadcasterHookup()
|
|
{
|
|
if (MOZ_LOG_TEST(gXULLog, LogLevel::Warning) && !mResolved) {
|
|
// Tell the world we failed
|
|
|
|
nsAutoString broadcasterID;
|
|
nsAutoString attribute;
|
|
|
|
if (mObservesElement->IsXULElement(nsGkAtoms::observes)) {
|
|
mObservesElement->GetAttr(kNameSpaceID_None, nsGkAtoms::element, broadcasterID);
|
|
mObservesElement->GetAttr(kNameSpaceID_None, nsGkAtoms::attribute, attribute);
|
|
}
|
|
else {
|
|
mObservesElement->GetAttr(kNameSpaceID_None, nsGkAtoms::observes, broadcasterID);
|
|
attribute.Assign('*');
|
|
}
|
|
|
|
nsAutoCString attributeC,broadcasteridC;
|
|
LossyCopyUTF16toASCII(attribute, attributeC);
|
|
LossyCopyUTF16toASCII(broadcasterID, broadcasteridC);
|
|
MOZ_LOG(gXULLog, LogLevel::Warning,
|
|
("xul: broadcaster hookup failed <%s attribute='%s'> to %s",
|
|
nsAtomCString(mObservesElement->NodeInfo()->NameAtom()).get(),
|
|
attributeC.get(),
|
|
broadcasteridC.get()));
|
|
}
|
|
}
|
|
|
|
//----------------------------------------------------------------------
|
|
|
|
nsresult
|
|
XULDocument::BroadcastAttributeChangeFromOverlay(nsIContent* aNode,
|
|
int32_t aNameSpaceID,
|
|
nsAtom* aAttribute,
|
|
nsAtom* aPrefix,
|
|
const nsAString& aValue)
|
|
{
|
|
nsresult rv = NS_OK;
|
|
|
|
if (!mBroadcasterMap || !CanBroadcast(aNameSpaceID, aAttribute))
|
|
return rv;
|
|
|
|
if (!aNode->IsElement())
|
|
return rv;
|
|
|
|
auto entry = static_cast<BroadcasterMapEntry*>
|
|
(mBroadcasterMap->Search(aNode->AsElement()));
|
|
if (!entry)
|
|
return rv;
|
|
|
|
// We've got listeners: push the value.
|
|
for (size_t i = entry->mListeners.Length() - 1; i != (size_t)-1; --i) {
|
|
BroadcastListener* bl = entry->mListeners[i];
|
|
|
|
if ((bl->mAttribute != aAttribute) &&
|
|
(bl->mAttribute != nsGkAtoms::_asterisk))
|
|
continue;
|
|
|
|
nsCOMPtr<Element> l = do_QueryReferent(bl->mListener);
|
|
if (l) {
|
|
rv = l->SetAttr(aNameSpaceID, aAttribute,
|
|
aPrefix, aValue, false);
|
|
if (NS_FAILED(rv)) return rv;
|
|
}
|
|
}
|
|
return rv;
|
|
}
|
|
|
|
nsresult
|
|
XULDocument::FindBroadcaster(Element* aElement,
|
|
Element** aListener,
|
|
nsString& aBroadcasterID,
|
|
nsString& aAttribute,
|
|
Element** aBroadcaster)
|
|
{
|
|
mozilla::dom::NodeInfo *ni = aElement->NodeInfo();
|
|
*aListener = nullptr;
|
|
*aBroadcaster = nullptr;
|
|
|
|
if (ni->Equals(nsGkAtoms::observes, kNameSpaceID_XUL)) {
|
|
// It's an <observes> element, which means that the actual
|
|
// listener is the _parent_ node. This element should have an
|
|
// 'element' attribute that specifies the ID of the
|
|
// broadcaster element, and an 'attribute' element, which
|
|
// specifies the name of the attribute to observe.
|
|
nsIContent* parent = aElement->GetParent();
|
|
if (!parent) {
|
|
// <observes> is the root element
|
|
return NS_FINDBROADCASTER_NOT_FOUND;
|
|
}
|
|
|
|
// If we're still parented by an 'overlay' tag, then we haven't
|
|
// made it into the real document yet. Defer hookup.
|
|
if (parent->NodeInfo()->Equals(nsGkAtoms::overlay,
|
|
kNameSpaceID_XUL)) {
|
|
return NS_FINDBROADCASTER_AWAIT_OVERLAYS;
|
|
}
|
|
|
|
*aListener = parent->IsElement() ? parent->AsElement() : nullptr;
|
|
NS_IF_ADDREF(*aListener);
|
|
|
|
aElement->GetAttr(kNameSpaceID_None, nsGkAtoms::element, aBroadcasterID);
|
|
if (aBroadcasterID.IsEmpty()) {
|
|
return NS_FINDBROADCASTER_NOT_FOUND;
|
|
}
|
|
aElement->GetAttr(kNameSpaceID_None, nsGkAtoms::attribute, aAttribute);
|
|
}
|
|
else {
|
|
// It's a generic element, which means that we'll use the
|
|
// value of the 'observes' attribute to determine the ID of
|
|
// the broadcaster element, and we'll watch _all_ of its
|
|
// values.
|
|
aElement->GetAttr(kNameSpaceID_None, nsGkAtoms::observes, aBroadcasterID);
|
|
|
|
// Bail if there's no aBroadcasterID
|
|
if (aBroadcasterID.IsEmpty()) {
|
|
// Try the command attribute next.
|
|
aElement->GetAttr(kNameSpaceID_None, nsGkAtoms::command, aBroadcasterID);
|
|
if (!aBroadcasterID.IsEmpty()) {
|
|
// We've got something in the command attribute. We
|
|
// only treat this as a normal broadcaster if we are
|
|
// not a menuitem or a key.
|
|
|
|
if (ni->Equals(nsGkAtoms::menuitem, kNameSpaceID_XUL) ||
|
|
ni->Equals(nsGkAtoms::key, kNameSpaceID_XUL)) {
|
|
return NS_FINDBROADCASTER_NOT_FOUND;
|
|
}
|
|
}
|
|
else {
|
|
return NS_FINDBROADCASTER_NOT_FOUND;
|
|
}
|
|
}
|
|
|
|
*aListener = aElement;
|
|
NS_ADDREF(*aListener);
|
|
|
|
aAttribute.Assign('*');
|
|
}
|
|
|
|
// Make sure we got a valid listener.
|
|
NS_ENSURE_TRUE(*aListener, NS_ERROR_UNEXPECTED);
|
|
|
|
// Try to find the broadcaster element in the document.
|
|
*aBroadcaster = GetElementById(aBroadcasterID);
|
|
|
|
// If we can't find the broadcaster, then we'll need to defer the
|
|
// hookup. We may need to resolve some of the other overlays
|
|
// first.
|
|
if (! *aBroadcaster) {
|
|
return NS_FINDBROADCASTER_AWAIT_OVERLAYS;
|
|
}
|
|
|
|
NS_ADDREF(*aBroadcaster);
|
|
|
|
return NS_FINDBROADCASTER_FOUND;
|
|
}
|
|
|
|
nsresult
|
|
XULDocument::CheckBroadcasterHookup(Element* aElement,
|
|
bool* aNeedsHookup,
|
|
bool* aDidResolve)
|
|
{
|
|
// Resolve a broadcaster hookup. Look at the element that we're
|
|
// trying to resolve: it could be an '<observes>' element, or just
|
|
// a vanilla element with an 'observes' attribute on it.
|
|
nsresult rv;
|
|
|
|
*aDidResolve = false;
|
|
|
|
nsCOMPtr<Element> listener;
|
|
nsAutoString broadcasterID;
|
|
nsAutoString attribute;
|
|
nsCOMPtr<Element> broadcaster;
|
|
|
|
rv = FindBroadcaster(aElement, getter_AddRefs(listener),
|
|
broadcasterID, attribute, getter_AddRefs(broadcaster));
|
|
switch (rv) {
|
|
case NS_FINDBROADCASTER_NOT_FOUND:
|
|
*aNeedsHookup = false;
|
|
return NS_OK;
|
|
case NS_FINDBROADCASTER_AWAIT_OVERLAYS:
|
|
*aNeedsHookup = true;
|
|
return NS_OK;
|
|
case NS_FINDBROADCASTER_FOUND:
|
|
break;
|
|
default:
|
|
return rv;
|
|
}
|
|
|
|
NS_ENSURE_ARG(broadcaster && listener);
|
|
ErrorResult domRv;
|
|
AddBroadcastListenerFor(*broadcaster, *listener, attribute, domRv);
|
|
if (domRv.Failed()) {
|
|
return domRv.StealNSResult();
|
|
}
|
|
|
|
// Tell the world we succeeded
|
|
if (MOZ_LOG_TEST(gXULLog, LogLevel::Debug)) {
|
|
nsCOMPtr<nsIContent> content =
|
|
do_QueryInterface(listener);
|
|
|
|
NS_ASSERTION(content != nullptr, "not an nsIContent");
|
|
if (! content)
|
|
return rv;
|
|
|
|
nsAutoCString attributeC,broadcasteridC;
|
|
LossyCopyUTF16toASCII(attribute, attributeC);
|
|
LossyCopyUTF16toASCII(broadcasterID, broadcasteridC);
|
|
MOZ_LOG(gXULLog, LogLevel::Debug,
|
|
("xul: broadcaster hookup <%s attribute='%s'> to %s",
|
|
nsAtomCString(content->NodeInfo()->NameAtom()).get(),
|
|
attributeC.get(),
|
|
broadcasteridC.get()));
|
|
}
|
|
|
|
*aNeedsHookup = false;
|
|
*aDidResolve = true;
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult
|
|
XULDocument::InsertElement(nsINode* aParent, nsIContent* aChild, bool aNotify)
|
|
{
|
|
// Insert aChild appropriately into aParent, accounting for a
|
|
// 'pos' attribute set on aChild.
|
|
|
|
nsAutoString posStr;
|
|
bool wasInserted = false;
|
|
|
|
// insert after an element of a given id
|
|
if (aChild->IsElement()) {
|
|
aChild->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::insertafter, posStr);
|
|
}
|
|
|
|
bool isInsertAfter = true;
|
|
if (posStr.IsEmpty()) {
|
|
if (aChild->IsElement()) {
|
|
aChild->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::insertbefore, posStr);
|
|
}
|
|
isInsertAfter = false;
|
|
}
|
|
|
|
if (!posStr.IsEmpty()) {
|
|
nsIDocument *document = aParent->OwnerDoc();
|
|
|
|
nsIContent *content = nullptr;
|
|
|
|
char* str = ToNewCString(posStr);
|
|
char* rest;
|
|
char* token = nsCRT::strtok(str, ", ", &rest);
|
|
|
|
while (token) {
|
|
content = document->GetElementById(NS_ConvertASCIItoUTF16(token));
|
|
if (content)
|
|
break;
|
|
|
|
token = nsCRT::strtok(rest, ", ", &rest);
|
|
}
|
|
free(str);
|
|
|
|
if (content) {
|
|
if (content->GetParent() == aParent) {
|
|
nsIContent* nodeToInsertBefore =
|
|
isInsertAfter ? content->GetNextSibling() : content;
|
|
nsresult rv = aParent->InsertChildBefore(aChild,
|
|
nodeToInsertBefore,
|
|
aNotify);
|
|
if (NS_FAILED(rv)) {
|
|
return rv;
|
|
}
|
|
|
|
wasInserted = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!wasInserted) {
|
|
if (aChild->IsElement() &&
|
|
aChild->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::position, posStr) &&
|
|
!posStr.IsEmpty()) {
|
|
nsresult rv;
|
|
// Positions are one-indexed.
|
|
int32_t pos = posStr.ToInteger(&rv);
|
|
// Note: if the insertion index (which is |pos - 1|) would be less
|
|
// than 0 or greater than the number of children aParent has, then
|
|
// don't insert, since the position is bogus. Just skip on to
|
|
// appending.
|
|
if (NS_SUCCEEDED(rv) && pos > 0 &&
|
|
uint32_t(pos - 1) <= aParent->GetChildCount()) {
|
|
nsIContent* nodeToInsertBefore =
|
|
aParent->GetChildAt_Deprecated(pos - 1);
|
|
rv = aParent->InsertChildBefore(aChild, nodeToInsertBefore,
|
|
aNotify);
|
|
if (NS_SUCCEEDED(rv))
|
|
wasInserted = true;
|
|
// If the insertion fails, then we should still
|
|
// attempt an append. Thus, rather than returning rv
|
|
// immediately, we fall through to the final
|
|
// "catch-all" case that just does an AppendChildTo.
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!wasInserted) {
|
|
return aParent->AppendChildTo(aChild, aNotify);
|
|
}
|
|
return NS_OK;
|
|
}
|
|
|
|
//----------------------------------------------------------------------
|
|
//
|
|
// CachedChromeStreamListener
|
|
//
|
|
|
|
XULDocument::CachedChromeStreamListener::CachedChromeStreamListener(XULDocument* aDocument, bool aProtoLoaded)
|
|
: mDocument(aDocument),
|
|
mProtoLoaded(aProtoLoaded)
|
|
{
|
|
}
|
|
|
|
|
|
XULDocument::CachedChromeStreamListener::~CachedChromeStreamListener()
|
|
{
|
|
}
|
|
|
|
|
|
NS_IMPL_ISUPPORTS(XULDocument::CachedChromeStreamListener,
|
|
nsIRequestObserver, nsIStreamListener)
|
|
|
|
NS_IMETHODIMP
|
|
XULDocument::CachedChromeStreamListener::OnStartRequest(nsIRequest *request,
|
|
nsISupports* acontext)
|
|
{
|
|
return NS_ERROR_PARSED_DATA_CACHED;
|
|
}
|
|
|
|
|
|
NS_IMETHODIMP
|
|
XULDocument::CachedChromeStreamListener::OnStopRequest(nsIRequest *request,
|
|
nsISupports* aContext,
|
|
nsresult aStatus)
|
|
{
|
|
if (! mProtoLoaded)
|
|
return NS_OK;
|
|
|
|
return mDocument->OnPrototypeLoadDone(true);
|
|
}
|
|
|
|
|
|
NS_IMETHODIMP
|
|
XULDocument::CachedChromeStreamListener::OnDataAvailable(nsIRequest *request,
|
|
nsISupports* aContext,
|
|
nsIInputStream* aInStr,
|
|
uint64_t aSourceOffset,
|
|
uint32_t aCount)
|
|
{
|
|
NS_NOTREACHED("CachedChromeStream doesn't receive data");
|
|
return NS_ERROR_UNEXPECTED;
|
|
}
|
|
|
|
//----------------------------------------------------------------------
|
|
//
|
|
// ParserObserver
|
|
//
|
|
|
|
XULDocument::ParserObserver::ParserObserver(XULDocument* aDocument,
|
|
nsXULPrototypeDocument* aPrototype)
|
|
: mDocument(aDocument), mPrototype(aPrototype)
|
|
{
|
|
}
|
|
|
|
XULDocument::ParserObserver::~ParserObserver()
|
|
{
|
|
}
|
|
|
|
NS_IMPL_ISUPPORTS(XULDocument::ParserObserver, nsIRequestObserver)
|
|
|
|
NS_IMETHODIMP
|
|
XULDocument::ParserObserver::OnStartRequest(nsIRequest *request,
|
|
nsISupports* aContext)
|
|
{
|
|
// Guard against buggy channels calling OnStartRequest multiple times.
|
|
if (mPrototype) {
|
|
nsCOMPtr<nsIChannel> channel = do_QueryInterface(request);
|
|
nsIScriptSecurityManager* secMan = nsContentUtils::GetSecurityManager();
|
|
if (channel && secMan) {
|
|
nsCOMPtr<nsIPrincipal> principal;
|
|
secMan->GetChannelResultPrincipal(channel, getter_AddRefs(principal));
|
|
|
|
principal = mDocument->MaybeDowngradePrincipal(principal);
|
|
// Failure there is ok -- it'll just set a (safe) null principal
|
|
mPrototype->SetDocumentPrincipal(principal);
|
|
}
|
|
|
|
// Make sure to avoid cycles
|
|
mPrototype = nullptr;
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
XULDocument::ParserObserver::OnStopRequest(nsIRequest *request,
|
|
nsISupports* aContext,
|
|
nsresult aStatus)
|
|
{
|
|
nsresult rv = NS_OK;
|
|
|
|
if (NS_FAILED(aStatus)) {
|
|
// If an overlay load fails, we need to nudge the prototype
|
|
// walk along.
|
|
nsCOMPtr<nsIChannel> aChannel = do_QueryInterface(request);
|
|
if (aChannel) {
|
|
nsCOMPtr<nsIURI> uri;
|
|
aChannel->GetOriginalURI(getter_AddRefs(uri));
|
|
if (uri) {
|
|
mDocument->ReportMissingOverlay(uri);
|
|
}
|
|
}
|
|
|
|
rv = mDocument->ResumeWalk();
|
|
}
|
|
|
|
// Drop the reference to the document to break cycle between the
|
|
// document, the parser, the content sink, and the parser
|
|
// observer.
|
|
mDocument = nullptr;
|
|
|
|
return rv;
|
|
}
|
|
|
|
already_AddRefed<nsPIWindowRoot>
|
|
XULDocument::GetWindowRoot()
|
|
{
|
|
if (!mDocumentContainer) {
|
|
return nullptr;
|
|
}
|
|
|
|
nsCOMPtr<nsPIDOMWindowOuter> piWin = mDocumentContainer->GetWindow();
|
|
return piWin ? piWin->GetTopWindowRoot() : nullptr;
|
|
}
|
|
|
|
bool
|
|
XULDocument::IsDocumentRightToLeft()
|
|
{
|
|
// setting the localedir attribute on the root element forces a
|
|
// specific direction for the document.
|
|
Element* element = GetRootElement();
|
|
if (element) {
|
|
static Element::AttrValuesArray strings[] =
|
|
{&nsGkAtoms::ltr, &nsGkAtoms::rtl, nullptr};
|
|
switch (element->FindAttrValueIn(kNameSpaceID_None, nsGkAtoms::localedir,
|
|
strings, eCaseMatters)) {
|
|
case 0: return false;
|
|
case 1: return true;
|
|
default: break; // otherwise, not a valid value, so fall through
|
|
}
|
|
}
|
|
|
|
// otherwise, get the locale from the chrome registry and
|
|
// look up the intl.uidirection.<locale> preference
|
|
nsCOMPtr<nsIXULChromeRegistry> reg =
|
|
mozilla::services::GetXULChromeRegistryService();
|
|
if (!reg)
|
|
return false;
|
|
|
|
nsAutoCString package;
|
|
bool isChrome;
|
|
if (NS_SUCCEEDED(mDocumentURI->SchemeIs("chrome", &isChrome)) &&
|
|
isChrome) {
|
|
mDocumentURI->GetHostPort(package);
|
|
}
|
|
else {
|
|
// use the 'global' package for about and resource uris.
|
|
// otherwise, just default to left-to-right.
|
|
bool isAbout, isResource;
|
|
if (NS_SUCCEEDED(mDocumentURI->SchemeIs("about", &isAbout)) &&
|
|
isAbout) {
|
|
package.AssignLiteral("global");
|
|
}
|
|
else if (NS_SUCCEEDED(mDocumentURI->SchemeIs("resource", &isResource)) &&
|
|
isResource) {
|
|
package.AssignLiteral("global");
|
|
}
|
|
else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
bool isRTL = false;
|
|
reg->IsLocaleRTL(package, &isRTL);
|
|
return isRTL;
|
|
}
|
|
|
|
void
|
|
XULDocument::ResetDocumentDirection()
|
|
{
|
|
DocumentStatesChanged(NS_DOCUMENT_STATE_RTL_LOCALE);
|
|
}
|
|
|
|
void
|
|
XULDocument::DirectionChanged(const char* aPrefName, void* aData)
|
|
{
|
|
// Reset the direction and restyle the document if necessary.
|
|
XULDocument* doc = (XULDocument *)aData;
|
|
if (doc) {
|
|
doc->ResetDocumentDirection();
|
|
}
|
|
}
|
|
|
|
nsIDocument::DocumentTheme
|
|
XULDocument::GetDocumentLWTheme()
|
|
{
|
|
if (mDocLWTheme == Doc_Theme_Uninitialized) {
|
|
mDocLWTheme = ThreadSafeGetDocumentLWTheme();
|
|
}
|
|
return mDocLWTheme;
|
|
}
|
|
|
|
nsIDocument::DocumentTheme
|
|
XULDocument::ThreadSafeGetDocumentLWTheme() const
|
|
{
|
|
if (mDocLWTheme != Doc_Theme_Uninitialized) {
|
|
return mDocLWTheme;
|
|
}
|
|
|
|
DocumentTheme theme = Doc_Theme_None; // No lightweight theme by default
|
|
Element* element = GetRootElement();
|
|
nsAutoString hasLWTheme;
|
|
if (element &&
|
|
element->GetAttr(kNameSpaceID_None, nsGkAtoms::lwtheme, hasLWTheme) &&
|
|
!(hasLWTheme.IsEmpty()) &&
|
|
hasLWTheme.EqualsLiteral("true")) {
|
|
theme = Doc_Theme_Neutral;
|
|
nsAutoString lwTheme;
|
|
element->GetAttr(kNameSpaceID_None, nsGkAtoms::lwthemetextcolor, lwTheme);
|
|
if (!(lwTheme.IsEmpty())) {
|
|
if (lwTheme.EqualsLiteral("dark"))
|
|
theme = Doc_Theme_Dark;
|
|
else if (lwTheme.EqualsLiteral("bright"))
|
|
theme = Doc_Theme_Bright;
|
|
}
|
|
}
|
|
return theme;
|
|
}
|
|
|
|
JSObject*
|
|
XULDocument::WrapNode(JSContext *aCx, JS::Handle<JSObject*> aGivenProto)
|
|
{
|
|
return XULDocumentBinding::Wrap(aCx, this, aGivenProto);
|
|
}
|
|
|
|
} // namespace dom
|
|
} // namespace mozilla
|