content/xul/document/src/XULDocument.cpp

Tue, 06 Jan 2015 21:39:09 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Tue, 06 Jan 2015 21:39:09 +0100
branch
TOR_BUG_9701
changeset 8
97036ab72558
permissions
-rw-r--r--

Conditionally force memory storage according to privacy.thirdparty.isolate;
This solves Tor bug #9701, complying with disk avoidance documented in
https://www.torproject.org/projects/torbrowser/design/#disk-avoidance.

michael@0 1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
michael@0 2 /* vim: set ts=4 sw=4 et tw=80: */
michael@0 3 /* This Source Code Form is subject to the terms of the Mozilla Public
michael@0 4 * License, v. 2.0. If a copy of the MPL was not distributed with this
michael@0 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
michael@0 6
michael@0 7 /*
michael@0 8
michael@0 9 An implementation for the XUL document. This implementation serves
michael@0 10 as the basis for generating an NGLayout content model.
michael@0 11
michael@0 12 Notes
michael@0 13 -----
michael@0 14
michael@0 15 1. We do some monkey business in the document observer methods to`
michael@0 16 keep the element map in sync for HTML elements. Why don't we just
michael@0 17 do it for _all_ elements? Well, in the case of XUL elements,
michael@0 18 which may be lazily created during frame construction, the
michael@0 19 document observer methods will never be called because we'll be
michael@0 20 adding the XUL nodes into the content model "quietly".
michael@0 21
michael@0 22 */
michael@0 23
michael@0 24 #include "mozilla/ArrayUtils.h"
michael@0 25
michael@0 26 // Note the ALPHABETICAL ORDERING
michael@0 27 #include "XULDocument.h"
michael@0 28
michael@0 29 #include "nsError.h"
michael@0 30 #include "nsIBoxObject.h"
michael@0 31 #include "nsIChromeRegistry.h"
michael@0 32 #include "nsView.h"
michael@0 33 #include "nsViewManager.h"
michael@0 34 #include "nsIContentViewer.h"
michael@0 35 #include "nsIDOMXULElement.h"
michael@0 36 #include "nsIRDFNode.h"
michael@0 37 #include "nsIRDFRemoteDataSource.h"
michael@0 38 #include "nsIRDFService.h"
michael@0 39 #include "nsIStreamListener.h"
michael@0 40 #include "nsITimer.h"
michael@0 41 #include "nsDocShell.h"
michael@0 42 #include "nsGkAtoms.h"
michael@0 43 #include "nsXMLContentSink.h"
michael@0 44 #include "nsXULContentSink.h"
michael@0 45 #include "nsXULContentUtils.h"
michael@0 46 #include "nsIXULOverlayProvider.h"
michael@0 47 #include "nsNetUtil.h"
michael@0 48 #include "nsParserCIID.h"
michael@0 49 #include "nsPIBoxObject.h"
michael@0 50 #include "nsRDFCID.h"
michael@0 51 #include "nsILocalStore.h"
michael@0 52 #include "nsXPIDLString.h"
michael@0 53 #include "nsPIDOMWindow.h"
michael@0 54 #include "nsPIWindowRoot.h"
michael@0 55 #include "nsXULCommandDispatcher.h"
michael@0 56 #include "nsXULElement.h"
michael@0 57 #include "prlog.h"
michael@0 58 #include "rdf.h"
michael@0 59 #include "nsIFrame.h"
michael@0 60 #include "nsXBLService.h"
michael@0 61 #include "nsCExternalHandlerService.h"
michael@0 62 #include "nsMimeTypes.h"
michael@0 63 #include "nsIObjectInputStream.h"
michael@0 64 #include "nsIObjectOutputStream.h"
michael@0 65 #include "nsContentList.h"
michael@0 66 #include "nsIScriptGlobalObject.h"
michael@0 67 #include "nsIScriptSecurityManager.h"
michael@0 68 #include "nsNodeInfoManager.h"
michael@0 69 #include "nsContentCreatorFunctions.h"
michael@0 70 #include "nsContentUtils.h"
michael@0 71 #include "nsIParser.h"
michael@0 72 #include "nsCharsetSource.h"
michael@0 73 #include "nsIParserService.h"
michael@0 74 #include "nsCSSStyleSheet.h"
michael@0 75 #include "mozilla/css/Loader.h"
michael@0 76 #include "nsIScriptError.h"
michael@0 77 #include "nsIStyleSheetLinkingElement.h"
michael@0 78 #include "nsIObserverService.h"
michael@0 79 #include "nsNodeUtils.h"
michael@0 80 #include "nsIDocShellTreeOwner.h"
michael@0 81 #include "nsIXULWindow.h"
michael@0 82 #include "nsXULPopupManager.h"
michael@0 83 #include "nsCCUncollectableMarker.h"
michael@0 84 #include "nsURILoader.h"
michael@0 85 #include "mozilla/BasicEvents.h"
michael@0 86 #include "mozilla/dom/Element.h"
michael@0 87 #include "mozilla/dom/ProcessingInstruction.h"
michael@0 88 #include "mozilla/dom/XULDocumentBinding.h"
michael@0 89 #include "mozilla/EventDispatcher.h"
michael@0 90 #include "mozilla/Preferences.h"
michael@0 91 #include "nsTextNode.h"
michael@0 92 #include "nsJSUtils.h"
michael@0 93 #include "mozilla/dom/URL.h"
michael@0 94
michael@0 95 using namespace mozilla;
michael@0 96 using namespace mozilla::dom;
michael@0 97
michael@0 98 //----------------------------------------------------------------------
michael@0 99 //
michael@0 100 // CIDs
michael@0 101 //
michael@0 102
michael@0 103 static NS_DEFINE_CID(kParserCID, NS_PARSER_CID);
michael@0 104
michael@0 105 static bool IsOverlayAllowed(nsIURI* aURI)
michael@0 106 {
michael@0 107 bool canOverlay = false;
michael@0 108 if (NS_SUCCEEDED(aURI->SchemeIs("about", &canOverlay)) && canOverlay)
michael@0 109 return true;
michael@0 110 if (NS_SUCCEEDED(aURI->SchemeIs("chrome", &canOverlay)) && canOverlay)
michael@0 111 return true;
michael@0 112 return false;
michael@0 113 }
michael@0 114
michael@0 115 //----------------------------------------------------------------------
michael@0 116 //
michael@0 117 // Miscellaneous Constants
michael@0 118 //
michael@0 119
michael@0 120 const nsForwardReference::Phase nsForwardReference::kPasses[] = {
michael@0 121 nsForwardReference::eConstruction,
michael@0 122 nsForwardReference::eHookup,
michael@0 123 nsForwardReference::eDone
michael@0 124 };
michael@0 125
michael@0 126 const uint32_t kMaxAttrNameLength = 512;
michael@0 127 const uint32_t kMaxAttributeLength = 4096;
michael@0 128
michael@0 129 //----------------------------------------------------------------------
michael@0 130 //
michael@0 131 // Statics
michael@0 132 //
michael@0 133
michael@0 134 int32_t XULDocument::gRefCnt = 0;
michael@0 135
michael@0 136 nsIRDFService* XULDocument::gRDFService;
michael@0 137 nsIRDFResource* XULDocument::kNC_persist;
michael@0 138 nsIRDFResource* XULDocument::kNC_attribute;
michael@0 139 nsIRDFResource* XULDocument::kNC_value;
michael@0 140
michael@0 141 PRLogModuleInfo* XULDocument::gXULLog;
michael@0 142
michael@0 143 //----------------------------------------------------------------------
michael@0 144
michael@0 145 struct BroadcasterMapEntry : public PLDHashEntryHdr {
michael@0 146 Element* mBroadcaster; // [WEAK]
michael@0 147 nsSmallVoidArray mListeners; // [OWNING] of BroadcastListener objects
michael@0 148 };
michael@0 149
michael@0 150 struct BroadcastListener {
michael@0 151 nsWeakPtr mListener;
michael@0 152 nsCOMPtr<nsIAtom> mAttribute;
michael@0 153 };
michael@0 154
michael@0 155 Element*
michael@0 156 nsRefMapEntry::GetFirstElement()
michael@0 157 {
michael@0 158 return static_cast<Element*>(mRefContentList.SafeElementAt(0));
michael@0 159 }
michael@0 160
michael@0 161 void
michael@0 162 nsRefMapEntry::AppendAll(nsCOMArray<nsIContent>* aElements)
michael@0 163 {
michael@0 164 for (int32_t i = 0; i < mRefContentList.Count(); ++i) {
michael@0 165 aElements->AppendObject(static_cast<nsIContent*>(mRefContentList[i]));
michael@0 166 }
michael@0 167 }
michael@0 168
michael@0 169 bool
michael@0 170 nsRefMapEntry::AddElement(Element* aElement)
michael@0 171 {
michael@0 172 if (mRefContentList.IndexOf(aElement) >= 0)
michael@0 173 return true;
michael@0 174 return mRefContentList.AppendElement(aElement);
michael@0 175 }
michael@0 176
michael@0 177 bool
michael@0 178 nsRefMapEntry::RemoveElement(Element* aElement)
michael@0 179 {
michael@0 180 mRefContentList.RemoveElement(aElement);
michael@0 181 return mRefContentList.Count() == 0;
michael@0 182 }
michael@0 183
michael@0 184 //----------------------------------------------------------------------
michael@0 185 //
michael@0 186 // ctors & dtors
michael@0 187 //
michael@0 188
michael@0 189 namespace mozilla {
michael@0 190 namespace dom {
michael@0 191
michael@0 192 XULDocument::XULDocument(void)
michael@0 193 : XMLDocument("application/vnd.mozilla.xul+xml"),
michael@0 194 mDocLWTheme(Doc_Theme_Uninitialized),
michael@0 195 mState(eState_Master),
michael@0 196 mResolutionPhase(nsForwardReference::eStart)
michael@0 197 {
michael@0 198 // NOTE! nsDocument::operator new() zeroes out all members, so don't
michael@0 199 // bother initializing members to 0.
michael@0 200
michael@0 201 // Override the default in nsDocument
michael@0 202 mCharacterSet.AssignLiteral("UTF-8");
michael@0 203
michael@0 204 mDefaultElementType = kNameSpaceID_XUL;
michael@0 205 mIsXUL = true;
michael@0 206
michael@0 207 mDelayFrameLoaderInitialization = true;
michael@0 208
michael@0 209 mAllowXULXBL = eTriTrue;
michael@0 210 }
michael@0 211
michael@0 212 XULDocument::~XULDocument()
michael@0 213 {
michael@0 214 NS_ASSERTION(mNextSrcLoadWaiter == nullptr,
michael@0 215 "unreferenced document still waiting for script source to load?");
michael@0 216
michael@0 217 // In case we failed somewhere early on and the forward observer
michael@0 218 // decls never got resolved.
michael@0 219 mForwardReferences.Clear();
michael@0 220 // Likewise for any references we have to IDs where we might
michael@0 221 // look for persisted data:
michael@0 222 mPersistenceIds.Clear();
michael@0 223
michael@0 224 // Destroy our broadcaster map.
michael@0 225 if (mBroadcasterMap) {
michael@0 226 PL_DHashTableDestroy(mBroadcasterMap);
michael@0 227 }
michael@0 228
michael@0 229 if (mLocalStore) {
michael@0 230 nsCOMPtr<nsIRDFRemoteDataSource> remote =
michael@0 231 do_QueryInterface(mLocalStore);
michael@0 232 if (remote)
michael@0 233 remote->Flush();
michael@0 234 }
michael@0 235
michael@0 236 delete mTemplateBuilderTable;
michael@0 237
michael@0 238 Preferences::UnregisterCallback(XULDocument::DirectionChanged,
michael@0 239 "intl.uidirection.", this);
michael@0 240
michael@0 241 if (--gRefCnt == 0) {
michael@0 242 NS_IF_RELEASE(gRDFService);
michael@0 243
michael@0 244 NS_IF_RELEASE(kNC_persist);
michael@0 245 NS_IF_RELEASE(kNC_attribute);
michael@0 246 NS_IF_RELEASE(kNC_value);
michael@0 247 }
michael@0 248
michael@0 249 if (mOffThreadCompileStringBuf) {
michael@0 250 js_free(mOffThreadCompileStringBuf);
michael@0 251 }
michael@0 252 }
michael@0 253
michael@0 254 } // namespace dom
michael@0 255 } // namespace mozilla
michael@0 256
michael@0 257 nsresult
michael@0 258 NS_NewXULDocument(nsIXULDocument** result)
michael@0 259 {
michael@0 260 NS_PRECONDITION(result != nullptr, "null ptr");
michael@0 261 if (! result)
michael@0 262 return NS_ERROR_NULL_POINTER;
michael@0 263
michael@0 264 XULDocument* doc = new XULDocument();
michael@0 265 if (! doc)
michael@0 266 return NS_ERROR_OUT_OF_MEMORY;
michael@0 267
michael@0 268 NS_ADDREF(doc);
michael@0 269
michael@0 270 nsresult rv;
michael@0 271 if (NS_FAILED(rv = doc->Init())) {
michael@0 272 NS_RELEASE(doc);
michael@0 273 return rv;
michael@0 274 }
michael@0 275
michael@0 276 *result = doc;
michael@0 277 return NS_OK;
michael@0 278 }
michael@0 279
michael@0 280
michael@0 281 namespace mozilla {
michael@0 282 namespace dom {
michael@0 283
michael@0 284 //----------------------------------------------------------------------
michael@0 285 //
michael@0 286 // nsISupports interface
michael@0 287 //
michael@0 288
michael@0 289 static PLDHashOperator
michael@0 290 TraverseTemplateBuilders(nsISupports* aKey, nsIXULTemplateBuilder* aData,
michael@0 291 void* aContext)
michael@0 292 {
michael@0 293 nsCycleCollectionTraversalCallback *cb =
michael@0 294 static_cast<nsCycleCollectionTraversalCallback*>(aContext);
michael@0 295
michael@0 296 NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(*cb, "mTemplateBuilderTable key");
michael@0 297 cb->NoteXPCOMChild(aKey);
michael@0 298 NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(*cb, "mTemplateBuilderTable value");
michael@0 299 cb->NoteXPCOMChild(aData);
michael@0 300
michael@0 301 return PL_DHASH_NEXT;
michael@0 302 }
michael@0 303
michael@0 304 static PLDHashOperator
michael@0 305 TraverseObservers(nsIURI* aKey, nsIObserver* aData, void* aContext)
michael@0 306 {
michael@0 307 nsCycleCollectionTraversalCallback *cb =
michael@0 308 static_cast<nsCycleCollectionTraversalCallback*>(aContext);
michael@0 309
michael@0 310 NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(*cb, "mOverlayLoadObservers/mPendingOverlayLoadNotifications value");
michael@0 311 cb->NoteXPCOMChild(aData);
michael@0 312
michael@0 313 return PL_DHASH_NEXT;
michael@0 314 }
michael@0 315
michael@0 316 NS_IMPL_CYCLE_COLLECTION_CLASS(XULDocument)
michael@0 317
michael@0 318 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(XULDocument, XMLDocument)
michael@0 319 NS_ASSERTION(!nsCCUncollectableMarker::InGeneration(cb, tmp->GetMarkedCCGeneration()),
michael@0 320 "Shouldn't traverse XULDocument!");
michael@0 321 // XXX tmp->mForwardReferences?
michael@0 322 // XXX tmp->mContextStack?
michael@0 323
michael@0 324 // An element will only have a template builder as long as it's in the
michael@0 325 // document, so we'll traverse the table here instead of from the element.
michael@0 326 if (tmp->mTemplateBuilderTable)
michael@0 327 tmp->mTemplateBuilderTable->EnumerateRead(TraverseTemplateBuilders, &cb);
michael@0 328
michael@0 329 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCurrentPrototype)
michael@0 330 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mMasterPrototype)
michael@0 331 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCommandDispatcher)
michael@0 332 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPrototypes);
michael@0 333 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mLocalStore)
michael@0 334
michael@0 335 if (tmp->mOverlayLoadObservers) {
michael@0 336 tmp->mOverlayLoadObservers->EnumerateRead(TraverseObservers, &cb);
michael@0 337 }
michael@0 338 if (tmp->mPendingOverlayLoadNotifications) {
michael@0 339 tmp->mPendingOverlayLoadNotifications->EnumerateRead(TraverseObservers, &cb);
michael@0 340 }
michael@0 341 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
michael@0 342
michael@0 343 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(XULDocument, XMLDocument)
michael@0 344 delete tmp->mTemplateBuilderTable;
michael@0 345 tmp->mTemplateBuilderTable = nullptr;
michael@0 346
michael@0 347 NS_IMPL_CYCLE_COLLECTION_UNLINK(mCommandDispatcher)
michael@0 348 //XXX We should probably unlink all the objects we traverse.
michael@0 349 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
michael@0 350
michael@0 351 NS_IMPL_ADDREF_INHERITED(XULDocument, XMLDocument)
michael@0 352 NS_IMPL_RELEASE_INHERITED(XULDocument, XMLDocument)
michael@0 353
michael@0 354
michael@0 355 // QueryInterface implementation for XULDocument
michael@0 356 NS_INTERFACE_TABLE_HEAD_CYCLE_COLLECTION_INHERITED(XULDocument)
michael@0 357 NS_INTERFACE_TABLE_INHERITED(XULDocument, nsIXULDocument,
michael@0 358 nsIDOMXULDocument, nsIStreamLoaderObserver,
michael@0 359 nsICSSLoaderObserver, nsIOffThreadScriptReceiver)
michael@0 360 NS_INTERFACE_TABLE_TAIL_INHERITING(XMLDocument)
michael@0 361
michael@0 362
michael@0 363 //----------------------------------------------------------------------
michael@0 364 //
michael@0 365 // nsIDocument interface
michael@0 366 //
michael@0 367
michael@0 368 void
michael@0 369 XULDocument::Reset(nsIChannel* aChannel, nsILoadGroup* aLoadGroup)
michael@0 370 {
michael@0 371 NS_NOTREACHED("Reset");
michael@0 372 }
michael@0 373
michael@0 374 void
michael@0 375 XULDocument::ResetToURI(nsIURI* aURI, nsILoadGroup* aLoadGroup,
michael@0 376 nsIPrincipal* aPrincipal)
michael@0 377 {
michael@0 378 NS_NOTREACHED("ResetToURI");
michael@0 379 }
michael@0 380
michael@0 381 void
michael@0 382 XULDocument::SetContentType(const nsAString& aContentType)
michael@0 383 {
michael@0 384 NS_ASSERTION(aContentType.EqualsLiteral("application/vnd.mozilla.xul+xml"),
michael@0 385 "xul-documents always has content-type application/vnd.mozilla.xul+xml");
michael@0 386 // Don't do anything, xul always has the mimetype
michael@0 387 // application/vnd.mozilla.xul+xml
michael@0 388 }
michael@0 389
michael@0 390 // This is called when the master document begins loading, whether it's
michael@0 391 // being cached or not.
michael@0 392 nsresult
michael@0 393 XULDocument::StartDocumentLoad(const char* aCommand, nsIChannel* aChannel,
michael@0 394 nsILoadGroup* aLoadGroup,
michael@0 395 nsISupports* aContainer,
michael@0 396 nsIStreamListener **aDocListener,
michael@0 397 bool aReset, nsIContentSink* aSink)
michael@0 398 {
michael@0 399 #ifdef PR_LOGGING
michael@0 400 if (PR_LOG_TEST(gXULLog, PR_LOG_WARNING)) {
michael@0 401
michael@0 402 nsCOMPtr<nsIURI> uri;
michael@0 403 nsresult rv = aChannel->GetOriginalURI(getter_AddRefs(uri));
michael@0 404 if (NS_SUCCEEDED(rv)) {
michael@0 405 nsAutoCString urlspec;
michael@0 406 rv = uri->GetSpec(urlspec);
michael@0 407 if (NS_SUCCEEDED(rv)) {
michael@0 408 PR_LOG(gXULLog, PR_LOG_WARNING,
michael@0 409 ("xul: load document '%s'", urlspec.get()));
michael@0 410 }
michael@0 411 }
michael@0 412 }
michael@0 413 #endif
michael@0 414 // NOTE: If this ever starts calling nsDocument::StartDocumentLoad
michael@0 415 // we'll possibly need to reset our content type afterwards.
michael@0 416 mStillWalking = true;
michael@0 417 mMayStartLayout = false;
michael@0 418 mDocumentLoadGroup = do_GetWeakReference(aLoadGroup);
michael@0 419
michael@0 420 mChannel = aChannel;
michael@0 421
michael@0 422 mHaveInputEncoding = true;
michael@0 423
michael@0 424 // Get the URI. Note that this should match nsDocShell::OnLoadingSite
michael@0 425 nsresult rv =
michael@0 426 NS_GetFinalChannelURI(aChannel, getter_AddRefs(mDocumentURI));
michael@0 427 NS_ENSURE_SUCCESS(rv, rv);
michael@0 428
michael@0 429 ResetStylesheetsToURI(mDocumentURI);
michael@0 430
michael@0 431 RetrieveRelevantHeaders(aChannel);
michael@0 432
michael@0 433 // Look in the chrome cache: we've got this puppy loaded
michael@0 434 // already.
michael@0 435 nsXULPrototypeDocument* proto = IsChromeURI(mDocumentURI) ?
michael@0 436 nsXULPrototypeCache::GetInstance()->GetPrototype(mDocumentURI) :
michael@0 437 nullptr;
michael@0 438
michael@0 439 // Same comment as nsChromeProtocolHandler::NewChannel and
michael@0 440 // XULDocument::ResumeWalk
michael@0 441 // - Ben Goodger
michael@0 442 //
michael@0 443 // We don't abort on failure here because there are too many valid
michael@0 444 // cases that can return failure, and the null-ness of |proto| is enough
michael@0 445 // to trigger the fail-safe parse-from-disk solution. Example failure cases
michael@0 446 // (for reference) include:
michael@0 447 //
michael@0 448 // NS_ERROR_NOT_AVAILABLE: the URI cannot be found in the startup cache,
michael@0 449 // parse from disk
michael@0 450 // other: the startup cache file could not be found, probably
michael@0 451 // due to being accessed before a profile has been selected (e.g.
michael@0 452 // loading chrome for the profile manager itself). This must be
michael@0 453 // parsed from disk.
michael@0 454
michael@0 455 if (proto) {
michael@0 456 // If we're racing with another document to load proto, wait till the
michael@0 457 // load has finished loading before trying to add cloned style sheets.
michael@0 458 // XULDocument::EndLoad will call proto->NotifyLoadDone, which will
michael@0 459 // find all racing documents and notify them via OnPrototypeLoadDone,
michael@0 460 // which will add style sheet clones to each document.
michael@0 461 bool loaded;
michael@0 462 rv = proto->AwaitLoadDone(this, &loaded);
michael@0 463 if (NS_FAILED(rv)) return rv;
michael@0 464
michael@0 465 mMasterPrototype = mCurrentPrototype = proto;
michael@0 466
michael@0 467 // Set up the right principal on ourselves.
michael@0 468 SetPrincipal(proto->DocumentPrincipal());
michael@0 469
michael@0 470 // We need a listener, even if proto is not yet loaded, in which
michael@0 471 // event the listener's OnStopRequest method does nothing, and all
michael@0 472 // the interesting work happens below XULDocument::EndLoad, from
michael@0 473 // the call there to mCurrentPrototype->NotifyLoadDone().
michael@0 474 *aDocListener = new CachedChromeStreamListener(this, loaded);
michael@0 475 if (! *aDocListener)
michael@0 476 return NS_ERROR_OUT_OF_MEMORY;
michael@0 477 }
michael@0 478 else {
michael@0 479 bool useXULCache = nsXULPrototypeCache::GetInstance()->IsEnabled();
michael@0 480 bool fillXULCache = (useXULCache && IsChromeURI(mDocumentURI));
michael@0 481
michael@0 482
michael@0 483 // It's just a vanilla document load. Create a parser to deal
michael@0 484 // with the stream n' stuff.
michael@0 485
michael@0 486 nsCOMPtr<nsIParser> parser;
michael@0 487 rv = PrepareToLoad(aContainer, aCommand, aChannel, aLoadGroup,
michael@0 488 getter_AddRefs(parser));
michael@0 489 if (NS_FAILED(rv)) return rv;
michael@0 490
michael@0 491 // Predicate mIsWritingFastLoad on the XUL cache being enabled,
michael@0 492 // so we don't have to re-check whether the cache is enabled all
michael@0 493 // the time.
michael@0 494 mIsWritingFastLoad = useXULCache;
michael@0 495
michael@0 496 nsCOMPtr<nsIStreamListener> listener = do_QueryInterface(parser, &rv);
michael@0 497 NS_ASSERTION(NS_SUCCEEDED(rv), "parser doesn't support nsIStreamListener");
michael@0 498 if (NS_FAILED(rv)) return rv;
michael@0 499
michael@0 500 *aDocListener = listener;
michael@0 501
michael@0 502 parser->Parse(mDocumentURI);
michael@0 503
michael@0 504 // Put the current prototype, created under PrepareToLoad, into the
michael@0 505 // XUL prototype cache now. We can't do this under PrepareToLoad or
michael@0 506 // overlay loading will break; search for PutPrototype in ResumeWalk
michael@0 507 // and see the comment there.
michael@0 508 if (fillXULCache) {
michael@0 509 nsXULPrototypeCache::GetInstance()->PutPrototype(mCurrentPrototype);
michael@0 510 }
michael@0 511 }
michael@0 512
michael@0 513 NS_IF_ADDREF(*aDocListener);
michael@0 514 return NS_OK;
michael@0 515 }
michael@0 516
michael@0 517 // This gets invoked after a prototype for this document or one of
michael@0 518 // its overlays is fully built in the content sink.
michael@0 519 void
michael@0 520 XULDocument::EndLoad()
michael@0 521 {
michael@0 522 // This can happen if an overlay fails to load
michael@0 523 if (!mCurrentPrototype)
michael@0 524 return;
michael@0 525
michael@0 526 nsresult rv;
michael@0 527
michael@0 528 // Whack the prototype document into the cache so that the next
michael@0 529 // time somebody asks for it, they don't need to load it by hand.
michael@0 530
michael@0 531 nsCOMPtr<nsIURI> uri = mCurrentPrototype->GetURI();
michael@0 532 bool isChrome = IsChromeURI(uri);
michael@0 533
michael@0 534 // Remember if the XUL cache is on
michael@0 535 bool useXULCache = nsXULPrototypeCache::GetInstance()->IsEnabled();
michael@0 536
michael@0 537 // If the current prototype is an overlay document (non-master prototype)
michael@0 538 // and we're filling the FastLoad disk cache, tell the cache we're done
michael@0 539 // loading it, and write the prototype. The master prototype is put into
michael@0 540 // the cache earlier in XULDocument::StartDocumentLoad.
michael@0 541 if (useXULCache && mIsWritingFastLoad && isChrome &&
michael@0 542 mMasterPrototype != mCurrentPrototype) {
michael@0 543 nsXULPrototypeCache::GetInstance()->WritePrototype(mCurrentPrototype);
michael@0 544 }
michael@0 545
michael@0 546 if (IsOverlayAllowed(uri)) {
michael@0 547 nsCOMPtr<nsIXULOverlayProvider> reg =
michael@0 548 mozilla::services::GetXULOverlayProviderService();
michael@0 549
michael@0 550 if (reg) {
michael@0 551 nsCOMPtr<nsISimpleEnumerator> overlays;
michael@0 552 rv = reg->GetStyleOverlays(uri, getter_AddRefs(overlays));
michael@0 553 if (NS_FAILED(rv)) return;
michael@0 554
michael@0 555 bool moreSheets;
michael@0 556 nsCOMPtr<nsISupports> next;
michael@0 557 nsCOMPtr<nsIURI> sheetURI;
michael@0 558
michael@0 559 while (NS_SUCCEEDED(rv = overlays->HasMoreElements(&moreSheets)) &&
michael@0 560 moreSheets) {
michael@0 561 overlays->GetNext(getter_AddRefs(next));
michael@0 562
michael@0 563 sheetURI = do_QueryInterface(next);
michael@0 564 if (!sheetURI) {
michael@0 565 NS_ERROR("Chrome registry handed me a non-nsIURI object!");
michael@0 566 continue;
michael@0 567 }
michael@0 568
michael@0 569 if (IsChromeURI(sheetURI)) {
michael@0 570 mCurrentPrototype->AddStyleSheetReference(sheetURI);
michael@0 571 }
michael@0 572 }
michael@0 573 }
michael@0 574
michael@0 575 if (isChrome && useXULCache) {
michael@0 576 // If it's a chrome prototype document, then notify any
michael@0 577 // documents that raced to load the prototype, and awaited
michael@0 578 // its load completion via proto->AwaitLoadDone().
michael@0 579 rv = mCurrentPrototype->NotifyLoadDone();
michael@0 580 if (NS_FAILED(rv)) return;
michael@0 581 }
michael@0 582 }
michael@0 583
michael@0 584 OnPrototypeLoadDone(true);
michael@0 585 #ifdef PR_LOGGING
michael@0 586 if (PR_LOG_TEST(gXULLog, PR_LOG_WARNING)) {
michael@0 587 nsAutoCString urlspec;
michael@0 588 rv = uri->GetSpec(urlspec);
michael@0 589 if (NS_SUCCEEDED(rv)) {
michael@0 590 PR_LOG(gXULLog, PR_LOG_WARNING,
michael@0 591 ("xul: Finished loading document '%s'", urlspec.get()));
michael@0 592 }
michael@0 593 }
michael@0 594 #endif
michael@0 595 }
michael@0 596
michael@0 597 NS_IMETHODIMP
michael@0 598 XULDocument::OnPrototypeLoadDone(bool aResumeWalk)
michael@0 599 {
michael@0 600 nsresult rv;
michael@0 601
michael@0 602 // Add the style overlays from chrome registry, if any.
michael@0 603 rv = AddPrototypeSheets();
michael@0 604 if (NS_FAILED(rv)) return rv;
michael@0 605
michael@0 606 rv = PrepareToWalk();
michael@0 607 NS_ASSERTION(NS_SUCCEEDED(rv), "unable to prepare for walk");
michael@0 608 if (NS_FAILED(rv)) return rv;
michael@0 609
michael@0 610 if (aResumeWalk) {
michael@0 611 rv = ResumeWalk();
michael@0 612 }
michael@0 613 return rv;
michael@0 614 }
michael@0 615
michael@0 616 // called when an error occurs parsing a document
michael@0 617 bool
michael@0 618 XULDocument::OnDocumentParserError()
michael@0 619 {
michael@0 620 // don't report errors that are from overlays
michael@0 621 if (mCurrentPrototype && mMasterPrototype != mCurrentPrototype) {
michael@0 622 nsCOMPtr<nsIURI> uri = mCurrentPrototype->GetURI();
michael@0 623 if (IsChromeURI(uri)) {
michael@0 624 nsCOMPtr<nsIObserverService> os =
michael@0 625 mozilla::services::GetObserverService();
michael@0 626 if (os)
michael@0 627 os->NotifyObservers(uri, "xul-overlay-parsererror",
michael@0 628 EmptyString().get());
michael@0 629 }
michael@0 630
michael@0 631 return false;
michael@0 632 }
michael@0 633
michael@0 634 return true;
michael@0 635 }
michael@0 636
michael@0 637 static void
michael@0 638 ClearBroadcasterMapEntry(PLDHashTable* aTable, PLDHashEntryHdr* aEntry)
michael@0 639 {
michael@0 640 BroadcasterMapEntry* entry =
michael@0 641 static_cast<BroadcasterMapEntry*>(aEntry);
michael@0 642 for (int32_t i = entry->mListeners.Count() - 1; i >= 0; --i) {
michael@0 643 delete (BroadcastListener*)entry->mListeners[i];
michael@0 644 }
michael@0 645
michael@0 646 // N.B. that we need to manually run the dtor because we
michael@0 647 // constructed the nsSmallVoidArray object in-place.
michael@0 648 entry->mListeners.~nsSmallVoidArray();
michael@0 649 }
michael@0 650
michael@0 651 static bool
michael@0 652 CanBroadcast(int32_t aNameSpaceID, nsIAtom* aAttribute)
michael@0 653 {
michael@0 654 // Don't push changes to the |id|, |ref|, |persist|, |command| or
michael@0 655 // |observes| attribute.
michael@0 656 if (aNameSpaceID == kNameSpaceID_None) {
michael@0 657 if ((aAttribute == nsGkAtoms::id) ||
michael@0 658 (aAttribute == nsGkAtoms::ref) ||
michael@0 659 (aAttribute == nsGkAtoms::persist) ||
michael@0 660 (aAttribute == nsGkAtoms::command) ||
michael@0 661 (aAttribute == nsGkAtoms::observes)) {
michael@0 662 return false;
michael@0 663 }
michael@0 664 }
michael@0 665 return true;
michael@0 666 }
michael@0 667
michael@0 668 struct nsAttrNameInfo
michael@0 669 {
michael@0 670 nsAttrNameInfo(int32_t aNamespaceID, nsIAtom* aName, nsIAtom* aPrefix) :
michael@0 671 mNamespaceID(aNamespaceID), mName(aName), mPrefix(aPrefix) {}
michael@0 672 nsAttrNameInfo(const nsAttrNameInfo& aOther) :
michael@0 673 mNamespaceID(aOther.mNamespaceID), mName(aOther.mName),
michael@0 674 mPrefix(aOther.mPrefix) {}
michael@0 675 int32_t mNamespaceID;
michael@0 676 nsCOMPtr<nsIAtom> mName;
michael@0 677 nsCOMPtr<nsIAtom> mPrefix;
michael@0 678 };
michael@0 679
michael@0 680 void
michael@0 681 XULDocument::SynchronizeBroadcastListener(Element *aBroadcaster,
michael@0 682 Element *aListener,
michael@0 683 const nsAString &aAttr)
michael@0 684 {
michael@0 685 if (!nsContentUtils::IsSafeToRunScript()) {
michael@0 686 nsDelayedBroadcastUpdate delayedUpdate(aBroadcaster, aListener,
michael@0 687 aAttr);
michael@0 688 mDelayedBroadcasters.AppendElement(delayedUpdate);
michael@0 689 MaybeBroadcast();
michael@0 690 return;
michael@0 691 }
michael@0 692 bool notify = mDocumentLoaded || mHandlingDelayedBroadcasters;
michael@0 693
michael@0 694 if (aAttr.EqualsLiteral("*")) {
michael@0 695 uint32_t count = aBroadcaster->GetAttrCount();
michael@0 696 nsTArray<nsAttrNameInfo> attributes(count);
michael@0 697 for (uint32_t i = 0; i < count; ++i) {
michael@0 698 const nsAttrName* attrName = aBroadcaster->GetAttrNameAt(i);
michael@0 699 int32_t nameSpaceID = attrName->NamespaceID();
michael@0 700 nsIAtom* name = attrName->LocalName();
michael@0 701
michael@0 702 // _Don't_ push the |id|, |ref|, or |persist| attribute's value!
michael@0 703 if (! CanBroadcast(nameSpaceID, name))
michael@0 704 continue;
michael@0 705
michael@0 706 attributes.AppendElement(nsAttrNameInfo(nameSpaceID, name,
michael@0 707 attrName->GetPrefix()));
michael@0 708 }
michael@0 709
michael@0 710 count = attributes.Length();
michael@0 711 while (count-- > 0) {
michael@0 712 int32_t nameSpaceID = attributes[count].mNamespaceID;
michael@0 713 nsIAtom* name = attributes[count].mName;
michael@0 714 nsAutoString value;
michael@0 715 if (aBroadcaster->GetAttr(nameSpaceID, name, value)) {
michael@0 716 aListener->SetAttr(nameSpaceID, name, attributes[count].mPrefix,
michael@0 717 value, notify);
michael@0 718 }
michael@0 719
michael@0 720 #if 0
michael@0 721 // XXX we don't fire the |onbroadcast| handler during
michael@0 722 // initial hookup: doing so would potentially run the
michael@0 723 // |onbroadcast| handler before the |onload| handler,
michael@0 724 // which could define JS properties that mask XBL
michael@0 725 // properties, etc.
michael@0 726 ExecuteOnBroadcastHandlerFor(aBroadcaster, aListener, name);
michael@0 727 #endif
michael@0 728 }
michael@0 729 }
michael@0 730 else {
michael@0 731 // Find out if the attribute is even present at all.
michael@0 732 nsCOMPtr<nsIAtom> name = do_GetAtom(aAttr);
michael@0 733
michael@0 734 nsAutoString value;
michael@0 735 if (aBroadcaster->GetAttr(kNameSpaceID_None, name, value)) {
michael@0 736 aListener->SetAttr(kNameSpaceID_None, name, value, notify);
michael@0 737 } else {
michael@0 738 aListener->UnsetAttr(kNameSpaceID_None, name, notify);
michael@0 739 }
michael@0 740
michael@0 741 #if 0
michael@0 742 // XXX we don't fire the |onbroadcast| handler during initial
michael@0 743 // hookup: doing so would potentially run the |onbroadcast|
michael@0 744 // handler before the |onload| handler, which could define JS
michael@0 745 // properties that mask XBL properties, etc.
michael@0 746 ExecuteOnBroadcastHandlerFor(aBroadcaster, aListener, name);
michael@0 747 #endif
michael@0 748 }
michael@0 749 }
michael@0 750
michael@0 751 NS_IMETHODIMP
michael@0 752 XULDocument::AddBroadcastListenerFor(nsIDOMElement* aBroadcaster,
michael@0 753 nsIDOMElement* aListener,
michael@0 754 const nsAString& aAttr)
michael@0 755 {
michael@0 756 ErrorResult rv;
michael@0 757 nsCOMPtr<Element> broadcaster = do_QueryInterface(aBroadcaster);
michael@0 758 nsCOMPtr<Element> listener = do_QueryInterface(aListener);
michael@0 759 NS_ENSURE_ARG(broadcaster && listener);
michael@0 760 AddBroadcastListenerFor(*broadcaster, *listener, aAttr, rv);
michael@0 761 return rv.ErrorCode();
michael@0 762 }
michael@0 763
michael@0 764 void
michael@0 765 XULDocument::AddBroadcastListenerFor(Element& aBroadcaster, Element& aListener,
michael@0 766 const nsAString& aAttr, ErrorResult& aRv)
michael@0 767 {
michael@0 768 nsresult rv =
michael@0 769 nsContentUtils::CheckSameOrigin(this, &aBroadcaster);
michael@0 770
michael@0 771 if (NS_FAILED(rv)) {
michael@0 772 aRv.Throw(rv);
michael@0 773 return;
michael@0 774 }
michael@0 775
michael@0 776 rv = nsContentUtils::CheckSameOrigin(this, &aListener);
michael@0 777
michael@0 778 if (NS_FAILED(rv)) {
michael@0 779 aRv.Throw(rv);
michael@0 780 return;
michael@0 781 }
michael@0 782
michael@0 783 static const PLDHashTableOps gOps = {
michael@0 784 PL_DHashAllocTable,
michael@0 785 PL_DHashFreeTable,
michael@0 786 PL_DHashVoidPtrKeyStub,
michael@0 787 PL_DHashMatchEntryStub,
michael@0 788 PL_DHashMoveEntryStub,
michael@0 789 ClearBroadcasterMapEntry,
michael@0 790 PL_DHashFinalizeStub,
michael@0 791 nullptr
michael@0 792 };
michael@0 793
michael@0 794 if (! mBroadcasterMap) {
michael@0 795 mBroadcasterMap =
michael@0 796 PL_NewDHashTable(&gOps, nullptr, sizeof(BroadcasterMapEntry),
michael@0 797 PL_DHASH_MIN_SIZE);
michael@0 798
michael@0 799 if (! mBroadcasterMap) {
michael@0 800 aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
michael@0 801 return;
michael@0 802 }
michael@0 803 }
michael@0 804
michael@0 805 BroadcasterMapEntry* entry =
michael@0 806 static_cast<BroadcasterMapEntry*>
michael@0 807 (PL_DHashTableOperate(mBroadcasterMap, &aBroadcaster,
michael@0 808 PL_DHASH_LOOKUP));
michael@0 809
michael@0 810 if (PL_DHASH_ENTRY_IS_FREE(entry)) {
michael@0 811 entry =
michael@0 812 static_cast<BroadcasterMapEntry*>
michael@0 813 (PL_DHashTableOperate(mBroadcasterMap, &aBroadcaster,
michael@0 814 PL_DHASH_ADD));
michael@0 815
michael@0 816 if (! entry) {
michael@0 817 aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
michael@0 818 return;
michael@0 819 }
michael@0 820
michael@0 821 entry->mBroadcaster = &aBroadcaster;
michael@0 822
michael@0 823 // N.B. placement new to construct the nsSmallVoidArray object
michael@0 824 // in-place
michael@0 825 new (&entry->mListeners) nsSmallVoidArray();
michael@0 826 }
michael@0 827
michael@0 828 // Only add the listener if it's not there already!
michael@0 829 nsCOMPtr<nsIAtom> attr = do_GetAtom(aAttr);
michael@0 830
michael@0 831 BroadcastListener* bl;
michael@0 832 for (int32_t i = entry->mListeners.Count() - 1; i >= 0; --i) {
michael@0 833 bl = static_cast<BroadcastListener*>(entry->mListeners[i]);
michael@0 834
michael@0 835 nsCOMPtr<Element> blListener = do_QueryReferent(bl->mListener);
michael@0 836
michael@0 837 if (blListener == &aListener && bl->mAttribute == attr)
michael@0 838 return;
michael@0 839 }
michael@0 840
michael@0 841 bl = new BroadcastListener;
michael@0 842
michael@0 843 bl->mListener = do_GetWeakReference(&aListener);
michael@0 844 bl->mAttribute = attr;
michael@0 845
michael@0 846 entry->mListeners.AppendElement(bl);
michael@0 847
michael@0 848 SynchronizeBroadcastListener(&aBroadcaster, &aListener, aAttr);
michael@0 849 }
michael@0 850
michael@0 851 NS_IMETHODIMP
michael@0 852 XULDocument::RemoveBroadcastListenerFor(nsIDOMElement* aBroadcaster,
michael@0 853 nsIDOMElement* aListener,
michael@0 854 const nsAString& aAttr)
michael@0 855 {
michael@0 856 nsCOMPtr<Element> broadcaster = do_QueryInterface(aBroadcaster);
michael@0 857 nsCOMPtr<Element> listener = do_QueryInterface(aListener);
michael@0 858 NS_ENSURE_ARG(broadcaster && listener);
michael@0 859 RemoveBroadcastListenerFor(*broadcaster, *listener, aAttr);
michael@0 860 return NS_OK;
michael@0 861 }
michael@0 862
michael@0 863 void
michael@0 864 XULDocument::RemoveBroadcastListenerFor(Element& aBroadcaster,
michael@0 865 Element& aListener,
michael@0 866 const nsAString& aAttr)
michael@0 867 {
michael@0 868 // If we haven't added any broadcast listeners, then there sure
michael@0 869 // aren't any to remove.
michael@0 870 if (! mBroadcasterMap)
michael@0 871 return;
michael@0 872
michael@0 873 BroadcasterMapEntry* entry =
michael@0 874 static_cast<BroadcasterMapEntry*>
michael@0 875 (PL_DHashTableOperate(mBroadcasterMap, &aBroadcaster,
michael@0 876 PL_DHASH_LOOKUP));
michael@0 877
michael@0 878 if (PL_DHASH_ENTRY_IS_BUSY(entry)) {
michael@0 879 nsCOMPtr<nsIAtom> attr = do_GetAtom(aAttr);
michael@0 880 for (int32_t i = entry->mListeners.Count() - 1; i >= 0; --i) {
michael@0 881 BroadcastListener* bl =
michael@0 882 static_cast<BroadcastListener*>(entry->mListeners[i]);
michael@0 883
michael@0 884 nsCOMPtr<Element> blListener = do_QueryReferent(bl->mListener);
michael@0 885
michael@0 886 if (blListener == &aListener && bl->mAttribute == attr) {
michael@0 887 entry->mListeners.RemoveElementAt(i);
michael@0 888 delete bl;
michael@0 889
michael@0 890 if (entry->mListeners.Count() == 0)
michael@0 891 PL_DHashTableOperate(mBroadcasterMap, &aBroadcaster,
michael@0 892 PL_DHASH_REMOVE);
michael@0 893
michael@0 894 break;
michael@0 895 }
michael@0 896 }
michael@0 897 }
michael@0 898 }
michael@0 899
michael@0 900 nsresult
michael@0 901 XULDocument::ExecuteOnBroadcastHandlerFor(Element* aBroadcaster,
michael@0 902 Element* aListener,
michael@0 903 nsIAtom* aAttr)
michael@0 904 {
michael@0 905 // Now we execute the onchange handler in the context of the
michael@0 906 // observer. We need to find the observer in order to
michael@0 907 // execute the handler.
michael@0 908
michael@0 909 for (nsIContent* child = aListener->GetFirstChild();
michael@0 910 child;
michael@0 911 child = child->GetNextSibling()) {
michael@0 912
michael@0 913 // Look for an <observes> element beneath the listener. This
michael@0 914 // ought to have an |element| attribute that refers to
michael@0 915 // aBroadcaster, and an |attribute| element that tells us what
michael@0 916 // attriubtes we're listening for.
michael@0 917 if (!child->NodeInfo()->Equals(nsGkAtoms::observes, kNameSpaceID_XUL))
michael@0 918 continue;
michael@0 919
michael@0 920 // Is this the element that was listening to us?
michael@0 921 nsAutoString listeningToID;
michael@0 922 child->GetAttr(kNameSpaceID_None, nsGkAtoms::element, listeningToID);
michael@0 923
michael@0 924 nsAutoString broadcasterID;
michael@0 925 aBroadcaster->GetAttr(kNameSpaceID_None, nsGkAtoms::id, broadcasterID);
michael@0 926
michael@0 927 if (listeningToID != broadcasterID)
michael@0 928 continue;
michael@0 929
michael@0 930 // We are observing the broadcaster, but is this the right
michael@0 931 // attribute?
michael@0 932 nsAutoString listeningToAttribute;
michael@0 933 child->GetAttr(kNameSpaceID_None, nsGkAtoms::attribute,
michael@0 934 listeningToAttribute);
michael@0 935
michael@0 936 if (!aAttr->Equals(listeningToAttribute) &&
michael@0 937 !listeningToAttribute.EqualsLiteral("*")) {
michael@0 938 continue;
michael@0 939 }
michael@0 940
michael@0 941 // This is the right <observes> element. Execute the
michael@0 942 // |onbroadcast| event handler
michael@0 943 WidgetEvent event(true, NS_XUL_BROADCAST);
michael@0 944
michael@0 945 nsCOMPtr<nsIPresShell> shell = GetShell();
michael@0 946 if (shell) {
michael@0 947 nsRefPtr<nsPresContext> aPresContext = shell->GetPresContext();
michael@0 948
michael@0 949 // Handle the DOM event
michael@0 950 nsEventStatus status = nsEventStatus_eIgnore;
michael@0 951 EventDispatcher::Dispatch(child, aPresContext, &event, nullptr,
michael@0 952 &status);
michael@0 953 }
michael@0 954 }
michael@0 955
michael@0 956 return NS_OK;
michael@0 957 }
michael@0 958
michael@0 959 void
michael@0 960 XULDocument::AttributeWillChange(nsIDocument* aDocument,
michael@0 961 Element* aElement, int32_t aNameSpaceID,
michael@0 962 nsIAtom* aAttribute, int32_t aModType)
michael@0 963 {
michael@0 964 NS_ABORT_IF_FALSE(aElement, "Null content!");
michael@0 965 NS_PRECONDITION(aAttribute, "Must have an attribute that's changing!");
michael@0 966
michael@0 967 // XXXbz check aNameSpaceID, dammit!
michael@0 968 // See if we need to update our ref map.
michael@0 969 if (aAttribute == nsGkAtoms::ref ||
michael@0 970 (aAttribute == nsGkAtoms::id && !aElement->GetIDAttributeName())) {
michael@0 971 // Might not need this, but be safe for now.
michael@0 972 nsCOMPtr<nsIMutationObserver> kungFuDeathGrip(this);
michael@0 973 RemoveElementFromRefMap(aElement);
michael@0 974 }
michael@0 975 }
michael@0 976
michael@0 977 void
michael@0 978 XULDocument::AttributeChanged(nsIDocument* aDocument,
michael@0 979 Element* aElement, int32_t aNameSpaceID,
michael@0 980 nsIAtom* aAttribute, int32_t aModType)
michael@0 981 {
michael@0 982 NS_ASSERTION(aDocument == this, "unexpected doc");
michael@0 983
michael@0 984 // Might not need this, but be safe for now.
michael@0 985 nsCOMPtr<nsIMutationObserver> kungFuDeathGrip(this);
michael@0 986
michael@0 987 // XXXbz check aNameSpaceID, dammit!
michael@0 988 // See if we need to update our ref map.
michael@0 989 if (aAttribute == nsGkAtoms::ref ||
michael@0 990 (aAttribute == nsGkAtoms::id && !aElement->GetIDAttributeName())) {
michael@0 991 AddElementToRefMap(aElement);
michael@0 992 }
michael@0 993
michael@0 994 nsresult rv;
michael@0 995
michael@0 996 // Synchronize broadcast listeners
michael@0 997 if (mBroadcasterMap &&
michael@0 998 CanBroadcast(aNameSpaceID, aAttribute)) {
michael@0 999 BroadcasterMapEntry* entry =
michael@0 1000 static_cast<BroadcasterMapEntry*>
michael@0 1001 (PL_DHashTableOperate(mBroadcasterMap, aElement,
michael@0 1002 PL_DHASH_LOOKUP));
michael@0 1003
michael@0 1004 if (PL_DHASH_ENTRY_IS_BUSY(entry)) {
michael@0 1005 // We've got listeners: push the value.
michael@0 1006 nsAutoString value;
michael@0 1007 bool attrSet = aElement->GetAttr(kNameSpaceID_None, aAttribute, value);
michael@0 1008
michael@0 1009 int32_t i;
michael@0 1010 for (i = entry->mListeners.Count() - 1; i >= 0; --i) {
michael@0 1011 BroadcastListener* bl =
michael@0 1012 static_cast<BroadcastListener*>(entry->mListeners[i]);
michael@0 1013
michael@0 1014 if ((bl->mAttribute == aAttribute) ||
michael@0 1015 (bl->mAttribute == nsGkAtoms::_asterix)) {
michael@0 1016 nsCOMPtr<Element> listenerEl
michael@0 1017 = do_QueryReferent(bl->mListener);
michael@0 1018 if (listenerEl) {
michael@0 1019 nsAutoString currentValue;
michael@0 1020 bool hasAttr = listenerEl->GetAttr(kNameSpaceID_None,
michael@0 1021 aAttribute,
michael@0 1022 currentValue);
michael@0 1023 // We need to update listener only if we're
michael@0 1024 // (1) removing an existing attribute,
michael@0 1025 // (2) adding a new attribute or
michael@0 1026 // (3) changing the value of an attribute.
michael@0 1027 bool needsAttrChange =
michael@0 1028 attrSet != hasAttr || !value.Equals(currentValue);
michael@0 1029 nsDelayedBroadcastUpdate delayedUpdate(aElement,
michael@0 1030 listenerEl,
michael@0 1031 aAttribute,
michael@0 1032 value,
michael@0 1033 attrSet,
michael@0 1034 needsAttrChange);
michael@0 1035
michael@0 1036 uint32_t index =
michael@0 1037 mDelayedAttrChangeBroadcasts.IndexOf(delayedUpdate,
michael@0 1038 0, nsDelayedBroadcastUpdate::Comparator());
michael@0 1039 if (index != mDelayedAttrChangeBroadcasts.NoIndex) {
michael@0 1040 if (mHandlingDelayedAttrChange) {
michael@0 1041 NS_WARNING("Broadcasting loop!");
michael@0 1042 continue;
michael@0 1043 }
michael@0 1044 mDelayedAttrChangeBroadcasts.RemoveElementAt(index);
michael@0 1045 }
michael@0 1046
michael@0 1047 mDelayedAttrChangeBroadcasts.AppendElement(delayedUpdate);
michael@0 1048 }
michael@0 1049 }
michael@0 1050 }
michael@0 1051 }
michael@0 1052 }
michael@0 1053
michael@0 1054 // checks for modifications in broadcasters
michael@0 1055 bool listener, resolved;
michael@0 1056 CheckBroadcasterHookup(aElement, &listener, &resolved);
michael@0 1057
michael@0 1058 // See if there is anything we need to persist in the localstore.
michael@0 1059 //
michael@0 1060 // XXX Namespace handling broken :-(
michael@0 1061 nsAutoString persist;
michael@0 1062 aElement->GetAttr(kNameSpaceID_None, nsGkAtoms::persist, persist);
michael@0 1063 if (!persist.IsEmpty()) {
michael@0 1064 // XXXldb This should check that it's a token, not just a substring.
michael@0 1065 if (persist.Find(nsDependentAtomString(aAttribute)) >= 0) {
michael@0 1066 rv = Persist(aElement, kNameSpaceID_None, aAttribute);
michael@0 1067 if (NS_FAILED(rv)) return;
michael@0 1068 }
michael@0 1069 }
michael@0 1070 }
michael@0 1071
michael@0 1072 void
michael@0 1073 XULDocument::ContentAppended(nsIDocument* aDocument,
michael@0 1074 nsIContent* aContainer,
michael@0 1075 nsIContent* aFirstNewContent,
michael@0 1076 int32_t aNewIndexInContainer)
michael@0 1077 {
michael@0 1078 NS_ASSERTION(aDocument == this, "unexpected doc");
michael@0 1079
michael@0 1080 // Might not need this, but be safe for now.
michael@0 1081 nsCOMPtr<nsIMutationObserver> kungFuDeathGrip(this);
michael@0 1082
michael@0 1083 // Update our element map
michael@0 1084 nsresult rv = NS_OK;
michael@0 1085 for (nsIContent* cur = aFirstNewContent; cur && NS_SUCCEEDED(rv);
michael@0 1086 cur = cur->GetNextSibling()) {
michael@0 1087 rv = AddSubtreeToDocument(cur);
michael@0 1088 }
michael@0 1089 }
michael@0 1090
michael@0 1091 void
michael@0 1092 XULDocument::ContentInserted(nsIDocument* aDocument,
michael@0 1093 nsIContent* aContainer,
michael@0 1094 nsIContent* aChild,
michael@0 1095 int32_t aIndexInContainer)
michael@0 1096 {
michael@0 1097 NS_ASSERTION(aDocument == this, "unexpected doc");
michael@0 1098
michael@0 1099 // Might not need this, but be safe for now.
michael@0 1100 nsCOMPtr<nsIMutationObserver> kungFuDeathGrip(this);
michael@0 1101
michael@0 1102 AddSubtreeToDocument(aChild);
michael@0 1103 }
michael@0 1104
michael@0 1105 void
michael@0 1106 XULDocument::ContentRemoved(nsIDocument* aDocument,
michael@0 1107 nsIContent* aContainer,
michael@0 1108 nsIContent* aChild,
michael@0 1109 int32_t aIndexInContainer,
michael@0 1110 nsIContent* aPreviousSibling)
michael@0 1111 {
michael@0 1112 NS_ASSERTION(aDocument == this, "unexpected doc");
michael@0 1113
michael@0 1114 // Might not need this, but be safe for now.
michael@0 1115 nsCOMPtr<nsIMutationObserver> kungFuDeathGrip(this);
michael@0 1116
michael@0 1117 RemoveSubtreeFromDocument(aChild);
michael@0 1118 }
michael@0 1119
michael@0 1120 //----------------------------------------------------------------------
michael@0 1121 //
michael@0 1122 // nsIXULDocument interface
michael@0 1123 //
michael@0 1124
michael@0 1125 void
michael@0 1126 XULDocument::GetElementsForID(const nsAString& aID,
michael@0 1127 nsCOMArray<nsIContent>& aElements)
michael@0 1128 {
michael@0 1129 aElements.Clear();
michael@0 1130
michael@0 1131 nsIdentifierMapEntry *entry = mIdentifierMap.GetEntry(aID);
michael@0 1132 if (entry) {
michael@0 1133 entry->AppendAllIdContent(&aElements);
michael@0 1134 }
michael@0 1135 nsRefMapEntry *refEntry = mRefMap.GetEntry(aID);
michael@0 1136 if (refEntry) {
michael@0 1137 refEntry->AppendAll(&aElements);
michael@0 1138 }
michael@0 1139 }
michael@0 1140
michael@0 1141 nsresult
michael@0 1142 XULDocument::AddForwardReference(nsForwardReference* aRef)
michael@0 1143 {
michael@0 1144 if (mResolutionPhase < aRef->GetPhase()) {
michael@0 1145 if (!mForwardReferences.AppendElement(aRef)) {
michael@0 1146 delete aRef;
michael@0 1147 return NS_ERROR_OUT_OF_MEMORY;
michael@0 1148 }
michael@0 1149 }
michael@0 1150 else {
michael@0 1151 NS_ERROR("forward references have already been resolved");
michael@0 1152 delete aRef;
michael@0 1153 }
michael@0 1154
michael@0 1155 return NS_OK;
michael@0 1156 }
michael@0 1157
michael@0 1158 nsresult
michael@0 1159 XULDocument::ResolveForwardReferences()
michael@0 1160 {
michael@0 1161 if (mResolutionPhase == nsForwardReference::eDone)
michael@0 1162 return NS_OK;
michael@0 1163
michael@0 1164 NS_ASSERTION(mResolutionPhase == nsForwardReference::eStart,
michael@0 1165 "nested ResolveForwardReferences()");
michael@0 1166
michael@0 1167 // Resolve each outstanding 'forward' reference. We iterate
michael@0 1168 // through the list of forward references until no more forward
michael@0 1169 // references can be resolved. This annealing process is
michael@0 1170 // guaranteed to converge because we've "closed the gate" to new
michael@0 1171 // forward references.
michael@0 1172
michael@0 1173 const nsForwardReference::Phase* pass = nsForwardReference::kPasses;
michael@0 1174 while ((mResolutionPhase = *pass) != nsForwardReference::eDone) {
michael@0 1175 uint32_t previous = 0;
michael@0 1176 while (mForwardReferences.Length() &&
michael@0 1177 mForwardReferences.Length() != previous) {
michael@0 1178 previous = mForwardReferences.Length();
michael@0 1179
michael@0 1180 for (uint32_t i = 0; i < mForwardReferences.Length(); ++i) {
michael@0 1181 nsForwardReference* fwdref = mForwardReferences[i];
michael@0 1182
michael@0 1183 if (fwdref->GetPhase() == *pass) {
michael@0 1184 nsForwardReference::Result result = fwdref->Resolve();
michael@0 1185
michael@0 1186 switch (result) {
michael@0 1187 case nsForwardReference::eResolve_Succeeded:
michael@0 1188 case nsForwardReference::eResolve_Error:
michael@0 1189 mForwardReferences.RemoveElementAt(i);
michael@0 1190
michael@0 1191 // fixup because we removed from list
michael@0 1192 --i;
michael@0 1193 break;
michael@0 1194
michael@0 1195 case nsForwardReference::eResolve_Later:
michael@0 1196 // do nothing. we'll try again later
michael@0 1197 ;
michael@0 1198 }
michael@0 1199
michael@0 1200 if (mResolutionPhase == nsForwardReference::eStart) {
michael@0 1201 // Resolve() loaded a dynamic overlay,
michael@0 1202 // (see XULDocument::LoadOverlayInternal()).
michael@0 1203 // Return for now, we will be called again.
michael@0 1204 return NS_OK;
michael@0 1205 }
michael@0 1206 }
michael@0 1207 }
michael@0 1208 }
michael@0 1209
michael@0 1210 ++pass;
michael@0 1211 }
michael@0 1212
michael@0 1213 mForwardReferences.Clear();
michael@0 1214 return NS_OK;
michael@0 1215 }
michael@0 1216
michael@0 1217 //----------------------------------------------------------------------
michael@0 1218 //
michael@0 1219 // nsIDOMDocument interface
michael@0 1220 //
michael@0 1221
michael@0 1222 NS_IMETHODIMP
michael@0 1223 XULDocument::GetElementsByAttribute(const nsAString& aAttribute,
michael@0 1224 const nsAString& aValue,
michael@0 1225 nsIDOMNodeList** aReturn)
michael@0 1226 {
michael@0 1227 *aReturn = GetElementsByAttribute(aAttribute, aValue).take();
michael@0 1228 return NS_OK;
michael@0 1229 }
michael@0 1230
michael@0 1231 already_AddRefed<nsINodeList>
michael@0 1232 XULDocument::GetElementsByAttribute(const nsAString& aAttribute,
michael@0 1233 const nsAString& aValue)
michael@0 1234 {
michael@0 1235 nsCOMPtr<nsIAtom> attrAtom(do_GetAtom(aAttribute));
michael@0 1236 void* attrValue = new nsString(aValue);
michael@0 1237 nsRefPtr<nsContentList> list = new nsContentList(this,
michael@0 1238 MatchAttribute,
michael@0 1239 nsContentUtils::DestroyMatchString,
michael@0 1240 attrValue,
michael@0 1241 true,
michael@0 1242 attrAtom,
michael@0 1243 kNameSpaceID_Unknown);
michael@0 1244
michael@0 1245 return list.forget();
michael@0 1246 }
michael@0 1247
michael@0 1248 NS_IMETHODIMP
michael@0 1249 XULDocument::GetElementsByAttributeNS(const nsAString& aNamespaceURI,
michael@0 1250 const nsAString& aAttribute,
michael@0 1251 const nsAString& aValue,
michael@0 1252 nsIDOMNodeList** aReturn)
michael@0 1253 {
michael@0 1254 ErrorResult rv;
michael@0 1255 *aReturn = GetElementsByAttributeNS(aNamespaceURI, aAttribute,
michael@0 1256 aValue, rv).take();
michael@0 1257 return rv.ErrorCode();
michael@0 1258 }
michael@0 1259
michael@0 1260 already_AddRefed<nsINodeList>
michael@0 1261 XULDocument::GetElementsByAttributeNS(const nsAString& aNamespaceURI,
michael@0 1262 const nsAString& aAttribute,
michael@0 1263 const nsAString& aValue,
michael@0 1264 ErrorResult& aRv)
michael@0 1265 {
michael@0 1266 nsCOMPtr<nsIAtom> attrAtom(do_GetAtom(aAttribute));
michael@0 1267 void* attrValue = new nsString(aValue);
michael@0 1268
michael@0 1269 int32_t nameSpaceId = kNameSpaceID_Wildcard;
michael@0 1270 if (!aNamespaceURI.EqualsLiteral("*")) {
michael@0 1271 nsresult rv =
michael@0 1272 nsContentUtils::NameSpaceManager()->RegisterNameSpace(aNamespaceURI,
michael@0 1273 nameSpaceId);
michael@0 1274 if (NS_FAILED(rv)) {
michael@0 1275 aRv.Throw(rv);
michael@0 1276 return nullptr;
michael@0 1277 }
michael@0 1278 }
michael@0 1279
michael@0 1280 nsRefPtr<nsContentList> list = new nsContentList(this,
michael@0 1281 MatchAttribute,
michael@0 1282 nsContentUtils::DestroyMatchString,
michael@0 1283 attrValue,
michael@0 1284 true,
michael@0 1285 attrAtom,
michael@0 1286 nameSpaceId);
michael@0 1287 return list.forget();
michael@0 1288 }
michael@0 1289
michael@0 1290 NS_IMETHODIMP
michael@0 1291 XULDocument::Persist(const nsAString& aID,
michael@0 1292 const nsAString& aAttr)
michael@0 1293 {
michael@0 1294 // If we're currently reading persisted attributes out of the
michael@0 1295 // localstore, _don't_ re-enter and try to set them again!
michael@0 1296 if (mApplyingPersistedAttrs)
michael@0 1297 return NS_OK;
michael@0 1298
michael@0 1299 Element* element = nsDocument::GetElementById(aID);
michael@0 1300 if (!element)
michael@0 1301 return NS_OK;
michael@0 1302
michael@0 1303 nsCOMPtr<nsIAtom> tag;
michael@0 1304 int32_t nameSpaceID;
michael@0 1305
michael@0 1306 nsCOMPtr<nsINodeInfo> ni = element->GetExistingAttrNameFromQName(aAttr);
michael@0 1307 nsresult rv;
michael@0 1308 if (ni) {
michael@0 1309 tag = ni->NameAtom();
michael@0 1310 nameSpaceID = ni->NamespaceID();
michael@0 1311 }
michael@0 1312 else {
michael@0 1313 // Make sure that this QName is going to be valid.
michael@0 1314 const char16_t *colon;
michael@0 1315 rv = nsContentUtils::CheckQName(PromiseFlatString(aAttr), true, &colon);
michael@0 1316
michael@0 1317 if (NS_FAILED(rv)) {
michael@0 1318 // There was an invalid character or it was malformed.
michael@0 1319 return NS_ERROR_INVALID_ARG;
michael@0 1320 }
michael@0 1321
michael@0 1322 if (colon) {
michael@0 1323 // We don't really handle namespace qualifiers in attribute names.
michael@0 1324 return NS_ERROR_NOT_IMPLEMENTED;
michael@0 1325 }
michael@0 1326
michael@0 1327 tag = do_GetAtom(aAttr);
michael@0 1328 NS_ENSURE_TRUE(tag, NS_ERROR_OUT_OF_MEMORY);
michael@0 1329
michael@0 1330 nameSpaceID = kNameSpaceID_None;
michael@0 1331 }
michael@0 1332
michael@0 1333 rv = Persist(element, nameSpaceID, tag);
michael@0 1334 if (NS_FAILED(rv)) return rv;
michael@0 1335
michael@0 1336 return NS_OK;
michael@0 1337 }
michael@0 1338
michael@0 1339 nsresult
michael@0 1340 XULDocument::Persist(nsIContent* aElement, int32_t aNameSpaceID,
michael@0 1341 nsIAtom* aAttribute)
michael@0 1342 {
michael@0 1343 // For non-chrome documents, persistance is simply broken
michael@0 1344 if (!nsContentUtils::IsSystemPrincipal(NodePrincipal()))
michael@0 1345 return NS_ERROR_NOT_AVAILABLE;
michael@0 1346
michael@0 1347 // First make sure we _have_ a local store to stuff the persisted
michael@0 1348 // information into. (We might not have one if profile information
michael@0 1349 // hasn't been loaded yet...)
michael@0 1350 if (!mLocalStore)
michael@0 1351 return NS_OK;
michael@0 1352
michael@0 1353 nsresult rv;
michael@0 1354
michael@0 1355 nsCOMPtr<nsIRDFResource> element;
michael@0 1356 rv = nsXULContentUtils::GetElementResource(aElement, getter_AddRefs(element));
michael@0 1357 if (NS_FAILED(rv)) return rv;
michael@0 1358
michael@0 1359 // No ID, so nothing to persist.
michael@0 1360 if (! element)
michael@0 1361 return NS_OK;
michael@0 1362
michael@0 1363 // Ick. Construct a property from the attribute. Punt on
michael@0 1364 // namespaces for now.
michael@0 1365 // Don't bother with unreasonable attributes. We clamp long values,
michael@0 1366 // but truncating attribute names turns it into a different attribute
michael@0 1367 // so there's no point in persisting anything at all
michael@0 1368 nsAtomCString attrstr(aAttribute);
michael@0 1369 if (attrstr.Length() > kMaxAttrNameLength) {
michael@0 1370 NS_WARNING("Can't persist, Attribute name too long");
michael@0 1371 return NS_ERROR_ILLEGAL_VALUE;
michael@0 1372 }
michael@0 1373
michael@0 1374 nsCOMPtr<nsIRDFResource> attr;
michael@0 1375 rv = gRDFService->GetResource(attrstr,
michael@0 1376 getter_AddRefs(attr));
michael@0 1377 if (NS_FAILED(rv)) return rv;
michael@0 1378
michael@0 1379 // Turn the value into a literal
michael@0 1380 nsAutoString valuestr;
michael@0 1381 aElement->GetAttr(kNameSpaceID_None, aAttribute, valuestr);
michael@0 1382
michael@0 1383 // prevent over-long attributes that choke the parser (bug 319846)
michael@0 1384 // (can't simply Truncate without testing, it's implemented
michael@0 1385 // using SetLength and will grow a short string)
michael@0 1386 if (valuestr.Length() > kMaxAttributeLength) {
michael@0 1387 NS_WARNING("Truncating persisted attribute value");
michael@0 1388 valuestr.Truncate(kMaxAttributeLength);
michael@0 1389 }
michael@0 1390
michael@0 1391 // See if there was an old value...
michael@0 1392 nsCOMPtr<nsIRDFNode> oldvalue;
michael@0 1393 rv = mLocalStore->GetTarget(element, attr, true, getter_AddRefs(oldvalue));
michael@0 1394 if (NS_FAILED(rv)) return rv;
michael@0 1395
michael@0 1396 if (oldvalue && valuestr.IsEmpty()) {
michael@0 1397 // ...there was an oldvalue, and they've removed it. XXXThis
michael@0 1398 // handling isn't quite right...
michael@0 1399 rv = mLocalStore->Unassert(element, attr, oldvalue);
michael@0 1400 }
michael@0 1401 else {
michael@0 1402 // Now either 'change' or 'assert' based on whether there was
michael@0 1403 // an old value.
michael@0 1404 nsCOMPtr<nsIRDFLiteral> newvalue;
michael@0 1405 rv = gRDFService->GetLiteral(valuestr.get(), getter_AddRefs(newvalue));
michael@0 1406 if (NS_FAILED(rv)) return rv;
michael@0 1407
michael@0 1408 if (oldvalue) {
michael@0 1409 if (oldvalue != newvalue)
michael@0 1410 rv = mLocalStore->Change(element, attr, oldvalue, newvalue);
michael@0 1411 else
michael@0 1412 rv = NS_OK;
michael@0 1413 }
michael@0 1414 else {
michael@0 1415 rv = mLocalStore->Assert(element, attr, newvalue, true);
michael@0 1416 }
michael@0 1417 }
michael@0 1418
michael@0 1419 if (NS_FAILED(rv)) return rv;
michael@0 1420
michael@0 1421 // Add it to the persisted set for this document (if it's not
michael@0 1422 // there already).
michael@0 1423 {
michael@0 1424 nsAutoCString docurl;
michael@0 1425 rv = mDocumentURI->GetSpec(docurl);
michael@0 1426 if (NS_FAILED(rv)) return rv;
michael@0 1427
michael@0 1428 nsCOMPtr<nsIRDFResource> doc;
michael@0 1429 rv = gRDFService->GetResource(docurl, getter_AddRefs(doc));
michael@0 1430 if (NS_FAILED(rv)) return rv;
michael@0 1431
michael@0 1432 bool hasAssertion;
michael@0 1433 rv = mLocalStore->HasAssertion(doc, kNC_persist, element, true, &hasAssertion);
michael@0 1434 if (NS_FAILED(rv)) return rv;
michael@0 1435
michael@0 1436 if (! hasAssertion) {
michael@0 1437 rv = mLocalStore->Assert(doc, kNC_persist, element, true);
michael@0 1438 if (NS_FAILED(rv)) return rv;
michael@0 1439 }
michael@0 1440 }
michael@0 1441
michael@0 1442 return NS_OK;
michael@0 1443 }
michael@0 1444
michael@0 1445
michael@0 1446 nsresult
michael@0 1447 XULDocument::GetViewportSize(int32_t* aWidth,
michael@0 1448 int32_t* aHeight)
michael@0 1449 {
michael@0 1450 *aWidth = *aHeight = 0;
michael@0 1451
michael@0 1452 FlushPendingNotifications(Flush_Layout);
michael@0 1453
michael@0 1454 nsIPresShell *shell = GetShell();
michael@0 1455 NS_ENSURE_TRUE(shell, NS_ERROR_FAILURE);
michael@0 1456
michael@0 1457 nsIFrame* frame = shell->GetRootFrame();
michael@0 1458 NS_ENSURE_TRUE(frame, NS_ERROR_FAILURE);
michael@0 1459
michael@0 1460 nsSize size = frame->GetSize();
michael@0 1461
michael@0 1462 *aWidth = nsPresContext::AppUnitsToIntCSSPixels(size.width);
michael@0 1463 *aHeight = nsPresContext::AppUnitsToIntCSSPixels(size.height);
michael@0 1464
michael@0 1465 return NS_OK;
michael@0 1466 }
michael@0 1467
michael@0 1468 NS_IMETHODIMP
michael@0 1469 XULDocument::GetWidth(int32_t* aWidth)
michael@0 1470 {
michael@0 1471 NS_ENSURE_ARG_POINTER(aWidth);
michael@0 1472
michael@0 1473 int32_t height;
michael@0 1474 return GetViewportSize(aWidth, &height);
michael@0 1475 }
michael@0 1476
michael@0 1477 int32_t
michael@0 1478 XULDocument::GetWidth(ErrorResult& aRv)
michael@0 1479 {
michael@0 1480 int32_t width;
michael@0 1481 aRv = GetWidth(&width);
michael@0 1482 return width;
michael@0 1483 }
michael@0 1484
michael@0 1485 NS_IMETHODIMP
michael@0 1486 XULDocument::GetHeight(int32_t* aHeight)
michael@0 1487 {
michael@0 1488 NS_ENSURE_ARG_POINTER(aHeight);
michael@0 1489
michael@0 1490 int32_t width;
michael@0 1491 return GetViewportSize(&width, aHeight);
michael@0 1492 }
michael@0 1493
michael@0 1494 int32_t
michael@0 1495 XULDocument::GetHeight(ErrorResult& aRv)
michael@0 1496 {
michael@0 1497 int32_t height;
michael@0 1498 aRv = GetHeight(&height);
michael@0 1499 return height;
michael@0 1500 }
michael@0 1501
michael@0 1502 JSObject*
michael@0 1503 GetScopeObjectOfNode(nsIDOMNode* node)
michael@0 1504 {
michael@0 1505 MOZ_ASSERT(node, "Must not be called with null.");
michael@0 1506
michael@0 1507 // Window root occasionally keeps alive a node of a document whose
michael@0 1508 // window is already dead. If in this brief period someone calls
michael@0 1509 // GetPopupNode and we return that node, nsNodeSH::PreCreate will throw,
michael@0 1510 // because it will not know which scope this node belongs to. Returning
michael@0 1511 // an orphan node like that to JS would be a bug anyway, so to avoid
michael@0 1512 // this, let's do the same check as nsNodeSH::PreCreate does to
michael@0 1513 // determine the scope and if it fails let's just return null in
michael@0 1514 // XULDocument::GetPopupNode.
michael@0 1515 nsCOMPtr<nsINode> inode = do_QueryInterface(node);
michael@0 1516 MOZ_ASSERT(inode, "How can this happen?");
michael@0 1517
michael@0 1518 nsIDocument* doc = inode->OwnerDoc();
michael@0 1519 MOZ_ASSERT(inode, "This should never happen.");
michael@0 1520
michael@0 1521 nsIGlobalObject* global = doc->GetScopeObject();
michael@0 1522 return global ? global->GetGlobalJSObject() : nullptr;
michael@0 1523 }
michael@0 1524
michael@0 1525 //----------------------------------------------------------------------
michael@0 1526 //
michael@0 1527 // nsIDOMXULDocument interface
michael@0 1528 //
michael@0 1529
michael@0 1530 NS_IMETHODIMP
michael@0 1531 XULDocument::GetPopupNode(nsIDOMNode** aNode)
michael@0 1532 {
michael@0 1533 *aNode = nullptr;
michael@0 1534
michael@0 1535 nsCOMPtr<nsIDOMNode> node;
michael@0 1536 nsCOMPtr<nsPIWindowRoot> rootWin = GetWindowRoot();
michael@0 1537 if (rootWin)
michael@0 1538 node = rootWin->GetPopupNode(); // addref happens here
michael@0 1539
michael@0 1540 if (!node) {
michael@0 1541 nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
michael@0 1542 if (pm) {
michael@0 1543 node = pm->GetLastTriggerPopupNode(this);
michael@0 1544 }
michael@0 1545 }
michael@0 1546
michael@0 1547 if (node && nsContentUtils::CanCallerAccess(node)
michael@0 1548 && GetScopeObjectOfNode(node)) {
michael@0 1549 node.swap(*aNode);
michael@0 1550 }
michael@0 1551
michael@0 1552 return NS_OK;
michael@0 1553 }
michael@0 1554
michael@0 1555 already_AddRefed<nsINode>
michael@0 1556 XULDocument::GetPopupNode()
michael@0 1557 {
michael@0 1558 nsCOMPtr<nsIDOMNode> node;
michael@0 1559 DebugOnly<nsresult> rv = GetPopupNode(getter_AddRefs(node));
michael@0 1560 MOZ_ASSERT(NS_SUCCEEDED(rv));
michael@0 1561 nsCOMPtr<nsINode> retval(do_QueryInterface(node));
michael@0 1562 return retval.forget();
michael@0 1563 }
michael@0 1564
michael@0 1565 NS_IMETHODIMP
michael@0 1566 XULDocument::SetPopupNode(nsIDOMNode* aNode)
michael@0 1567 {
michael@0 1568 if (aNode) {
michael@0 1569 // only allow real node objects
michael@0 1570 nsCOMPtr<nsINode> node = do_QueryInterface(aNode);
michael@0 1571 NS_ENSURE_ARG(node);
michael@0 1572 }
michael@0 1573
michael@0 1574 nsCOMPtr<nsPIWindowRoot> rootWin = GetWindowRoot();
michael@0 1575 if (rootWin)
michael@0 1576 rootWin->SetPopupNode(aNode); // addref happens here
michael@0 1577
michael@0 1578 return NS_OK;
michael@0 1579 }
michael@0 1580
michael@0 1581 void
michael@0 1582 XULDocument::SetPopupNode(nsINode* aNode)
michael@0 1583 {
michael@0 1584 nsCOMPtr<nsIDOMNode> node(do_QueryInterface(aNode));
michael@0 1585 DebugOnly<nsresult> rv = SetPopupNode(node);
michael@0 1586 MOZ_ASSERT(NS_SUCCEEDED(rv));
michael@0 1587 }
michael@0 1588
michael@0 1589 // Returns the rangeOffset element from the XUL Popup Manager. This is for
michael@0 1590 // chrome callers only.
michael@0 1591 NS_IMETHODIMP
michael@0 1592 XULDocument::GetPopupRangeParent(nsIDOMNode** aRangeParent)
michael@0 1593 {
michael@0 1594 NS_ENSURE_ARG_POINTER(aRangeParent);
michael@0 1595 *aRangeParent = nullptr;
michael@0 1596
michael@0 1597 nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
michael@0 1598 if (!pm)
michael@0 1599 return NS_ERROR_FAILURE;
michael@0 1600
michael@0 1601 int32_t offset;
michael@0 1602 pm->GetMouseLocation(aRangeParent, &offset);
michael@0 1603
michael@0 1604 if (*aRangeParent && !nsContentUtils::CanCallerAccess(*aRangeParent)) {
michael@0 1605 NS_RELEASE(*aRangeParent);
michael@0 1606 return NS_ERROR_DOM_SECURITY_ERR;
michael@0 1607 }
michael@0 1608
michael@0 1609 return NS_OK;
michael@0 1610 }
michael@0 1611
michael@0 1612 already_AddRefed<nsINode>
michael@0 1613 XULDocument::GetPopupRangeParent(ErrorResult& aRv)
michael@0 1614 {
michael@0 1615 nsCOMPtr<nsIDOMNode> node;
michael@0 1616 aRv = GetPopupRangeParent(getter_AddRefs(node));
michael@0 1617 nsCOMPtr<nsINode> retval(do_QueryInterface(node));
michael@0 1618 return retval.forget();
michael@0 1619 }
michael@0 1620
michael@0 1621
michael@0 1622 // Returns the rangeOffset element from the XUL Popup Manager. We check the
michael@0 1623 // rangeParent to determine if the caller has rights to access to the data.
michael@0 1624 NS_IMETHODIMP
michael@0 1625 XULDocument::GetPopupRangeOffset(int32_t* aRangeOffset)
michael@0 1626 {
michael@0 1627 ErrorResult rv;
michael@0 1628 *aRangeOffset = GetPopupRangeOffset(rv);
michael@0 1629 return rv.ErrorCode();
michael@0 1630 }
michael@0 1631
michael@0 1632 int32_t
michael@0 1633 XULDocument::GetPopupRangeOffset(ErrorResult& aRv)
michael@0 1634 {
michael@0 1635 nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
michael@0 1636 if (!pm) {
michael@0 1637 aRv.Throw(NS_ERROR_FAILURE);
michael@0 1638 return 0;
michael@0 1639 }
michael@0 1640
michael@0 1641 int32_t offset;
michael@0 1642 nsCOMPtr<nsIDOMNode> parent;
michael@0 1643 pm->GetMouseLocation(getter_AddRefs(parent), &offset);
michael@0 1644
michael@0 1645 if (parent && !nsContentUtils::CanCallerAccess(parent)) {
michael@0 1646 aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
michael@0 1647 return 0;
michael@0 1648 }
michael@0 1649 return offset;
michael@0 1650 }
michael@0 1651
michael@0 1652 NS_IMETHODIMP
michael@0 1653 XULDocument::GetTooltipNode(nsIDOMNode** aNode)
michael@0 1654 {
michael@0 1655 *aNode = nullptr;
michael@0 1656
michael@0 1657 nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
michael@0 1658 if (pm) {
michael@0 1659 nsCOMPtr<nsIDOMNode> node = pm->GetLastTriggerTooltipNode(this);
michael@0 1660 if (node && nsContentUtils::CanCallerAccess(node))
michael@0 1661 node.swap(*aNode);
michael@0 1662 }
michael@0 1663
michael@0 1664 return NS_OK;
michael@0 1665 }
michael@0 1666
michael@0 1667 already_AddRefed<nsINode>
michael@0 1668 XULDocument::GetTooltipNode()
michael@0 1669 {
michael@0 1670 nsCOMPtr<nsIDOMNode> node;
michael@0 1671 DebugOnly<nsresult> rv = GetTooltipNode(getter_AddRefs(node));
michael@0 1672 MOZ_ASSERT(NS_SUCCEEDED(rv));
michael@0 1673 nsCOMPtr<nsINode> retval(do_QueryInterface(node));
michael@0 1674 return retval.forget();
michael@0 1675 }
michael@0 1676
michael@0 1677 NS_IMETHODIMP
michael@0 1678 XULDocument::SetTooltipNode(nsIDOMNode* aNode)
michael@0 1679 {
michael@0 1680 // do nothing
michael@0 1681 return NS_OK;
michael@0 1682 }
michael@0 1683
michael@0 1684
michael@0 1685 NS_IMETHODIMP
michael@0 1686 XULDocument::GetCommandDispatcher(nsIDOMXULCommandDispatcher** aTracker)
michael@0 1687 {
michael@0 1688 *aTracker = mCommandDispatcher;
michael@0 1689 NS_IF_ADDREF(*aTracker);
michael@0 1690 return NS_OK;
michael@0 1691 }
michael@0 1692
michael@0 1693 Element*
michael@0 1694 XULDocument::GetElementById(const nsAString& aId)
michael@0 1695 {
michael@0 1696 if (!CheckGetElementByIdArg(aId))
michael@0 1697 return nullptr;
michael@0 1698
michael@0 1699 nsIdentifierMapEntry *entry = mIdentifierMap.GetEntry(aId);
michael@0 1700 if (entry) {
michael@0 1701 Element* element = entry->GetIdElement();
michael@0 1702 if (element)
michael@0 1703 return element;
michael@0 1704 }
michael@0 1705
michael@0 1706 nsRefMapEntry* refEntry = mRefMap.GetEntry(aId);
michael@0 1707 if (refEntry) {
michael@0 1708 NS_ASSERTION(refEntry->GetFirstElement(),
michael@0 1709 "nsRefMapEntries should have nonempty content lists");
michael@0 1710 return refEntry->GetFirstElement();
michael@0 1711 }
michael@0 1712 return nullptr;
michael@0 1713 }
michael@0 1714
michael@0 1715 nsresult
michael@0 1716 XULDocument::AddElementToDocumentPre(Element* aElement)
michael@0 1717 {
michael@0 1718 // Do a bunch of work that's necessary when an element gets added
michael@0 1719 // to the XUL Document.
michael@0 1720 nsresult rv;
michael@0 1721
michael@0 1722 // 1. Add the element to the resource-to-element map. Also add it to
michael@0 1723 // the id map, since it seems this can be called when creating
michael@0 1724 // elements from prototypes.
michael@0 1725 nsIAtom* id = aElement->GetID();
michael@0 1726 if (id) {
michael@0 1727 // FIXME: Shouldn't BindToTree take care of this?
michael@0 1728 nsAutoScriptBlocker scriptBlocker;
michael@0 1729 AddToIdTable(aElement, id);
michael@0 1730 }
michael@0 1731 rv = AddElementToRefMap(aElement);
michael@0 1732 if (NS_FAILED(rv)) return rv;
michael@0 1733
michael@0 1734 // 2. If the element is a 'command updater' (i.e., has a
michael@0 1735 // "commandupdater='true'" attribute), then add the element to the
michael@0 1736 // document's command dispatcher
michael@0 1737 if (aElement->AttrValueIs(kNameSpaceID_None, nsGkAtoms::commandupdater,
michael@0 1738 nsGkAtoms::_true, eCaseMatters)) {
michael@0 1739 rv = nsXULContentUtils::SetCommandUpdater(this, aElement);
michael@0 1740 if (NS_FAILED(rv)) return rv;
michael@0 1741 }
michael@0 1742
michael@0 1743 // 3. Check for a broadcaster hookup attribute, in which case
michael@0 1744 // we'll hook the node up as a listener on a broadcaster.
michael@0 1745 bool listener, resolved;
michael@0 1746 rv = CheckBroadcasterHookup(aElement, &listener, &resolved);
michael@0 1747 if (NS_FAILED(rv)) return rv;
michael@0 1748
michael@0 1749 // If it's not there yet, we may be able to defer hookup until
michael@0 1750 // later.
michael@0 1751 if (listener && !resolved && (mResolutionPhase != nsForwardReference::eDone)) {
michael@0 1752 BroadcasterHookup* hookup = new BroadcasterHookup(this, aElement);
michael@0 1753 if (! hookup)
michael@0 1754 return NS_ERROR_OUT_OF_MEMORY;
michael@0 1755
michael@0 1756 rv = AddForwardReference(hookup);
michael@0 1757 if (NS_FAILED(rv)) return rv;
michael@0 1758 }
michael@0 1759
michael@0 1760 return NS_OK;
michael@0 1761 }
michael@0 1762
michael@0 1763 nsresult
michael@0 1764 XULDocument::AddElementToDocumentPost(Element* aElement)
michael@0 1765 {
michael@0 1766 // We need to pay special attention to the keyset tag to set up a listener
michael@0 1767 if (aElement->NodeInfo()->Equals(nsGkAtoms::keyset, kNameSpaceID_XUL)) {
michael@0 1768 // Create our XUL key listener and hook it up.
michael@0 1769 nsXBLService::AttachGlobalKeyHandler(aElement);
michael@0 1770 }
michael@0 1771
michael@0 1772 // See if we need to attach a XUL template to this node
michael@0 1773 bool needsHookup;
michael@0 1774 nsresult rv = CheckTemplateBuilderHookup(aElement, &needsHookup);
michael@0 1775 if (NS_FAILED(rv))
michael@0 1776 return rv;
michael@0 1777
michael@0 1778 if (needsHookup) {
michael@0 1779 if (mResolutionPhase == nsForwardReference::eDone) {
michael@0 1780 rv = CreateTemplateBuilder(aElement);
michael@0 1781 if (NS_FAILED(rv))
michael@0 1782 return rv;
michael@0 1783 }
michael@0 1784 else {
michael@0 1785 TemplateBuilderHookup* hookup = new TemplateBuilderHookup(aElement);
michael@0 1786 if (! hookup)
michael@0 1787 return NS_ERROR_OUT_OF_MEMORY;
michael@0 1788
michael@0 1789 rv = AddForwardReference(hookup);
michael@0 1790 if (NS_FAILED(rv))
michael@0 1791 return rv;
michael@0 1792 }
michael@0 1793 }
michael@0 1794
michael@0 1795 return NS_OK;
michael@0 1796 }
michael@0 1797
michael@0 1798 NS_IMETHODIMP
michael@0 1799 XULDocument::AddSubtreeToDocument(nsIContent* aContent)
michael@0 1800 {
michael@0 1801 NS_ASSERTION(aContent->GetCurrentDoc() == this, "Element not in doc!");
michael@0 1802 // From here on we only care about elements.
michael@0 1803 if (!aContent->IsElement()) {
michael@0 1804 return NS_OK;
michael@0 1805 }
michael@0 1806
michael@0 1807 Element* aElement = aContent->AsElement();
michael@0 1808
michael@0 1809 // Do pre-order addition magic
michael@0 1810 nsresult rv = AddElementToDocumentPre(aElement);
michael@0 1811 if (NS_FAILED(rv)) return rv;
michael@0 1812
michael@0 1813 // Recurse to children
michael@0 1814 for (nsIContent* child = aElement->GetLastChild();
michael@0 1815 child;
michael@0 1816 child = child->GetPreviousSibling()) {
michael@0 1817
michael@0 1818 rv = AddSubtreeToDocument(child);
michael@0 1819 if (NS_FAILED(rv))
michael@0 1820 return rv;
michael@0 1821 }
michael@0 1822
michael@0 1823 // Do post-order addition magic
michael@0 1824 return AddElementToDocumentPost(aElement);
michael@0 1825 }
michael@0 1826
michael@0 1827 NS_IMETHODIMP
michael@0 1828 XULDocument::RemoveSubtreeFromDocument(nsIContent* aContent)
michael@0 1829 {
michael@0 1830 // From here on we only care about elements.
michael@0 1831 if (!aContent->IsElement()) {
michael@0 1832 return NS_OK;
michael@0 1833 }
michael@0 1834
michael@0 1835 Element* aElement = aContent->AsElement();
michael@0 1836
michael@0 1837 // Do a bunch of cleanup to remove an element from the XUL
michael@0 1838 // document.
michael@0 1839 nsresult rv;
michael@0 1840
michael@0 1841 if (aElement->NodeInfo()->Equals(nsGkAtoms::keyset, kNameSpaceID_XUL)) {
michael@0 1842 nsXBLService::DetachGlobalKeyHandler(aElement);
michael@0 1843 }
michael@0 1844
michael@0 1845 // 1. Remove any children from the document.
michael@0 1846 for (nsIContent* child = aElement->GetLastChild();
michael@0 1847 child;
michael@0 1848 child = child->GetPreviousSibling()) {
michael@0 1849
michael@0 1850 rv = RemoveSubtreeFromDocument(child);
michael@0 1851 if (NS_FAILED(rv))
michael@0 1852 return rv;
michael@0 1853 }
michael@0 1854
michael@0 1855 // 2. Remove the element from the resource-to-element map.
michael@0 1856 // Also remove it from the id map, since we added it in
michael@0 1857 // AddElementToDocumentPre().
michael@0 1858 RemoveElementFromRefMap(aElement);
michael@0 1859 nsIAtom* id = aElement->GetID();
michael@0 1860 if (id) {
michael@0 1861 // FIXME: Shouldn't UnbindFromTree take care of this?
michael@0 1862 nsAutoScriptBlocker scriptBlocker;
michael@0 1863 RemoveFromIdTable(aElement, id);
michael@0 1864 }
michael@0 1865
michael@0 1866 // 3. If the element is a 'command updater', then remove the
michael@0 1867 // element from the document's command dispatcher.
michael@0 1868 if (aElement->AttrValueIs(kNameSpaceID_None, nsGkAtoms::commandupdater,
michael@0 1869 nsGkAtoms::_true, eCaseMatters)) {
michael@0 1870 nsCOMPtr<nsIDOMElement> domelement = do_QueryInterface(aElement);
michael@0 1871 NS_ASSERTION(domelement != nullptr, "not a DOM element");
michael@0 1872 if (! domelement)
michael@0 1873 return NS_ERROR_UNEXPECTED;
michael@0 1874
michael@0 1875 rv = mCommandDispatcher->RemoveCommandUpdater(domelement);
michael@0 1876 if (NS_FAILED(rv)) return rv;
michael@0 1877 }
michael@0 1878
michael@0 1879 // 4. Remove the element from our broadcaster map, since it is no longer
michael@0 1880 // in the document.
michael@0 1881 nsCOMPtr<Element> broadcaster, listener;
michael@0 1882 nsAutoString attribute, broadcasterID;
michael@0 1883 rv = FindBroadcaster(aElement, getter_AddRefs(listener),
michael@0 1884 broadcasterID, attribute, getter_AddRefs(broadcaster));
michael@0 1885 if (rv == NS_FINDBROADCASTER_FOUND) {
michael@0 1886 RemoveBroadcastListenerFor(*broadcaster, *listener, attribute);
michael@0 1887 }
michael@0 1888
michael@0 1889 return NS_OK;
michael@0 1890 }
michael@0 1891
michael@0 1892 NS_IMETHODIMP
michael@0 1893 XULDocument::SetTemplateBuilderFor(nsIContent* aContent,
michael@0 1894 nsIXULTemplateBuilder* aBuilder)
michael@0 1895 {
michael@0 1896 if (! mTemplateBuilderTable) {
michael@0 1897 if (!aBuilder) {
michael@0 1898 return NS_OK;
michael@0 1899 }
michael@0 1900 mTemplateBuilderTable = new BuilderTable;
michael@0 1901 }
michael@0 1902
michael@0 1903 if (aBuilder) {
michael@0 1904 mTemplateBuilderTable->Put(aContent, aBuilder);
michael@0 1905 }
michael@0 1906 else {
michael@0 1907 mTemplateBuilderTable->Remove(aContent);
michael@0 1908 }
michael@0 1909
michael@0 1910 return NS_OK;
michael@0 1911 }
michael@0 1912
michael@0 1913 NS_IMETHODIMP
michael@0 1914 XULDocument::GetTemplateBuilderFor(nsIContent* aContent,
michael@0 1915 nsIXULTemplateBuilder** aResult)
michael@0 1916 {
michael@0 1917 if (mTemplateBuilderTable) {
michael@0 1918 mTemplateBuilderTable->Get(aContent, aResult);
michael@0 1919 }
michael@0 1920 else
michael@0 1921 *aResult = nullptr;
michael@0 1922
michael@0 1923 return NS_OK;
michael@0 1924 }
michael@0 1925
michael@0 1926 static void
michael@0 1927 GetRefMapAttribute(Element* aElement, nsAutoString* aValue)
michael@0 1928 {
michael@0 1929 aElement->GetAttr(kNameSpaceID_None, nsGkAtoms::ref, *aValue);
michael@0 1930 if (aValue->IsEmpty() && !aElement->GetIDAttributeName()) {
michael@0 1931 aElement->GetAttr(kNameSpaceID_None, nsGkAtoms::id, *aValue);
michael@0 1932 }
michael@0 1933 }
michael@0 1934
michael@0 1935 nsresult
michael@0 1936 XULDocument::AddElementToRefMap(Element* aElement)
michael@0 1937 {
michael@0 1938 // Look at the element's 'ref' attribute, and if set,
michael@0 1939 // add an entry in the resource-to-element map to the element.
michael@0 1940 nsAutoString value;
michael@0 1941 GetRefMapAttribute(aElement, &value);
michael@0 1942 if (!value.IsEmpty()) {
michael@0 1943 nsRefMapEntry *entry = mRefMap.PutEntry(value);
michael@0 1944 if (!entry)
michael@0 1945 return NS_ERROR_OUT_OF_MEMORY;
michael@0 1946 if (!entry->AddElement(aElement))
michael@0 1947 return NS_ERROR_OUT_OF_MEMORY;
michael@0 1948 }
michael@0 1949
michael@0 1950 return NS_OK;
michael@0 1951 }
michael@0 1952
michael@0 1953 void
michael@0 1954 XULDocument::RemoveElementFromRefMap(Element* aElement)
michael@0 1955 {
michael@0 1956 // Remove the element from the resource-to-element map.
michael@0 1957 nsAutoString value;
michael@0 1958 GetRefMapAttribute(aElement, &value);
michael@0 1959 if (!value.IsEmpty()) {
michael@0 1960 nsRefMapEntry *entry = mRefMap.GetEntry(value);
michael@0 1961 if (!entry)
michael@0 1962 return;
michael@0 1963 if (entry->RemoveElement(aElement)) {
michael@0 1964 mRefMap.RawRemoveEntry(entry);
michael@0 1965 }
michael@0 1966 }
michael@0 1967 }
michael@0 1968
michael@0 1969 //----------------------------------------------------------------------
michael@0 1970 //
michael@0 1971 // nsIDOMNode interface
michael@0 1972 //
michael@0 1973
michael@0 1974 nsresult
michael@0 1975 XULDocument::Clone(nsINodeInfo *aNodeInfo, nsINode **aResult) const
michael@0 1976 {
michael@0 1977 // We don't allow cloning of a XUL document
michael@0 1978 *aResult = nullptr;
michael@0 1979 return NS_ERROR_DOM_NOT_SUPPORTED_ERR;
michael@0 1980 }
michael@0 1981
michael@0 1982
michael@0 1983 //----------------------------------------------------------------------
michael@0 1984 //
michael@0 1985 // Implementation methods
michael@0 1986 //
michael@0 1987
michael@0 1988 nsresult
michael@0 1989 XULDocument::Init()
michael@0 1990 {
michael@0 1991 nsresult rv = XMLDocument::Init();
michael@0 1992 NS_ENSURE_SUCCESS(rv, rv);
michael@0 1993
michael@0 1994 // Create our command dispatcher and hook it up.
michael@0 1995 mCommandDispatcher = new nsXULCommandDispatcher(this);
michael@0 1996 NS_ENSURE_TRUE(mCommandDispatcher, NS_ERROR_OUT_OF_MEMORY);
michael@0 1997
michael@0 1998 // this _could_ fail; e.g., if we've tried to grab the local store
michael@0 1999 // before profiles have initialized. If so, no big deal; nothing
michael@0 2000 // will persist.
michael@0 2001 mLocalStore = do_GetService(NS_LOCALSTORE_CONTRACTID);
michael@0 2002
michael@0 2003 if (gRefCnt++ == 0) {
michael@0 2004 // Keep the RDF service cached in a member variable to make using
michael@0 2005 // it a bit less painful
michael@0 2006 rv = CallGetService("@mozilla.org/rdf/rdf-service;1", &gRDFService);
michael@0 2007 NS_ASSERTION(NS_SUCCEEDED(rv), "unable to get RDF Service");
michael@0 2008 if (NS_FAILED(rv)) return rv;
michael@0 2009
michael@0 2010 gRDFService->GetResource(NS_LITERAL_CSTRING(NC_NAMESPACE_URI "persist"),
michael@0 2011 &kNC_persist);
michael@0 2012 gRDFService->GetResource(NS_LITERAL_CSTRING(NC_NAMESPACE_URI "attribute"),
michael@0 2013 &kNC_attribute);
michael@0 2014 gRDFService->GetResource(NS_LITERAL_CSTRING(NC_NAMESPACE_URI "value"),
michael@0 2015 &kNC_value);
michael@0 2016
michael@0 2017 // ensure that the XUL prototype cache is instantiated successfully,
michael@0 2018 // so that we can use nsXULPrototypeCache::GetInstance() without
michael@0 2019 // null-checks in the rest of the class.
michael@0 2020 nsXULPrototypeCache* cache = nsXULPrototypeCache::GetInstance();
michael@0 2021 if (!cache) {
michael@0 2022 NS_ERROR("Could not instantiate nsXULPrototypeCache");
michael@0 2023 return NS_ERROR_FAILURE;
michael@0 2024 }
michael@0 2025 }
michael@0 2026
michael@0 2027 Preferences::RegisterCallback(XULDocument::DirectionChanged,
michael@0 2028 "intl.uidirection.", this);
michael@0 2029
michael@0 2030 #ifdef PR_LOGGING
michael@0 2031 if (! gXULLog)
michael@0 2032 gXULLog = PR_NewLogModule("XULDocument");
michael@0 2033 #endif
michael@0 2034
michael@0 2035 return NS_OK;
michael@0 2036 }
michael@0 2037
michael@0 2038
michael@0 2039 nsresult
michael@0 2040 XULDocument::StartLayout(void)
michael@0 2041 {
michael@0 2042 mMayStartLayout = true;
michael@0 2043 nsCOMPtr<nsIPresShell> shell = GetShell();
michael@0 2044 if (shell) {
michael@0 2045 // Resize-reflow this time
michael@0 2046 nsPresContext *cx = shell->GetPresContext();
michael@0 2047 NS_ASSERTION(cx != nullptr, "no pres context");
michael@0 2048 if (! cx)
michael@0 2049 return NS_ERROR_UNEXPECTED;
michael@0 2050
michael@0 2051 nsCOMPtr<nsIDocShell> docShell = cx->GetDocShell();
michael@0 2052 NS_ASSERTION(docShell != nullptr, "container is not a docshell");
michael@0 2053 if (! docShell)
michael@0 2054 return NS_ERROR_UNEXPECTED;
michael@0 2055
michael@0 2056 nsresult rv = NS_OK;
michael@0 2057 nsRect r = cx->GetVisibleArea();
michael@0 2058 rv = shell->Initialize(r.width, r.height);
michael@0 2059 NS_ENSURE_SUCCESS(rv, rv);
michael@0 2060 }
michael@0 2061
michael@0 2062 return NS_OK;
michael@0 2063 }
michael@0 2064
michael@0 2065 /* static */
michael@0 2066 bool
michael@0 2067 XULDocument::MatchAttribute(nsIContent* aContent,
michael@0 2068 int32_t aNamespaceID,
michael@0 2069 nsIAtom* aAttrName,
michael@0 2070 void* aData)
michael@0 2071 {
michael@0 2072 NS_PRECONDITION(aContent, "Must have content node to work with!");
michael@0 2073 nsString* attrValue = static_cast<nsString*>(aData);
michael@0 2074 if (aNamespaceID != kNameSpaceID_Unknown &&
michael@0 2075 aNamespaceID != kNameSpaceID_Wildcard) {
michael@0 2076 return attrValue->EqualsLiteral("*") ?
michael@0 2077 aContent->HasAttr(aNamespaceID, aAttrName) :
michael@0 2078 aContent->AttrValueIs(aNamespaceID, aAttrName, *attrValue,
michael@0 2079 eCaseMatters);
michael@0 2080 }
michael@0 2081
michael@0 2082 // Qualified name match. This takes more work.
michael@0 2083
michael@0 2084 uint32_t count = aContent->GetAttrCount();
michael@0 2085 for (uint32_t i = 0; i < count; ++i) {
michael@0 2086 const nsAttrName* name = aContent->GetAttrNameAt(i);
michael@0 2087 bool nameMatch;
michael@0 2088 if (name->IsAtom()) {
michael@0 2089 nameMatch = name->Atom() == aAttrName;
michael@0 2090 } else if (aNamespaceID == kNameSpaceID_Wildcard) {
michael@0 2091 nameMatch = name->NodeInfo()->Equals(aAttrName);
michael@0 2092 } else {
michael@0 2093 nameMatch = name->NodeInfo()->QualifiedNameEquals(aAttrName);
michael@0 2094 }
michael@0 2095
michael@0 2096 if (nameMatch) {
michael@0 2097 return attrValue->EqualsLiteral("*") ||
michael@0 2098 aContent->AttrValueIs(name->NamespaceID(), name->LocalName(),
michael@0 2099 *attrValue, eCaseMatters);
michael@0 2100 }
michael@0 2101 }
michael@0 2102
michael@0 2103 return false;
michael@0 2104 }
michael@0 2105
michael@0 2106 nsresult
michael@0 2107 XULDocument::PrepareToLoad(nsISupports* aContainer,
michael@0 2108 const char* aCommand,
michael@0 2109 nsIChannel* aChannel,
michael@0 2110 nsILoadGroup* aLoadGroup,
michael@0 2111 nsIParser** aResult)
michael@0 2112 {
michael@0 2113 // Get the document's principal
michael@0 2114 nsCOMPtr<nsIPrincipal> principal;
michael@0 2115 nsContentUtils::GetSecurityManager()->
michael@0 2116 GetChannelPrincipal(aChannel, getter_AddRefs(principal));
michael@0 2117 return PrepareToLoadPrototype(mDocumentURI, aCommand, principal, aResult);
michael@0 2118 }
michael@0 2119
michael@0 2120
michael@0 2121 nsresult
michael@0 2122 XULDocument::PrepareToLoadPrototype(nsIURI* aURI, const char* aCommand,
michael@0 2123 nsIPrincipal* aDocumentPrincipal,
michael@0 2124 nsIParser** aResult)
michael@0 2125 {
michael@0 2126 nsresult rv;
michael@0 2127
michael@0 2128 // Create a new prototype document.
michael@0 2129 rv = NS_NewXULPrototypeDocument(getter_AddRefs(mCurrentPrototype));
michael@0 2130 if (NS_FAILED(rv)) return rv;
michael@0 2131
michael@0 2132 rv = mCurrentPrototype->InitPrincipal(aURI, aDocumentPrincipal);
michael@0 2133 if (NS_FAILED(rv)) {
michael@0 2134 mCurrentPrototype = nullptr;
michael@0 2135 return rv;
michael@0 2136 }
michael@0 2137
michael@0 2138 // Bootstrap the master document prototype.
michael@0 2139 if (! mMasterPrototype) {
michael@0 2140 mMasterPrototype = mCurrentPrototype;
michael@0 2141 // Set our principal based on the master proto.
michael@0 2142 SetPrincipal(aDocumentPrincipal);
michael@0 2143 }
michael@0 2144
michael@0 2145 // Create a XUL content sink, a parser, and kick off a load for
michael@0 2146 // the overlay.
michael@0 2147 nsRefPtr<XULContentSinkImpl> sink = new XULContentSinkImpl();
michael@0 2148 if (!sink) return NS_ERROR_OUT_OF_MEMORY;
michael@0 2149
michael@0 2150 rv = sink->Init(this, mCurrentPrototype);
michael@0 2151 NS_ASSERTION(NS_SUCCEEDED(rv), "Unable to initialize datasource sink");
michael@0 2152 if (NS_FAILED(rv)) return rv;
michael@0 2153
michael@0 2154 nsCOMPtr<nsIParser> parser = do_CreateInstance(kParserCID, &rv);
michael@0 2155 NS_ASSERTION(NS_SUCCEEDED(rv), "unable to create parser");
michael@0 2156 if (NS_FAILED(rv)) return rv;
michael@0 2157
michael@0 2158 parser->SetCommand(nsCRT::strcmp(aCommand, "view-source") ? eViewNormal :
michael@0 2159 eViewSource);
michael@0 2160
michael@0 2161 parser->SetDocumentCharset(NS_LITERAL_CSTRING("UTF-8"),
michael@0 2162 kCharsetFromDocTypeDefault);
michael@0 2163 parser->SetContentSink(sink); // grabs a reference to the parser
michael@0 2164
michael@0 2165 *aResult = parser;
michael@0 2166 NS_ADDREF(*aResult);
michael@0 2167 return NS_OK;
michael@0 2168 }
michael@0 2169
michael@0 2170
michael@0 2171 nsresult
michael@0 2172 XULDocument::ApplyPersistentAttributes()
michael@0 2173 {
michael@0 2174 // For non-chrome documents, persistance is simply broken
michael@0 2175 if (!nsContentUtils::IsSystemPrincipal(NodePrincipal()))
michael@0 2176 return NS_ERROR_NOT_AVAILABLE;
michael@0 2177
michael@0 2178 // Add all of the 'persisted' attributes into the content
michael@0 2179 // model.
michael@0 2180 if (!mLocalStore)
michael@0 2181 return NS_OK;
michael@0 2182
michael@0 2183 mApplyingPersistedAttrs = true;
michael@0 2184 ApplyPersistentAttributesInternal();
michael@0 2185 mApplyingPersistedAttrs = false;
michael@0 2186
michael@0 2187 // After we've applied persistence once, we should only reapply
michael@0 2188 // it to nodes created by overlays
michael@0 2189 mRestrictPersistence = true;
michael@0 2190 mPersistenceIds.Clear();
michael@0 2191
michael@0 2192 return NS_OK;
michael@0 2193 }
michael@0 2194
michael@0 2195
michael@0 2196 nsresult
michael@0 2197 XULDocument::ApplyPersistentAttributesInternal()
michael@0 2198 {
michael@0 2199 nsCOMArray<nsIContent> elements;
michael@0 2200
michael@0 2201 nsAutoCString docurl;
michael@0 2202 mDocumentURI->GetSpec(docurl);
michael@0 2203
michael@0 2204 nsCOMPtr<nsIRDFResource> doc;
michael@0 2205 gRDFService->GetResource(docurl, getter_AddRefs(doc));
michael@0 2206
michael@0 2207 nsCOMPtr<nsISimpleEnumerator> persisted;
michael@0 2208 mLocalStore->GetTargets(doc, kNC_persist, true, getter_AddRefs(persisted));
michael@0 2209
michael@0 2210 while (1) {
michael@0 2211 bool hasmore = false;
michael@0 2212 persisted->HasMoreElements(&hasmore);
michael@0 2213 if (! hasmore)
michael@0 2214 break;
michael@0 2215
michael@0 2216 nsCOMPtr<nsISupports> isupports;
michael@0 2217 persisted->GetNext(getter_AddRefs(isupports));
michael@0 2218
michael@0 2219 nsCOMPtr<nsIRDFResource> resource = do_QueryInterface(isupports);
michael@0 2220 if (! resource) {
michael@0 2221 NS_WARNING("expected element to be a resource");
michael@0 2222 continue;
michael@0 2223 }
michael@0 2224
michael@0 2225 const char *uri;
michael@0 2226 resource->GetValueConst(&uri);
michael@0 2227 if (! uri)
michael@0 2228 continue;
michael@0 2229
michael@0 2230 nsAutoString id;
michael@0 2231 nsXULContentUtils::MakeElementID(this, nsDependentCString(uri), id);
michael@0 2232
michael@0 2233 if (id.IsEmpty())
michael@0 2234 continue;
michael@0 2235
michael@0 2236 if (mRestrictPersistence && !mPersistenceIds.Contains(id))
michael@0 2237 continue;
michael@0 2238
michael@0 2239 // This will clear the array if there are no elements.
michael@0 2240 GetElementsForID(id, elements);
michael@0 2241
michael@0 2242 if (!elements.Count())
michael@0 2243 continue;
michael@0 2244
michael@0 2245 ApplyPersistentAttributesToElements(resource, elements);
michael@0 2246 }
michael@0 2247
michael@0 2248 return NS_OK;
michael@0 2249 }
michael@0 2250
michael@0 2251
michael@0 2252 nsresult
michael@0 2253 XULDocument::ApplyPersistentAttributesToElements(nsIRDFResource* aResource,
michael@0 2254 nsCOMArray<nsIContent>& aElements)
michael@0 2255 {
michael@0 2256 nsresult rv;
michael@0 2257
michael@0 2258 nsCOMPtr<nsISimpleEnumerator> attrs;
michael@0 2259 rv = mLocalStore->ArcLabelsOut(aResource, getter_AddRefs(attrs));
michael@0 2260 if (NS_FAILED(rv)) return rv;
michael@0 2261
michael@0 2262 while (1) {
michael@0 2263 bool hasmore;
michael@0 2264 rv = attrs->HasMoreElements(&hasmore);
michael@0 2265 if (NS_FAILED(rv)) return rv;
michael@0 2266
michael@0 2267 if (! hasmore)
michael@0 2268 break;
michael@0 2269
michael@0 2270 nsCOMPtr<nsISupports> isupports;
michael@0 2271 rv = attrs->GetNext(getter_AddRefs(isupports));
michael@0 2272 if (NS_FAILED(rv)) return rv;
michael@0 2273
michael@0 2274 nsCOMPtr<nsIRDFResource> property = do_QueryInterface(isupports);
michael@0 2275 if (! property) {
michael@0 2276 NS_WARNING("expected a resource");
michael@0 2277 continue;
michael@0 2278 }
michael@0 2279
michael@0 2280 const char* attrname;
michael@0 2281 rv = property->GetValueConst(&attrname);
michael@0 2282 if (NS_FAILED(rv)) return rv;
michael@0 2283
michael@0 2284 nsCOMPtr<nsIAtom> attr = do_GetAtom(attrname);
michael@0 2285 if (! attr)
michael@0 2286 return NS_ERROR_OUT_OF_MEMORY;
michael@0 2287
michael@0 2288 // XXX could hang namespace off here, as well...
michael@0 2289
michael@0 2290 nsCOMPtr<nsIRDFNode> node;
michael@0 2291 rv = mLocalStore->GetTarget(aResource, property, true,
michael@0 2292 getter_AddRefs(node));
michael@0 2293 if (NS_FAILED(rv)) return rv;
michael@0 2294
michael@0 2295 nsCOMPtr<nsIRDFLiteral> literal = do_QueryInterface(node);
michael@0 2296 if (! literal) {
michael@0 2297 NS_WARNING("expected a literal");
michael@0 2298 continue;
michael@0 2299 }
michael@0 2300
michael@0 2301 const char16_t* value;
michael@0 2302 rv = literal->GetValueConst(&value);
michael@0 2303 if (NS_FAILED(rv)) return rv;
michael@0 2304
michael@0 2305 nsDependentString wrapper(value);
michael@0 2306
michael@0 2307 uint32_t cnt = aElements.Count();
michael@0 2308
michael@0 2309 for (int32_t i = int32_t(cnt) - 1; i >= 0; --i) {
michael@0 2310 nsCOMPtr<nsIContent> element = aElements.SafeObjectAt(i);
michael@0 2311 if (!element)
michael@0 2312 continue;
michael@0 2313
michael@0 2314 rv = element->SetAttr(/* XXX */ kNameSpaceID_None,
michael@0 2315 attr,
michael@0 2316 wrapper,
michael@0 2317 true);
michael@0 2318 }
michael@0 2319 }
michael@0 2320
michael@0 2321 return NS_OK;
michael@0 2322 }
michael@0 2323
michael@0 2324 void
michael@0 2325 XULDocument::TraceProtos(JSTracer* aTrc, uint32_t aGCNumber)
michael@0 2326 {
michael@0 2327 uint32_t i, count = mPrototypes.Length();
michael@0 2328 for (i = 0; i < count; ++i) {
michael@0 2329 mPrototypes[i]->TraceProtos(aTrc, aGCNumber);
michael@0 2330 }
michael@0 2331 }
michael@0 2332
michael@0 2333 //----------------------------------------------------------------------
michael@0 2334 //
michael@0 2335 // XULDocument::ContextStack
michael@0 2336 //
michael@0 2337
michael@0 2338 XULDocument::ContextStack::ContextStack()
michael@0 2339 : mTop(nullptr), mDepth(0)
michael@0 2340 {
michael@0 2341 }
michael@0 2342
michael@0 2343 XULDocument::ContextStack::~ContextStack()
michael@0 2344 {
michael@0 2345 while (mTop) {
michael@0 2346 Entry* doomed = mTop;
michael@0 2347 mTop = mTop->mNext;
michael@0 2348 NS_IF_RELEASE(doomed->mElement);
michael@0 2349 delete doomed;
michael@0 2350 }
michael@0 2351 }
michael@0 2352
michael@0 2353 nsresult
michael@0 2354 XULDocument::ContextStack::Push(nsXULPrototypeElement* aPrototype,
michael@0 2355 nsIContent* aElement)
michael@0 2356 {
michael@0 2357 Entry* entry = new Entry;
michael@0 2358 if (! entry)
michael@0 2359 return NS_ERROR_OUT_OF_MEMORY;
michael@0 2360
michael@0 2361 entry->mPrototype = aPrototype;
michael@0 2362 entry->mElement = aElement;
michael@0 2363 NS_IF_ADDREF(entry->mElement);
michael@0 2364 entry->mIndex = 0;
michael@0 2365
michael@0 2366 entry->mNext = mTop;
michael@0 2367 mTop = entry;
michael@0 2368
michael@0 2369 ++mDepth;
michael@0 2370 return NS_OK;
michael@0 2371 }
michael@0 2372
michael@0 2373 nsresult
michael@0 2374 XULDocument::ContextStack::Pop()
michael@0 2375 {
michael@0 2376 if (mDepth == 0)
michael@0 2377 return NS_ERROR_UNEXPECTED;
michael@0 2378
michael@0 2379 Entry* doomed = mTop;
michael@0 2380 mTop = mTop->mNext;
michael@0 2381 --mDepth;
michael@0 2382
michael@0 2383 NS_IF_RELEASE(doomed->mElement);
michael@0 2384 delete doomed;
michael@0 2385 return NS_OK;
michael@0 2386 }
michael@0 2387
michael@0 2388 nsresult
michael@0 2389 XULDocument::ContextStack::Peek(nsXULPrototypeElement** aPrototype,
michael@0 2390 nsIContent** aElement,
michael@0 2391 int32_t* aIndex)
michael@0 2392 {
michael@0 2393 if (mDepth == 0)
michael@0 2394 return NS_ERROR_UNEXPECTED;
michael@0 2395
michael@0 2396 *aPrototype = mTop->mPrototype;
michael@0 2397 *aElement = mTop->mElement;
michael@0 2398 NS_IF_ADDREF(*aElement);
michael@0 2399 *aIndex = mTop->mIndex;
michael@0 2400
michael@0 2401 return NS_OK;
michael@0 2402 }
michael@0 2403
michael@0 2404
michael@0 2405 nsresult
michael@0 2406 XULDocument::ContextStack::SetTopIndex(int32_t aIndex)
michael@0 2407 {
michael@0 2408 if (mDepth == 0)
michael@0 2409 return NS_ERROR_UNEXPECTED;
michael@0 2410
michael@0 2411 mTop->mIndex = aIndex;
michael@0 2412 return NS_OK;
michael@0 2413 }
michael@0 2414
michael@0 2415
michael@0 2416 //----------------------------------------------------------------------
michael@0 2417 //
michael@0 2418 // Content model walking routines
michael@0 2419 //
michael@0 2420
michael@0 2421 nsresult
michael@0 2422 XULDocument::PrepareToWalk()
michael@0 2423 {
michael@0 2424 // Prepare to walk the mCurrentPrototype
michael@0 2425 nsresult rv;
michael@0 2426
michael@0 2427 // Keep an owning reference to the prototype document so that its
michael@0 2428 // elements aren't yanked from beneath us.
michael@0 2429 mPrototypes.AppendElement(mCurrentPrototype);
michael@0 2430
michael@0 2431 // Get the prototype's root element and initialize the context
michael@0 2432 // stack for the prototype walk.
michael@0 2433 nsXULPrototypeElement* proto = mCurrentPrototype->GetRootElement();
michael@0 2434
michael@0 2435 if (! proto) {
michael@0 2436 #ifdef PR_LOGGING
michael@0 2437 if (PR_LOG_TEST(gXULLog, PR_LOG_ERROR)) {
michael@0 2438 nsCOMPtr<nsIURI> url = mCurrentPrototype->GetURI();
michael@0 2439
michael@0 2440 nsAutoCString urlspec;
michael@0 2441 rv = url->GetSpec(urlspec);
michael@0 2442 if (NS_FAILED(rv)) return rv;
michael@0 2443
michael@0 2444 PR_LOG(gXULLog, PR_LOG_ERROR,
michael@0 2445 ("xul: error parsing '%s'", urlspec.get()));
michael@0 2446 }
michael@0 2447 #endif
michael@0 2448
michael@0 2449 return NS_OK;
michael@0 2450 }
michael@0 2451
michael@0 2452 uint32_t piInsertionPoint = 0;
michael@0 2453 if (mState != eState_Master) {
michael@0 2454 int32_t indexOfRoot = IndexOf(GetRootElement());
michael@0 2455 NS_ASSERTION(indexOfRoot >= 0,
michael@0 2456 "No root content when preparing to walk overlay!");
michael@0 2457 piInsertionPoint = indexOfRoot;
michael@0 2458 }
michael@0 2459
michael@0 2460 const nsTArray<nsRefPtr<nsXULPrototypePI> >& processingInstructions =
michael@0 2461 mCurrentPrototype->GetProcessingInstructions();
michael@0 2462
michael@0 2463 uint32_t total = processingInstructions.Length();
michael@0 2464 for (uint32_t i = 0; i < total; ++i) {
michael@0 2465 rv = CreateAndInsertPI(processingInstructions[i],
michael@0 2466 this, piInsertionPoint + i);
michael@0 2467 if (NS_FAILED(rv)) return rv;
michael@0 2468 }
michael@0 2469
michael@0 2470 // Now check the chrome registry for any additional overlays.
michael@0 2471 rv = AddChromeOverlays();
michael@0 2472 if (NS_FAILED(rv)) return rv;
michael@0 2473
michael@0 2474 // Do one-time initialization if we're preparing to walk the
michael@0 2475 // master document's prototype.
michael@0 2476 nsRefPtr<Element> root;
michael@0 2477
michael@0 2478 if (mState == eState_Master) {
michael@0 2479 // Add the root element
michael@0 2480 rv = CreateElementFromPrototype(proto, getter_AddRefs(root), true);
michael@0 2481 if (NS_FAILED(rv)) return rv;
michael@0 2482
michael@0 2483 rv = AppendChildTo(root, false);
michael@0 2484 if (NS_FAILED(rv)) return rv;
michael@0 2485
michael@0 2486 rv = AddElementToRefMap(root);
michael@0 2487 if (NS_FAILED(rv)) return rv;
michael@0 2488
michael@0 2489 // Block onload until we've finished building the complete
michael@0 2490 // document content model.
michael@0 2491 BlockOnload();
michael@0 2492 }
michael@0 2493
michael@0 2494 // There'd better not be anything on the context stack at this
michael@0 2495 // point! This is the basis case for our "induction" in
michael@0 2496 // ResumeWalk(), below, which'll assume that there's always a
michael@0 2497 // content element on the context stack if either 1) we're in the
michael@0 2498 // "master" document, or 2) we're in an overlay, and we've got
michael@0 2499 // more than one prototype element (the single, root "overlay"
michael@0 2500 // element) on the stack.
michael@0 2501 NS_ASSERTION(mContextStack.Depth() == 0, "something's on the context stack already");
michael@0 2502 if (mContextStack.Depth() != 0)
michael@0 2503 return NS_ERROR_UNEXPECTED;
michael@0 2504
michael@0 2505 rv = mContextStack.Push(proto, root);
michael@0 2506 if (NS_FAILED(rv)) return rv;
michael@0 2507
michael@0 2508 return NS_OK;
michael@0 2509 }
michael@0 2510
michael@0 2511 nsresult
michael@0 2512 XULDocument::CreateAndInsertPI(const nsXULPrototypePI* aProtoPI,
michael@0 2513 nsINode* aParent, uint32_t aIndex)
michael@0 2514 {
michael@0 2515 NS_PRECONDITION(aProtoPI, "null ptr");
michael@0 2516 NS_PRECONDITION(aParent, "null ptr");
michael@0 2517
michael@0 2518 nsRefPtr<ProcessingInstruction> node =
michael@0 2519 NS_NewXMLProcessingInstruction(mNodeInfoManager, aProtoPI->mTarget,
michael@0 2520 aProtoPI->mData);
michael@0 2521
michael@0 2522 nsresult rv;
michael@0 2523 if (aProtoPI->mTarget.EqualsLiteral("xml-stylesheet")) {
michael@0 2524 rv = InsertXMLStylesheetPI(aProtoPI, aParent, aIndex, node);
michael@0 2525 } else if (aProtoPI->mTarget.EqualsLiteral("xul-overlay")) {
michael@0 2526 rv = InsertXULOverlayPI(aProtoPI, aParent, aIndex, node);
michael@0 2527 } else {
michael@0 2528 // No special processing, just add the PI to the document.
michael@0 2529 rv = aParent->InsertChildAt(node, aIndex, false);
michael@0 2530 }
michael@0 2531
michael@0 2532 return rv;
michael@0 2533 }
michael@0 2534
michael@0 2535 nsresult
michael@0 2536 XULDocument::InsertXMLStylesheetPI(const nsXULPrototypePI* aProtoPI,
michael@0 2537 nsINode* aParent,
michael@0 2538 uint32_t aIndex,
michael@0 2539 nsIContent* aPINode)
michael@0 2540 {
michael@0 2541 nsCOMPtr<nsIStyleSheetLinkingElement> ssle(do_QueryInterface(aPINode));
michael@0 2542 NS_ASSERTION(ssle, "passed XML Stylesheet node does not "
michael@0 2543 "implement nsIStyleSheetLinkingElement!");
michael@0 2544
michael@0 2545 nsresult rv;
michael@0 2546
michael@0 2547 ssle->InitStyleLinkElement(false);
michael@0 2548 // We want to be notified when the style sheet finishes loading, so
michael@0 2549 // disable style sheet loading for now.
michael@0 2550 ssle->SetEnableUpdates(false);
michael@0 2551 ssle->OverrideBaseURI(mCurrentPrototype->GetURI());
michael@0 2552
michael@0 2553 rv = aParent->InsertChildAt(aPINode, aIndex, false);
michael@0 2554 if (NS_FAILED(rv)) return rv;
michael@0 2555
michael@0 2556 ssle->SetEnableUpdates(true);
michael@0 2557
michael@0 2558 // load the stylesheet if necessary, passing ourselves as
michael@0 2559 // nsICSSObserver
michael@0 2560 bool willNotify;
michael@0 2561 bool isAlternate;
michael@0 2562 rv = ssle->UpdateStyleSheet(this, &willNotify, &isAlternate);
michael@0 2563 if (NS_SUCCEEDED(rv) && willNotify && !isAlternate) {
michael@0 2564 ++mPendingSheets;
michael@0 2565 }
michael@0 2566
michael@0 2567 // Ignore errors from UpdateStyleSheet; we don't want failure to
michael@0 2568 // do that to break the XUL document load. But do propagate out
michael@0 2569 // NS_ERROR_OUT_OF_MEMORY.
michael@0 2570 if (rv == NS_ERROR_OUT_OF_MEMORY) {
michael@0 2571 return rv;
michael@0 2572 }
michael@0 2573
michael@0 2574 return NS_OK;
michael@0 2575 }
michael@0 2576
michael@0 2577 nsresult
michael@0 2578 XULDocument::InsertXULOverlayPI(const nsXULPrototypePI* aProtoPI,
michael@0 2579 nsINode* aParent,
michael@0 2580 uint32_t aIndex,
michael@0 2581 nsIContent* aPINode)
michael@0 2582 {
michael@0 2583 nsresult rv;
michael@0 2584
michael@0 2585 rv = aParent->InsertChildAt(aPINode, aIndex, false);
michael@0 2586 if (NS_FAILED(rv)) return rv;
michael@0 2587
michael@0 2588 // xul-overlay PI is special only in prolog
michael@0 2589 if (!nsContentUtils::InProlog(aPINode)) {
michael@0 2590 return NS_OK;
michael@0 2591 }
michael@0 2592
michael@0 2593 nsAutoString href;
michael@0 2594 nsContentUtils::GetPseudoAttributeValue(aProtoPI->mData,
michael@0 2595 nsGkAtoms::href,
michael@0 2596 href);
michael@0 2597
michael@0 2598 // If there was no href, we can't do anything with this PI
michael@0 2599 if (href.IsEmpty()) {
michael@0 2600 return NS_OK;
michael@0 2601 }
michael@0 2602
michael@0 2603 // Add the overlay to our list of overlays that need to be processed.
michael@0 2604 nsCOMPtr<nsIURI> uri;
michael@0 2605
michael@0 2606 rv = NS_NewURI(getter_AddRefs(uri), href, nullptr,
michael@0 2607 mCurrentPrototype->GetURI());
michael@0 2608 if (NS_SUCCEEDED(rv)) {
michael@0 2609 // We insert overlays into mUnloadedOverlays at the same index in
michael@0 2610 // document order, so they end up in the reverse of the document
michael@0 2611 // order in mUnloadedOverlays.
michael@0 2612 // This is needed because the code in ResumeWalk loads the overlays
michael@0 2613 // by processing the last item of mUnloadedOverlays and removing it
michael@0 2614 // from the array.
michael@0 2615 mUnloadedOverlays.InsertElementAt(0, uri);
michael@0 2616 rv = NS_OK;
michael@0 2617 } else if (rv == NS_ERROR_MALFORMED_URI) {
michael@0 2618 // The URL is bad, move along. Don't propagate for now.
michael@0 2619 // XXX report this to the Error Console (bug 359846)
michael@0 2620 rv = NS_OK;
michael@0 2621 }
michael@0 2622
michael@0 2623 return rv;
michael@0 2624 }
michael@0 2625
michael@0 2626 nsresult
michael@0 2627 XULDocument::AddChromeOverlays()
michael@0 2628 {
michael@0 2629 nsresult rv;
michael@0 2630
michael@0 2631 nsCOMPtr<nsIURI> docUri = mCurrentPrototype->GetURI();
michael@0 2632
michael@0 2633 /* overlays only apply to chrome or about URIs */
michael@0 2634 if (!IsOverlayAllowed(docUri)) return NS_OK;
michael@0 2635
michael@0 2636 nsCOMPtr<nsIXULOverlayProvider> chromeReg =
michael@0 2637 mozilla::services::GetXULOverlayProviderService();
michael@0 2638 // In embedding situations, the chrome registry may not provide overlays,
michael@0 2639 // or even exist at all; that's OK.
michael@0 2640 NS_ENSURE_TRUE(chromeReg, NS_OK);
michael@0 2641
michael@0 2642 nsCOMPtr<nsISimpleEnumerator> overlays;
michael@0 2643 rv = chromeReg->GetXULOverlays(docUri, getter_AddRefs(overlays));
michael@0 2644 NS_ENSURE_SUCCESS(rv, rv);
michael@0 2645
michael@0 2646 bool moreOverlays;
michael@0 2647 nsCOMPtr<nsISupports> next;
michael@0 2648 nsCOMPtr<nsIURI> uri;
michael@0 2649
michael@0 2650 while (NS_SUCCEEDED(rv = overlays->HasMoreElements(&moreOverlays)) &&
michael@0 2651 moreOverlays) {
michael@0 2652
michael@0 2653 rv = overlays->GetNext(getter_AddRefs(next));
michael@0 2654 if (NS_FAILED(rv) || !next) break;
michael@0 2655
michael@0 2656 uri = do_QueryInterface(next);
michael@0 2657 if (!uri) {
michael@0 2658 NS_ERROR("Chrome registry handed me a non-nsIURI object!");
michael@0 2659 continue;
michael@0 2660 }
michael@0 2661
michael@0 2662 // Same comment as in XULDocument::InsertXULOverlayPI
michael@0 2663 mUnloadedOverlays.InsertElementAt(0, uri);
michael@0 2664 }
michael@0 2665
michael@0 2666 return rv;
michael@0 2667 }
michael@0 2668
michael@0 2669 NS_IMETHODIMP
michael@0 2670 XULDocument::LoadOverlay(const nsAString& aURL, nsIObserver* aObserver)
michael@0 2671 {
michael@0 2672 nsresult rv;
michael@0 2673
michael@0 2674 nsCOMPtr<nsIURI> uri;
michael@0 2675 rv = NS_NewURI(getter_AddRefs(uri), aURL, nullptr);
michael@0 2676 if (NS_FAILED(rv)) return rv;
michael@0 2677
michael@0 2678 if (aObserver) {
michael@0 2679 nsIObserver* obs = nullptr;
michael@0 2680 if (!mOverlayLoadObservers) {
michael@0 2681 mOverlayLoadObservers = new nsInterfaceHashtable<nsURIHashKey,nsIObserver>;
michael@0 2682 }
michael@0 2683 obs = mOverlayLoadObservers->GetWeak(uri);
michael@0 2684
michael@0 2685 if (obs) {
michael@0 2686 // We don't support loading the same overlay twice into the same
michael@0 2687 // document - that doesn't make sense anyway.
michael@0 2688 return NS_ERROR_FAILURE;
michael@0 2689 }
michael@0 2690 mOverlayLoadObservers->Put(uri, aObserver);
michael@0 2691 }
michael@0 2692 bool shouldReturn, failureFromContent;
michael@0 2693 rv = LoadOverlayInternal(uri, true, &shouldReturn, &failureFromContent);
michael@0 2694 if (NS_FAILED(rv) && mOverlayLoadObservers)
michael@0 2695 mOverlayLoadObservers->Remove(uri); // remove the observer if LoadOverlayInternal generated an error
michael@0 2696 return rv;
michael@0 2697 }
michael@0 2698
michael@0 2699 nsresult
michael@0 2700 XULDocument::LoadOverlayInternal(nsIURI* aURI, bool aIsDynamic,
michael@0 2701 bool* aShouldReturn,
michael@0 2702 bool* aFailureFromContent)
michael@0 2703 {
michael@0 2704 nsresult rv;
michael@0 2705
michael@0 2706 *aShouldReturn = false;
michael@0 2707 *aFailureFromContent = false;
michael@0 2708
michael@0 2709 #ifdef PR_LOGGING
michael@0 2710 if (PR_LOG_TEST(gXULLog, PR_LOG_DEBUG)) {
michael@0 2711 nsAutoCString urlspec;
michael@0 2712 aURI->GetSpec(urlspec);
michael@0 2713 nsAutoCString parentDoc;
michael@0 2714 nsCOMPtr<nsIURI> uri;
michael@0 2715 nsresult rv = mChannel->GetOriginalURI(getter_AddRefs(uri));
michael@0 2716 if (NS_SUCCEEDED(rv))
michael@0 2717 rv = uri->GetSpec(parentDoc);
michael@0 2718 if (!(parentDoc.get()))
michael@0 2719 parentDoc = "";
michael@0 2720
michael@0 2721 PR_LOG(gXULLog, PR_LOG_DEBUG,
michael@0 2722 ("xul: %s loading overlay %s", parentDoc.get(), urlspec.get()));
michael@0 2723 }
michael@0 2724 #endif
michael@0 2725
michael@0 2726 if (aIsDynamic)
michael@0 2727 mResolutionPhase = nsForwardReference::eStart;
michael@0 2728
michael@0 2729 // Chrome documents are allowed to load overlays from anywhere.
michael@0 2730 // In all other cases, the overlay is only allowed to load if
michael@0 2731 // the master document and prototype document have the same origin.
michael@0 2732
michael@0 2733 bool documentIsChrome = IsChromeURI(mDocumentURI);
michael@0 2734 if (!documentIsChrome) {
michael@0 2735 // Make sure we're allowed to load this overlay.
michael@0 2736 rv = NodePrincipal()->CheckMayLoad(aURI, true, false);
michael@0 2737 if (NS_FAILED(rv)) {
michael@0 2738 *aFailureFromContent = true;
michael@0 2739 return rv;
michael@0 2740 }
michael@0 2741 }
michael@0 2742
michael@0 2743 // Look in the prototype cache for the prototype document with
michael@0 2744 // the specified overlay URI. Only use the cache if the containing
michael@0 2745 // document is chrome otherwise it may not have a system principal and
michael@0 2746 // the cached document will, see bug 565610.
michael@0 2747 bool overlayIsChrome = IsChromeURI(aURI);
michael@0 2748 mCurrentPrototype = overlayIsChrome && documentIsChrome ?
michael@0 2749 nsXULPrototypeCache::GetInstance()->GetPrototype(aURI) : nullptr;
michael@0 2750
michael@0 2751 // Same comment as nsChromeProtocolHandler::NewChannel and
michael@0 2752 // XULDocument::StartDocumentLoad
michael@0 2753 // - Ben Goodger
michael@0 2754 //
michael@0 2755 // We don't abort on failure here because there are too many valid
michael@0 2756 // cases that can return failure, and the null-ness of |proto| is
michael@0 2757 // enough to trigger the fail-safe parse-from-disk solution.
michael@0 2758 // Example failure cases (for reference) include:
michael@0 2759 //
michael@0 2760 // NS_ERROR_NOT_AVAILABLE: the URI was not found in the FastLoad file,
michael@0 2761 // parse from disk
michael@0 2762 // other: the FastLoad file, XUL.mfl, could not be found, probably
michael@0 2763 // due to being accessed before a profile has been selected
michael@0 2764 // (e.g. loading chrome for the profile manager itself).
michael@0 2765 // The .xul file must be parsed from disk.
michael@0 2766
michael@0 2767 bool useXULCache = nsXULPrototypeCache::GetInstance()->IsEnabled();
michael@0 2768 if (useXULCache && mCurrentPrototype) {
michael@0 2769 bool loaded;
michael@0 2770 rv = mCurrentPrototype->AwaitLoadDone(this, &loaded);
michael@0 2771 if (NS_FAILED(rv)) return rv;
michael@0 2772
michael@0 2773 if (! loaded) {
michael@0 2774 // Return to the main event loop and eagerly await the
michael@0 2775 // prototype overlay load's completion. When the content
michael@0 2776 // sink completes, it will trigger an EndLoad(), which'll
michael@0 2777 // wind us back up here, in ResumeWalk().
michael@0 2778 *aShouldReturn = true;
michael@0 2779 return NS_OK;
michael@0 2780 }
michael@0 2781
michael@0 2782 PR_LOG(gXULLog, PR_LOG_DEBUG, ("xul: overlay was cached"));
michael@0 2783
michael@0 2784 // Found the overlay's prototype in the cache, fully loaded. If
michael@0 2785 // this is a dynamic overlay, this will call ResumeWalk.
michael@0 2786 // Otherwise, we'll return to ResumeWalk, which called us.
michael@0 2787 return OnPrototypeLoadDone(aIsDynamic);
michael@0 2788 }
michael@0 2789 else {
michael@0 2790 // Not there. Initiate a load.
michael@0 2791 PR_LOG(gXULLog, PR_LOG_DEBUG, ("xul: overlay was not cached"));
michael@0 2792
michael@0 2793 if (mIsGoingAway) {
michael@0 2794 PR_LOG(gXULLog, PR_LOG_DEBUG, ("xul: ...and document already destroyed"));
michael@0 2795 return NS_ERROR_NOT_AVAILABLE;
michael@0 2796 }
michael@0 2797
michael@0 2798 // We'll set the right principal on the proto doc when we get
michael@0 2799 // OnStartRequest from the parser, so just pass in a null principal for
michael@0 2800 // now.
michael@0 2801 nsCOMPtr<nsIParser> parser;
michael@0 2802 rv = PrepareToLoadPrototype(aURI, "view", nullptr, getter_AddRefs(parser));
michael@0 2803 if (NS_FAILED(rv)) return rv;
michael@0 2804
michael@0 2805 // Predicate mIsWritingFastLoad on the XUL cache being enabled,
michael@0 2806 // so we don't have to re-check whether the cache is enabled all
michael@0 2807 // the time.
michael@0 2808 mIsWritingFastLoad = useXULCache;
michael@0 2809
michael@0 2810 nsCOMPtr<nsIStreamListener> listener = do_QueryInterface(parser);
michael@0 2811 if (! listener)
michael@0 2812 return NS_ERROR_UNEXPECTED;
michael@0 2813
michael@0 2814 // Add an observer to the parser; this'll get called when
michael@0 2815 // Necko fires its On[Start|Stop]Request() notifications,
michael@0 2816 // and will let us recover from a missing overlay.
michael@0 2817 ParserObserver* parserObserver =
michael@0 2818 new ParserObserver(this, mCurrentPrototype);
michael@0 2819 if (! parserObserver)
michael@0 2820 return NS_ERROR_OUT_OF_MEMORY;
michael@0 2821
michael@0 2822 NS_ADDREF(parserObserver);
michael@0 2823 parser->Parse(aURI, parserObserver);
michael@0 2824 NS_RELEASE(parserObserver);
michael@0 2825
michael@0 2826 nsCOMPtr<nsILoadGroup> group = do_QueryReferent(mDocumentLoadGroup);
michael@0 2827 nsCOMPtr<nsIChannel> channel;
michael@0 2828 rv = NS_NewChannel(getter_AddRefs(channel), aURI, nullptr, group);
michael@0 2829
michael@0 2830 if (NS_SUCCEEDED(rv)) {
michael@0 2831 // Set the owner of the channel to be our principal so
michael@0 2832 // that the overlay's JSObjects etc end up being created
michael@0 2833 // with the right principal and in the correct
michael@0 2834 // compartment.
michael@0 2835 channel->SetOwner(NodePrincipal());
michael@0 2836
michael@0 2837 rv = channel->AsyncOpen(listener, nullptr);
michael@0 2838 }
michael@0 2839
michael@0 2840 if (NS_FAILED(rv)) {
michael@0 2841 // Abandon this prototype
michael@0 2842 mCurrentPrototype = nullptr;
michael@0 2843
michael@0 2844 // The parser won't get an OnStartRequest and
michael@0 2845 // OnStopRequest, so it needs a Terminate.
michael@0 2846 parser->Terminate();
michael@0 2847
michael@0 2848 // Just move on to the next overlay. NS_OpenURI could fail
michael@0 2849 // just because a channel could not be opened, which can happen
michael@0 2850 // if a file or chrome package does not exist.
michael@0 2851 ReportMissingOverlay(aURI);
michael@0 2852
michael@0 2853 // XXX the error could indicate an internal error as well...
michael@0 2854 *aFailureFromContent = true;
michael@0 2855 return rv;
michael@0 2856 }
michael@0 2857
michael@0 2858 // If it's a 'chrome:' prototype document, then put it into
michael@0 2859 // the prototype cache; other XUL documents will be reloaded
michael@0 2860 // each time. We must do this after NS_OpenURI and AsyncOpen,
michael@0 2861 // or chrome code will wrongly create a cached chrome channel
michael@0 2862 // instead of a real one. Prototypes are only cached when the
michael@0 2863 // document to be overlayed is chrome to avoid caching overlay
michael@0 2864 // scripts with incorrect principals, see bug 565610.
michael@0 2865 if (useXULCache && overlayIsChrome && documentIsChrome) {
michael@0 2866 nsXULPrototypeCache::GetInstance()->PutPrototype(mCurrentPrototype);
michael@0 2867 }
michael@0 2868
michael@0 2869 // Return to the main event loop and eagerly await the
michael@0 2870 // overlay load's completion. When the content sink
michael@0 2871 // completes, it will trigger an EndLoad(), which'll wind
michael@0 2872 // us back in ResumeWalk().
michael@0 2873 if (!aIsDynamic)
michael@0 2874 *aShouldReturn = true;
michael@0 2875 }
michael@0 2876 return NS_OK;
michael@0 2877 }
michael@0 2878
michael@0 2879 static PLDHashOperator
michael@0 2880 FirePendingMergeNotification(nsIURI* aKey, nsCOMPtr<nsIObserver>& aObserver, void* aClosure)
michael@0 2881 {
michael@0 2882 aObserver->Observe(aKey, "xul-overlay-merged", EmptyString().get());
michael@0 2883
michael@0 2884 typedef nsInterfaceHashtable<nsURIHashKey,nsIObserver> table;
michael@0 2885 table* observers = static_cast<table*>(aClosure);
michael@0 2886 if (observers) {
michael@0 2887 observers->Remove(aKey);
michael@0 2888 }
michael@0 2889
michael@0 2890 return PL_DHASH_REMOVE;
michael@0 2891 }
michael@0 2892
michael@0 2893 nsresult
michael@0 2894 XULDocument::ResumeWalk()
michael@0 2895 {
michael@0 2896 // Walk the prototype and build the delegate content model. The
michael@0 2897 // walk is performed in a top-down, left-to-right fashion. That
michael@0 2898 // is, a parent is built before any of its children; a node is
michael@0 2899 // only built after all of its siblings to the left are fully
michael@0 2900 // constructed.
michael@0 2901 //
michael@0 2902 // It is interruptable so that transcluded documents (e.g.,
michael@0 2903 // <html:script src="..." />) can be properly re-loaded if the
michael@0 2904 // cached copy of the document becomes stale.
michael@0 2905 nsresult rv;
michael@0 2906 nsCOMPtr<nsIURI> overlayURI =
michael@0 2907 mCurrentPrototype ? mCurrentPrototype->GetURI() : nullptr;
michael@0 2908
michael@0 2909 while (1) {
michael@0 2910 // Begin (or resume) walking the current prototype.
michael@0 2911
michael@0 2912 while (mContextStack.Depth() > 0) {
michael@0 2913 // Look at the top of the stack to determine what we're
michael@0 2914 // currently working on.
michael@0 2915 // This will always be a node already constructed and
michael@0 2916 // inserted to the actual document.
michael@0 2917 nsXULPrototypeElement* proto;
michael@0 2918 nsCOMPtr<nsIContent> element;
michael@0 2919 int32_t indx; // all children of proto before indx (not
michael@0 2920 // inclusive) have already been constructed
michael@0 2921 rv = mContextStack.Peek(&proto, getter_AddRefs(element), &indx);
michael@0 2922 if (NS_FAILED(rv)) return rv;
michael@0 2923
michael@0 2924 if (indx >= (int32_t)proto->mChildren.Length()) {
michael@0 2925 if (element) {
michael@0 2926 // We've processed all of the prototype's children. If
michael@0 2927 // we're in the master prototype, do post-order
michael@0 2928 // document-level hookup. (An overlay will get its
michael@0 2929 // document hookup done when it's successfully
michael@0 2930 // resolved.)
michael@0 2931 if (mState == eState_Master) {
michael@0 2932 AddElementToDocumentPost(element->AsElement());
michael@0 2933
michael@0 2934 if (element->NodeInfo()->Equals(nsGkAtoms::style,
michael@0 2935 kNameSpaceID_XHTML) ||
michael@0 2936 element->NodeInfo()->Equals(nsGkAtoms::style,
michael@0 2937 kNameSpaceID_SVG)) {
michael@0 2938 // XXX sucks that we have to do this -
michael@0 2939 // see bug 370111
michael@0 2940 nsCOMPtr<nsIStyleSheetLinkingElement> ssle =
michael@0 2941 do_QueryInterface(element);
michael@0 2942 NS_ASSERTION(ssle, "<html:style> doesn't implement "
michael@0 2943 "nsIStyleSheetLinkingElement?");
michael@0 2944 bool willNotify;
michael@0 2945 bool isAlternate;
michael@0 2946 ssle->UpdateStyleSheet(nullptr, &willNotify,
michael@0 2947 &isAlternate);
michael@0 2948 }
michael@0 2949 }
michael@0 2950 }
michael@0 2951 // Now pop the context stack back up to the parent
michael@0 2952 // element and continue the prototype walk.
michael@0 2953 mContextStack.Pop();
michael@0 2954 continue;
michael@0 2955 }
michael@0 2956
michael@0 2957 // Grab the next child, and advance the current context stack
michael@0 2958 // to the next sibling to our right.
michael@0 2959 nsXULPrototypeNode* childproto = proto->mChildren[indx];
michael@0 2960 mContextStack.SetTopIndex(++indx);
michael@0 2961
michael@0 2962 // Whether we're in the "first ply" of an overlay:
michael@0 2963 // the "hookup" nodes. In the case !processingOverlayHookupNodes,
michael@0 2964 // we're in the master document -or- we're in an overlay, and far
michael@0 2965 // enough down into the overlay's content that we can simply build
michael@0 2966 // the delegates and attach them to the parent node.
michael@0 2967 bool processingOverlayHookupNodes = (mState == eState_Overlay) &&
michael@0 2968 (mContextStack.Depth() == 1);
michael@0 2969
michael@0 2970 NS_ASSERTION(element || processingOverlayHookupNodes,
michael@0 2971 "no element on context stack");
michael@0 2972
michael@0 2973 switch (childproto->mType) {
michael@0 2974 case nsXULPrototypeNode::eType_Element: {
michael@0 2975 // An 'element', which may contain more content.
michael@0 2976 nsXULPrototypeElement* protoele =
michael@0 2977 static_cast<nsXULPrototypeElement*>(childproto);
michael@0 2978
michael@0 2979 nsRefPtr<Element> child;
michael@0 2980
michael@0 2981 if (!processingOverlayHookupNodes) {
michael@0 2982 rv = CreateElementFromPrototype(protoele,
michael@0 2983 getter_AddRefs(child),
michael@0 2984 false);
michael@0 2985 if (NS_FAILED(rv)) return rv;
michael@0 2986
michael@0 2987 // ...and append it to the content model.
michael@0 2988 rv = element->AppendChildTo(child, false);
michael@0 2989 if (NS_FAILED(rv)) return rv;
michael@0 2990
michael@0 2991 // If we're only restoring persisted things on
michael@0 2992 // some elements, store the ID here to do that.
michael@0 2993 if (mRestrictPersistence) {
michael@0 2994 nsIAtom* id = child->GetID();
michael@0 2995 if (id) {
michael@0 2996 mPersistenceIds.PutEntry(nsDependentAtomString(id));
michael@0 2997 }
michael@0 2998 }
michael@0 2999
michael@0 3000 // do pre-order document-level hookup, but only if
michael@0 3001 // we're in the master document. For an overlay,
michael@0 3002 // this will happen when the overlay is
michael@0 3003 // successfully resolved.
michael@0 3004 if (mState == eState_Master)
michael@0 3005 AddElementToDocumentPre(child);
michael@0 3006 }
michael@0 3007 else {
michael@0 3008 // We're in the "first ply" of an overlay: the
michael@0 3009 // "hookup" nodes. Create an 'overlay' element so
michael@0 3010 // that we can continue to build content, and
michael@0 3011 // enter a forward reference so we can hook it up
michael@0 3012 // later.
michael@0 3013 rv = CreateOverlayElement(protoele, getter_AddRefs(child));
michael@0 3014 if (NS_FAILED(rv)) return rv;
michael@0 3015 }
michael@0 3016
michael@0 3017 // If it has children, push the element onto the context
michael@0 3018 // stack and begin to process them.
michael@0 3019 if (protoele->mChildren.Length() > 0) {
michael@0 3020 rv = mContextStack.Push(protoele, child);
michael@0 3021 if (NS_FAILED(rv)) return rv;
michael@0 3022 }
michael@0 3023 else {
michael@0 3024 if (mState == eState_Master) {
michael@0 3025 // If there are no children, and we're in the
michael@0 3026 // master document, do post-order document hookup
michael@0 3027 // immediately.
michael@0 3028 AddElementToDocumentPost(child);
michael@0 3029 }
michael@0 3030 }
michael@0 3031 }
michael@0 3032 break;
michael@0 3033
michael@0 3034 case nsXULPrototypeNode::eType_Script: {
michael@0 3035 // A script reference. Execute the script immediately;
michael@0 3036 // this may have side effects in the content model.
michael@0 3037 nsXULPrototypeScript* scriptproto =
michael@0 3038 static_cast<nsXULPrototypeScript*>(childproto);
michael@0 3039
michael@0 3040 if (scriptproto->mSrcURI) {
michael@0 3041 // A transcluded script reference; this may
michael@0 3042 // "block" our prototype walk if the script isn't
michael@0 3043 // cached, or the cached copy of the script is
michael@0 3044 // stale and must be reloaded.
michael@0 3045 bool blocked;
michael@0 3046 rv = LoadScript(scriptproto, &blocked);
michael@0 3047 // If the script cannot be loaded, just keep going!
michael@0 3048
michael@0 3049 if (NS_SUCCEEDED(rv) && blocked)
michael@0 3050 return NS_OK;
michael@0 3051 }
michael@0 3052 else if (scriptproto->GetScriptObject()) {
michael@0 3053 // An inline script
michael@0 3054 rv = ExecuteScript(scriptproto);
michael@0 3055 if (NS_FAILED(rv)) return rv;
michael@0 3056 }
michael@0 3057 }
michael@0 3058 break;
michael@0 3059
michael@0 3060 case nsXULPrototypeNode::eType_Text: {
michael@0 3061 // A simple text node.
michael@0 3062
michael@0 3063 if (!processingOverlayHookupNodes) {
michael@0 3064 // This does mean that text nodes that are direct children
michael@0 3065 // of <overlay> get ignored.
michael@0 3066
michael@0 3067 nsRefPtr<nsTextNode> text =
michael@0 3068 new nsTextNode(mNodeInfoManager);
michael@0 3069
michael@0 3070 nsXULPrototypeText* textproto =
michael@0 3071 static_cast<nsXULPrototypeText*>(childproto);
michael@0 3072 text->SetText(textproto->mValue, false);
michael@0 3073
michael@0 3074 rv = element->AppendChildTo(text, false);
michael@0 3075 NS_ENSURE_SUCCESS(rv, rv);
michael@0 3076 }
michael@0 3077 }
michael@0 3078 break;
michael@0 3079
michael@0 3080 case nsXULPrototypeNode::eType_PI: {
michael@0 3081 nsXULPrototypePI* piProto =
michael@0 3082 static_cast<nsXULPrototypePI*>(childproto);
michael@0 3083
michael@0 3084 // <?xul-overlay?> and <?xml-stylesheet?> don't have effect
michael@0 3085 // outside the prolog, like they used to. Issue a warning.
michael@0 3086
michael@0 3087 if (piProto->mTarget.EqualsLiteral("xml-stylesheet") ||
michael@0 3088 piProto->mTarget.EqualsLiteral("xul-overlay")) {
michael@0 3089
michael@0 3090 const char16_t* params[] = { piProto->mTarget.get() };
michael@0 3091
michael@0 3092 nsContentUtils::ReportToConsole(
michael@0 3093 nsIScriptError::warningFlag,
michael@0 3094 NS_LITERAL_CSTRING("XUL Document"), nullptr,
michael@0 3095 nsContentUtils::eXUL_PROPERTIES,
michael@0 3096 "PINotInProlog",
michael@0 3097 params, ArrayLength(params),
michael@0 3098 overlayURI);
michael@0 3099 }
michael@0 3100
michael@0 3101 nsIContent* parent = processingOverlayHookupNodes ?
michael@0 3102 GetRootElement() : element.get();
michael@0 3103
michael@0 3104 if (parent) {
michael@0 3105 // an inline script could have removed the root element
michael@0 3106 rv = CreateAndInsertPI(piProto, parent,
michael@0 3107 parent->GetChildCount());
michael@0 3108 NS_ENSURE_SUCCESS(rv, rv);
michael@0 3109 }
michael@0 3110 }
michael@0 3111 break;
michael@0 3112
michael@0 3113 default:
michael@0 3114 NS_NOTREACHED("Unexpected nsXULPrototypeNode::Type value");
michael@0 3115 }
michael@0 3116 }
michael@0 3117
michael@0 3118 // Once we get here, the context stack will have been
michael@0 3119 // depleted. That means that the entire prototype has been
michael@0 3120 // walked and content has been constructed.
michael@0 3121
michael@0 3122 // If we're not already, mark us as now processing overlays.
michael@0 3123 mState = eState_Overlay;
michael@0 3124
michael@0 3125 // If there are no overlay URIs, then we're done.
michael@0 3126 uint32_t count = mUnloadedOverlays.Length();
michael@0 3127 if (! count)
michael@0 3128 break;
michael@0 3129
michael@0 3130 nsCOMPtr<nsIURI> uri = mUnloadedOverlays[count-1];
michael@0 3131 mUnloadedOverlays.RemoveElementAt(count - 1);
michael@0 3132
michael@0 3133 bool shouldReturn, failureFromContent;
michael@0 3134 rv = LoadOverlayInternal(uri, false, &shouldReturn,
michael@0 3135 &failureFromContent);
michael@0 3136 if (failureFromContent)
michael@0 3137 // The failure |rv| was the result of a problem in the content
michael@0 3138 // rather than an unexpected problem in our implementation, so
michael@0 3139 // just continue with the next overlay.
michael@0 3140 continue;
michael@0 3141 if (NS_FAILED(rv))
michael@0 3142 return rv;
michael@0 3143 if (mOverlayLoadObservers) {
michael@0 3144 nsIObserver *obs = mOverlayLoadObservers->GetWeak(overlayURI);
michael@0 3145 if (obs) {
michael@0 3146 // This overlay has an unloaded overlay, so it will never
michael@0 3147 // notify. The best we can do is to notify for the unloaded
michael@0 3148 // overlay instead, assuming nobody is already notifiable
michael@0 3149 // for it. Note that this will confuse the observer.
michael@0 3150 if (!mOverlayLoadObservers->GetWeak(uri))
michael@0 3151 mOverlayLoadObservers->Put(uri, obs);
michael@0 3152 mOverlayLoadObservers->Remove(overlayURI);
michael@0 3153 }
michael@0 3154 }
michael@0 3155 if (shouldReturn)
michael@0 3156 return NS_OK;
michael@0 3157 overlayURI.swap(uri);
michael@0 3158 }
michael@0 3159
michael@0 3160 // If we get here, there is nothing left for us to walk. The content
michael@0 3161 // model is built and ready for layout.
michael@0 3162 rv = ResolveForwardReferences();
michael@0 3163 if (NS_FAILED(rv)) return rv;
michael@0 3164
michael@0 3165 ApplyPersistentAttributes();
michael@0 3166
michael@0 3167 mStillWalking = false;
michael@0 3168 if (mPendingSheets == 0) {
michael@0 3169 rv = DoneWalking();
michael@0 3170 }
michael@0 3171 return rv;
michael@0 3172 }
michael@0 3173
michael@0 3174 nsresult
michael@0 3175 XULDocument::DoneWalking()
michael@0 3176 {
michael@0 3177 NS_PRECONDITION(mPendingSheets == 0, "there are sheets to be loaded");
michael@0 3178 NS_PRECONDITION(!mStillWalking, "walk not done");
michael@0 3179
michael@0 3180 // XXXldb This is where we should really be setting the chromehidden
michael@0 3181 // attribute.
michael@0 3182
michael@0 3183 uint32_t count = mOverlaySheets.Length();
michael@0 3184 for (uint32_t i = 0; i < count; ++i) {
michael@0 3185 AddStyleSheet(mOverlaySheets[i]);
michael@0 3186 }
michael@0 3187 mOverlaySheets.Clear();
michael@0 3188
michael@0 3189 if (!mDocumentLoaded) {
michael@0 3190 // Make sure we don't reenter here from StartLayout(). Note that
michael@0 3191 // setting mDocumentLoaded to true here means that if StartLayout()
michael@0 3192 // causes ResumeWalk() to be reentered, we'll take the other branch of
michael@0 3193 // the |if (!mDocumentLoaded)| check above and since
michael@0 3194 // mInitialLayoutComplete will be false will follow the else branch
michael@0 3195 // there too. See the big comment there for how such reentry can
michael@0 3196 // happen.
michael@0 3197 mDocumentLoaded = true;
michael@0 3198
michael@0 3199 NotifyPossibleTitleChange(false);
michael@0 3200
michael@0 3201 // Before starting layout, check whether we're a toplevel chrome
michael@0 3202 // window. If we are, set our chrome flags now, so that we don't have
michael@0 3203 // to restyle the whole frame tree after StartLayout.
michael@0 3204 nsCOMPtr<nsIDocShellTreeItem> item = GetDocShell();
michael@0 3205 if (item) {
michael@0 3206 nsCOMPtr<nsIDocShellTreeOwner> owner;
michael@0 3207 item->GetTreeOwner(getter_AddRefs(owner));
michael@0 3208 nsCOMPtr<nsIXULWindow> xulWin = do_GetInterface(owner);
michael@0 3209 if (xulWin) {
michael@0 3210 nsCOMPtr<nsIDocShell> xulWinShell;
michael@0 3211 xulWin->GetDocShell(getter_AddRefs(xulWinShell));
michael@0 3212 if (SameCOMIdentity(xulWinShell, item)) {
michael@0 3213 // We're the chrome document! Apply our chrome flags now.
michael@0 3214 xulWin->ApplyChromeFlags();
michael@0 3215 }
michael@0 3216 }
michael@0 3217 }
michael@0 3218
michael@0 3219 StartLayout();
michael@0 3220
michael@0 3221 if (mIsWritingFastLoad && IsChromeURI(mDocumentURI))
michael@0 3222 nsXULPrototypeCache::GetInstance()->WritePrototype(mMasterPrototype);
michael@0 3223
michael@0 3224 NS_ASSERTION(mDelayFrameLoaderInitialization,
michael@0 3225 "mDelayFrameLoaderInitialization should be true!");
michael@0 3226 mDelayFrameLoaderInitialization = false;
michael@0 3227 NS_WARN_IF_FALSE(mUpdateNestLevel == 0,
michael@0 3228 "Constructing XUL document in middle of an update?");
michael@0 3229 if (mUpdateNestLevel == 0) {
michael@0 3230 MaybeInitializeFinalizeFrameLoaders();
michael@0 3231 }
michael@0 3232
michael@0 3233 NS_DOCUMENT_NOTIFY_OBSERVERS(EndLoad, (this));
michael@0 3234
michael@0 3235 // DispatchContentLoadedEvents undoes the onload-blocking we
michael@0 3236 // did in PrepareToWalk().
michael@0 3237 DispatchContentLoadedEvents();
michael@0 3238
michael@0 3239 mInitialLayoutComplete = true;
michael@0 3240
michael@0 3241 // Walk the set of pending load notifications and notify any observers.
michael@0 3242 // See below for detail.
michael@0 3243 if (mPendingOverlayLoadNotifications)
michael@0 3244 mPendingOverlayLoadNotifications->Enumerate(
michael@0 3245 FirePendingMergeNotification, mOverlayLoadObservers.get());
michael@0 3246 }
michael@0 3247 else {
michael@0 3248 if (mOverlayLoadObservers) {
michael@0 3249 nsCOMPtr<nsIURI> overlayURI = mCurrentPrototype->GetURI();
michael@0 3250 nsCOMPtr<nsIObserver> obs;
michael@0 3251 if (mInitialLayoutComplete) {
michael@0 3252 // We have completed initial layout, so just send the notification.
michael@0 3253 mOverlayLoadObservers->Get(overlayURI, getter_AddRefs(obs));
michael@0 3254 if (obs)
michael@0 3255 obs->Observe(overlayURI, "xul-overlay-merged", EmptyString().get());
michael@0 3256 mOverlayLoadObservers->Remove(overlayURI);
michael@0 3257 }
michael@0 3258 else {
michael@0 3259 // If we have not yet displayed the document for the first time
michael@0 3260 // (i.e. we came in here as the result of a dynamic overlay load
michael@0 3261 // which was spawned by a binding-attached event caused by
michael@0 3262 // StartLayout() on the master prototype - we must remember that
michael@0 3263 // this overlay has been merged and tell the listeners after
michael@0 3264 // StartLayout() is completely finished rather than doing so
michael@0 3265 // immediately - otherwise we may be executing code that needs to
michael@0 3266 // access XBL Binding implementations on nodes for which frames
michael@0 3267 // have not yet been constructed because their bindings have not
michael@0 3268 // yet been attached. This can be a race condition because dynamic
michael@0 3269 // overlay loading can take varying amounts of time depending on
michael@0 3270 // whether or not the overlay prototype is in the XUL cache. The
michael@0 3271 // most likely effect of this bug is odd UI initialization due to
michael@0 3272 // methods and properties that do not work.
michael@0 3273 // XXXbz really, we shouldn't be firing binding constructors
michael@0 3274 // until after StartLayout returns!
michael@0 3275
michael@0 3276 if (!mPendingOverlayLoadNotifications) {
michael@0 3277 mPendingOverlayLoadNotifications =
michael@0 3278 new nsInterfaceHashtable<nsURIHashKey,nsIObserver>;
michael@0 3279 }
michael@0 3280
michael@0 3281 mPendingOverlayLoadNotifications->Get(overlayURI, getter_AddRefs(obs));
michael@0 3282 if (!obs) {
michael@0 3283 mOverlayLoadObservers->Get(overlayURI, getter_AddRefs(obs));
michael@0 3284 NS_ASSERTION(obs, "null overlay load observer?");
michael@0 3285 mPendingOverlayLoadNotifications->Put(overlayURI, obs);
michael@0 3286 }
michael@0 3287 }
michael@0 3288 }
michael@0 3289 }
michael@0 3290
michael@0 3291 return NS_OK;
michael@0 3292 }
michael@0 3293
michael@0 3294 NS_IMETHODIMP
michael@0 3295 XULDocument::StyleSheetLoaded(nsCSSStyleSheet* aSheet,
michael@0 3296 bool aWasAlternate,
michael@0 3297 nsresult aStatus)
michael@0 3298 {
michael@0 3299 if (!aWasAlternate) {
michael@0 3300 // Don't care about when alternate sheets finish loading
michael@0 3301
michael@0 3302 NS_ASSERTION(mPendingSheets > 0,
michael@0 3303 "Unexpected StyleSheetLoaded notification");
michael@0 3304
michael@0 3305 --mPendingSheets;
michael@0 3306
michael@0 3307 if (!mStillWalking && mPendingSheets == 0) {
michael@0 3308 return DoneWalking();
michael@0 3309 }
michael@0 3310 }
michael@0 3311
michael@0 3312 return NS_OK;
michael@0 3313 }
michael@0 3314
michael@0 3315 void
michael@0 3316 XULDocument::MaybeBroadcast()
michael@0 3317 {
michael@0 3318 // Only broadcast when not in an update and when safe to run scripts.
michael@0 3319 if (mUpdateNestLevel == 0 &&
michael@0 3320 (mDelayedAttrChangeBroadcasts.Length() ||
michael@0 3321 mDelayedBroadcasters.Length())) {
michael@0 3322 if (!nsContentUtils::IsSafeToRunScript()) {
michael@0 3323 if (!mInDestructor) {
michael@0 3324 nsContentUtils::AddScriptRunner(
michael@0 3325 NS_NewRunnableMethod(this, &XULDocument::MaybeBroadcast));
michael@0 3326 }
michael@0 3327 return;
michael@0 3328 }
michael@0 3329 if (!mHandlingDelayedAttrChange) {
michael@0 3330 mHandlingDelayedAttrChange = true;
michael@0 3331 for (uint32_t i = 0; i < mDelayedAttrChangeBroadcasts.Length(); ++i) {
michael@0 3332 nsIAtom* attrName = mDelayedAttrChangeBroadcasts[i].mAttrName;
michael@0 3333 if (mDelayedAttrChangeBroadcasts[i].mNeedsAttrChange) {
michael@0 3334 nsCOMPtr<nsIContent> listener =
michael@0 3335 do_QueryInterface(mDelayedAttrChangeBroadcasts[i].mListener);
michael@0 3336 nsString value = mDelayedAttrChangeBroadcasts[i].mAttr;
michael@0 3337 if (mDelayedAttrChangeBroadcasts[i].mSetAttr) {
michael@0 3338 listener->SetAttr(kNameSpaceID_None, attrName, value,
michael@0 3339 true);
michael@0 3340 } else {
michael@0 3341 listener->UnsetAttr(kNameSpaceID_None, attrName,
michael@0 3342 true);
michael@0 3343 }
michael@0 3344 }
michael@0 3345 ExecuteOnBroadcastHandlerFor(mDelayedAttrChangeBroadcasts[i].mBroadcaster,
michael@0 3346 mDelayedAttrChangeBroadcasts[i].mListener,
michael@0 3347 attrName);
michael@0 3348 }
michael@0 3349 mDelayedAttrChangeBroadcasts.Clear();
michael@0 3350 mHandlingDelayedAttrChange = false;
michael@0 3351 }
michael@0 3352
michael@0 3353 uint32_t length = mDelayedBroadcasters.Length();
michael@0 3354 if (length) {
michael@0 3355 bool oldValue = mHandlingDelayedBroadcasters;
michael@0 3356 mHandlingDelayedBroadcasters = true;
michael@0 3357 nsTArray<nsDelayedBroadcastUpdate> delayedBroadcasters;
michael@0 3358 mDelayedBroadcasters.SwapElements(delayedBroadcasters);
michael@0 3359 for (uint32_t i = 0; i < length; ++i) {
michael@0 3360 SynchronizeBroadcastListener(delayedBroadcasters[i].mBroadcaster,
michael@0 3361 delayedBroadcasters[i].mListener,
michael@0 3362 delayedBroadcasters[i].mAttr);
michael@0 3363 }
michael@0 3364 mHandlingDelayedBroadcasters = oldValue;
michael@0 3365 }
michael@0 3366 }
michael@0 3367 }
michael@0 3368
michael@0 3369 void
michael@0 3370 XULDocument::EndUpdate(nsUpdateType aUpdateType)
michael@0 3371 {
michael@0 3372 XMLDocument::EndUpdate(aUpdateType);
michael@0 3373
michael@0 3374 MaybeBroadcast();
michael@0 3375 }
michael@0 3376
michael@0 3377 void
michael@0 3378 XULDocument::ReportMissingOverlay(nsIURI* aURI)
michael@0 3379 {
michael@0 3380 NS_PRECONDITION(aURI, "Must have a URI");
michael@0 3381
michael@0 3382 nsAutoCString spec;
michael@0 3383 aURI->GetSpec(spec);
michael@0 3384
michael@0 3385 NS_ConvertUTF8toUTF16 utfSpec(spec);
michael@0 3386 const char16_t* params[] = { utfSpec.get() };
michael@0 3387 nsContentUtils::ReportToConsole(nsIScriptError::warningFlag,
michael@0 3388 NS_LITERAL_CSTRING("XUL Document"), this,
michael@0 3389 nsContentUtils::eXUL_PROPERTIES,
michael@0 3390 "MissingOverlay",
michael@0 3391 params, ArrayLength(params));
michael@0 3392 }
michael@0 3393
michael@0 3394 nsresult
michael@0 3395 XULDocument::LoadScript(nsXULPrototypeScript* aScriptProto, bool* aBlock)
michael@0 3396 {
michael@0 3397 // Load a transcluded script
michael@0 3398 nsresult rv;
michael@0 3399
michael@0 3400 bool isChromeDoc = IsChromeURI(mDocumentURI);
michael@0 3401
michael@0 3402 if (isChromeDoc && aScriptProto->GetScriptObject()) {
michael@0 3403 rv = ExecuteScript(aScriptProto);
michael@0 3404
michael@0 3405 // Ignore return value from execution, and don't block
michael@0 3406 *aBlock = false;
michael@0 3407 return NS_OK;
michael@0 3408 }
michael@0 3409
michael@0 3410 // Try the XUL script cache, in case two XUL documents source the same
michael@0 3411 // .js file (e.g., strres.js from navigator.xul and utilityOverlay.xul).
michael@0 3412 // XXXbe the cache relies on aScriptProto's GC root!
michael@0 3413 bool useXULCache = nsXULPrototypeCache::GetInstance()->IsEnabled();
michael@0 3414
michael@0 3415 if (isChromeDoc && useXULCache) {
michael@0 3416 JSScript* newScriptObject =
michael@0 3417 nsXULPrototypeCache::GetInstance()->GetScript(
michael@0 3418 aScriptProto->mSrcURI);
michael@0 3419 if (newScriptObject) {
michael@0 3420 // The script language for a proto must remain constant - we
michael@0 3421 // can't just change it for this unexpected language.
michael@0 3422 aScriptProto->Set(newScriptObject);
michael@0 3423 }
michael@0 3424
michael@0 3425 if (aScriptProto->GetScriptObject()) {
michael@0 3426 rv = ExecuteScript(aScriptProto);
michael@0 3427
michael@0 3428 // Ignore return value from execution, and don't block
michael@0 3429 *aBlock = false;
michael@0 3430 return NS_OK;
michael@0 3431 }
michael@0 3432 }
michael@0 3433
michael@0 3434 // Allow security manager and content policies to veto the load. Note that
michael@0 3435 // at this point we already lost context information of the script.
michael@0 3436 rv = nsScriptLoader::ShouldLoadScript(
michael@0 3437 this,
michael@0 3438 static_cast<nsIDocument*>(this),
michael@0 3439 aScriptProto->mSrcURI,
michael@0 3440 NS_LITERAL_STRING("application/x-javascript"));
michael@0 3441 if (NS_FAILED(rv)) {
michael@0 3442 *aBlock = false;
michael@0 3443 return rv;
michael@0 3444 }
michael@0 3445
michael@0 3446 // Release script objects from FastLoad since we decided against using them
michael@0 3447 aScriptProto->UnlinkJSObjects();
michael@0 3448
michael@0 3449 // Set the current script prototype so that OnStreamComplete can report
michael@0 3450 // the right file if there are errors in the script.
michael@0 3451 NS_ASSERTION(!mCurrentScriptProto,
michael@0 3452 "still loading a script when starting another load?");
michael@0 3453 mCurrentScriptProto = aScriptProto;
michael@0 3454
michael@0 3455 if (aScriptProto->mSrcLoading) {
michael@0 3456 // Another XULDocument load has started, which is still in progress.
michael@0 3457 // Remember to ResumeWalk this document when the load completes.
michael@0 3458 mNextSrcLoadWaiter = aScriptProto->mSrcLoadWaiters;
michael@0 3459 aScriptProto->mSrcLoadWaiters = this;
michael@0 3460 NS_ADDREF_THIS();
michael@0 3461 }
michael@0 3462 else {
michael@0 3463 nsCOMPtr<nsILoadGroup> group = do_QueryReferent(mDocumentLoadGroup);
michael@0 3464
michael@0 3465 // Note: the loader will keep itself alive while it's loading.
michael@0 3466 nsCOMPtr<nsIStreamLoader> loader;
michael@0 3467 rv = NS_NewStreamLoader(getter_AddRefs(loader), aScriptProto->mSrcURI,
michael@0 3468 this, nullptr, group);
michael@0 3469 if (NS_FAILED(rv)) {
michael@0 3470 mCurrentScriptProto = nullptr;
michael@0 3471 return rv;
michael@0 3472 }
michael@0 3473
michael@0 3474 aScriptProto->mSrcLoading = true;
michael@0 3475 }
michael@0 3476
michael@0 3477 // Block until OnStreamComplete resumes us.
michael@0 3478 *aBlock = true;
michael@0 3479 return NS_OK;
michael@0 3480 }
michael@0 3481
michael@0 3482 NS_IMETHODIMP
michael@0 3483 XULDocument::OnStreamComplete(nsIStreamLoader* aLoader,
michael@0 3484 nsISupports* context,
michael@0 3485 nsresult aStatus,
michael@0 3486 uint32_t stringLen,
michael@0 3487 const uint8_t* string)
michael@0 3488 {
michael@0 3489 nsCOMPtr<nsIRequest> request;
michael@0 3490 aLoader->GetRequest(getter_AddRefs(request));
michael@0 3491 nsCOMPtr<nsIChannel> channel = do_QueryInterface(request);
michael@0 3492
michael@0 3493 #ifdef DEBUG
michael@0 3494 // print a load error on bad status
michael@0 3495 if (NS_FAILED(aStatus)) {
michael@0 3496 if (channel) {
michael@0 3497 nsCOMPtr<nsIURI> uri;
michael@0 3498 channel->GetURI(getter_AddRefs(uri));
michael@0 3499 if (uri) {
michael@0 3500 nsAutoCString uriSpec;
michael@0 3501 uri->GetSpec(uriSpec);
michael@0 3502 printf("Failed to load %s\n", uriSpec.get());
michael@0 3503 }
michael@0 3504 }
michael@0 3505 }
michael@0 3506 #endif
michael@0 3507
michael@0 3508 // This is the completion routine that will be called when a
michael@0 3509 // transcluded script completes. Compile and execute the script
michael@0 3510 // if the load was successful, then continue building content
michael@0 3511 // from the prototype.
michael@0 3512 nsresult rv = aStatus;
michael@0 3513
michael@0 3514 NS_ASSERTION(mCurrentScriptProto && mCurrentScriptProto->mSrcLoading,
michael@0 3515 "script source not loading on unichar stream complete?");
michael@0 3516 if (!mCurrentScriptProto) {
michael@0 3517 // XXX Wallpaper for bug 270042
michael@0 3518 return NS_OK;
michael@0 3519 }
michael@0 3520
michael@0 3521 if (NS_SUCCEEDED(aStatus)) {
michael@0 3522 // If the including XUL document is a FastLoad document, and we're
michael@0 3523 // compiling an out-of-line script (one with src=...), then we must
michael@0 3524 // be writing a new FastLoad file. If we were reading this script
michael@0 3525 // from the FastLoad file, XULContentSinkImpl::OpenScript (over in
michael@0 3526 // nsXULContentSink.cpp) would have already deserialized a non-null
michael@0 3527 // script->mScriptObject, causing control flow at the top of LoadScript
michael@0 3528 // not to reach here.
michael@0 3529 nsCOMPtr<nsIURI> uri = mCurrentScriptProto->mSrcURI;
michael@0 3530
michael@0 3531 // XXX should also check nsIHttpChannel::requestSucceeded
michael@0 3532
michael@0 3533 MOZ_ASSERT(!mOffThreadCompiling && (mOffThreadCompileStringLength == 0 &&
michael@0 3534 !mOffThreadCompileStringBuf),
michael@0 3535 "XULDocument can't load multiple scripts at once");
michael@0 3536
michael@0 3537 rv = nsScriptLoader::ConvertToUTF16(channel, string, stringLen,
michael@0 3538 EmptyString(), this,
michael@0 3539 mOffThreadCompileStringBuf,
michael@0 3540 mOffThreadCompileStringLength);
michael@0 3541 if (NS_SUCCEEDED(rv)) {
michael@0 3542 // Attempt to give ownership of the buffer to the JS engine. If
michael@0 3543 // we hit offthread compilation, however, we will have to take it
michael@0 3544 // back below in order to keep the memory alive until compilation
michael@0 3545 // completes.
michael@0 3546 JS::SourceBufferHolder srcBuf(mOffThreadCompileStringBuf,
michael@0 3547 mOffThreadCompileStringLength,
michael@0 3548 JS::SourceBufferHolder::GiveOwnership);
michael@0 3549 mOffThreadCompileStringBuf = nullptr;
michael@0 3550 mOffThreadCompileStringLength = 0;
michael@0 3551
michael@0 3552 rv = mCurrentScriptProto->Compile(srcBuf,
michael@0 3553 uri, 1, this,
michael@0 3554 mMasterPrototype,
michael@0 3555 this);
michael@0 3556 if (NS_SUCCEEDED(rv) && !mCurrentScriptProto->GetScriptObject()) {
michael@0 3557 // We will be notified via OnOffThreadCompileComplete when the
michael@0 3558 // compile finishes. Keep the contents of the compiled script
michael@0 3559 // alive until the compilation finishes.
michael@0 3560 mOffThreadCompiling = true;
michael@0 3561 // If the JS engine did not take the source buffer, then take
michael@0 3562 // it back here to ensure it remains alive.
michael@0 3563 mOffThreadCompileStringBuf = srcBuf.take();
michael@0 3564 if (mOffThreadCompileStringBuf) {
michael@0 3565 mOffThreadCompileStringLength = srcBuf.length();
michael@0 3566 }
michael@0 3567 BlockOnload();
michael@0 3568 return NS_OK;
michael@0 3569 }
michael@0 3570 }
michael@0 3571 }
michael@0 3572
michael@0 3573 return OnScriptCompileComplete(mCurrentScriptProto->GetScriptObject(), rv);
michael@0 3574 }
michael@0 3575
michael@0 3576 NS_IMETHODIMP
michael@0 3577 XULDocument::OnScriptCompileComplete(JSScript* aScript, nsresult aStatus)
michael@0 3578 {
michael@0 3579 // When compiling off thread the script will not have been attached to the
michael@0 3580 // script proto yet.
michael@0 3581 if (aScript && !mCurrentScriptProto->GetScriptObject())
michael@0 3582 mCurrentScriptProto->Set(aScript);
michael@0 3583
michael@0 3584 // Allow load events to be fired once off thread compilation finishes.
michael@0 3585 if (mOffThreadCompiling) {
michael@0 3586 mOffThreadCompiling = false;
michael@0 3587 UnblockOnload(false);
michael@0 3588 }
michael@0 3589
michael@0 3590 // After compilation finishes the script's characters are no longer needed.
michael@0 3591 if (mOffThreadCompileStringBuf) {
michael@0 3592 js_free(mOffThreadCompileStringBuf);
michael@0 3593 mOffThreadCompileStringBuf = nullptr;
michael@0 3594 mOffThreadCompileStringLength = 0;
michael@0 3595 }
michael@0 3596
michael@0 3597 // Clear mCurrentScriptProto now, but save it first for use below in
michael@0 3598 // the execute code, and in the while loop that resumes walks of other
michael@0 3599 // documents that raced to load this script.
michael@0 3600 nsXULPrototypeScript* scriptProto = mCurrentScriptProto;
michael@0 3601 mCurrentScriptProto = nullptr;
michael@0 3602
michael@0 3603 // Clear the prototype's loading flag before executing the script or
michael@0 3604 // resuming document walks, in case any of those control flows starts a
michael@0 3605 // new script load.
michael@0 3606 scriptProto->mSrcLoading = false;
michael@0 3607
michael@0 3608 nsresult rv = aStatus;
michael@0 3609 if (NS_SUCCEEDED(rv)) {
michael@0 3610 rv = ExecuteScript(scriptProto);
michael@0 3611
michael@0 3612 // If the XUL cache is enabled, save the script object there in
michael@0 3613 // case different XUL documents source the same script.
michael@0 3614 //
michael@0 3615 // But don't save the script in the cache unless the master XUL
michael@0 3616 // document URL is a chrome: URL. It is valid for a URL such as
michael@0 3617 // about:config to translate into a master document URL, whose
michael@0 3618 // prototype document nodes -- including prototype scripts that
michael@0 3619 // hold GC roots protecting their mJSObject pointers -- are not
michael@0 3620 // cached in the XUL prototype cache. See StartDocumentLoad,
michael@0 3621 // the fillXULCache logic.
michael@0 3622 //
michael@0 3623 // A document such as about:config is free to load a script via
michael@0 3624 // a URL such as chrome://global/content/config.js, and we must
michael@0 3625 // not cache that script object without a prototype cache entry
michael@0 3626 // containing a companion nsXULPrototypeScript node that owns a
michael@0 3627 // GC root protecting the script object. Otherwise, the script
michael@0 3628 // cache entry will dangle once the uncached prototype document
michael@0 3629 // is released when its owning XULDocument is unloaded.
michael@0 3630 //
michael@0 3631 // (See http://bugzilla.mozilla.org/show_bug.cgi?id=98207 for
michael@0 3632 // the true crime story.)
michael@0 3633 bool useXULCache = nsXULPrototypeCache::GetInstance()->IsEnabled();
michael@0 3634
michael@0 3635 if (useXULCache && IsChromeURI(mDocumentURI) && scriptProto->GetScriptObject()) {
michael@0 3636 nsXULPrototypeCache::GetInstance()->PutScript(
michael@0 3637 scriptProto->mSrcURI,
michael@0 3638 scriptProto->GetScriptObject());
michael@0 3639 }
michael@0 3640
michael@0 3641 if (mIsWritingFastLoad && mCurrentPrototype != mMasterPrototype) {
michael@0 3642 // If we are loading an overlay script, try to serialize
michael@0 3643 // it to the FastLoad file here. Master scripts will be
michael@0 3644 // serialized when the master prototype document gets
michael@0 3645 // written, at the bottom of ResumeWalk. That way, master
michael@0 3646 // out-of-line scripts are serialized in the same order that
michael@0 3647 // they'll be read, in the FastLoad file, which reduces the
michael@0 3648 // number of seeks that dump the underlying stream's buffer.
michael@0 3649 //
michael@0 3650 // Ignore the return value, as we don't need to propagate
michael@0 3651 // a failure to write to the FastLoad file, because this
michael@0 3652 // method aborts that whole process on error.
michael@0 3653 scriptProto->SerializeOutOfLine(nullptr, mCurrentPrototype);
michael@0 3654 }
michael@0 3655 // ignore any evaluation errors
michael@0 3656 }
michael@0 3657
michael@0 3658 rv = ResumeWalk();
michael@0 3659
michael@0 3660 // Load a pointer to the prototype-script's list of XULDocuments who
michael@0 3661 // raced to load the same script
michael@0 3662 XULDocument** docp = &scriptProto->mSrcLoadWaiters;
michael@0 3663
michael@0 3664 // Resume walking other documents that waited for this one's load, first
michael@0 3665 // executing the script we just compiled, in each doc's script context
michael@0 3666 XULDocument* doc;
michael@0 3667 while ((doc = *docp) != nullptr) {
michael@0 3668 NS_ASSERTION(doc->mCurrentScriptProto == scriptProto,
michael@0 3669 "waiting for wrong script to load?");
michael@0 3670 doc->mCurrentScriptProto = nullptr;
michael@0 3671
michael@0 3672 // Unlink doc from scriptProto's list before executing and resuming
michael@0 3673 *docp = doc->mNextSrcLoadWaiter;
michael@0 3674 doc->mNextSrcLoadWaiter = nullptr;
michael@0 3675
michael@0 3676 // Execute only if we loaded and compiled successfully, then resume
michael@0 3677 if (NS_SUCCEEDED(aStatus) && scriptProto->GetScriptObject()) {
michael@0 3678 doc->ExecuteScript(scriptProto);
michael@0 3679 }
michael@0 3680 doc->ResumeWalk();
michael@0 3681 NS_RELEASE(doc);
michael@0 3682 }
michael@0 3683
michael@0 3684 return rv;
michael@0 3685 }
michael@0 3686
michael@0 3687 nsresult
michael@0 3688 XULDocument::ExecuteScript(nsIScriptContext * aContext,
michael@0 3689 JS::Handle<JSScript*> aScriptObject)
michael@0 3690 {
michael@0 3691 NS_PRECONDITION(aScriptObject != nullptr && aContext != nullptr, "null ptr");
michael@0 3692 if (! aScriptObject || ! aContext)
michael@0 3693 return NS_ERROR_NULL_POINTER;
michael@0 3694
michael@0 3695 NS_ENSURE_TRUE(mScriptGlobalObject, NS_ERROR_NOT_INITIALIZED);
michael@0 3696
michael@0 3697 // Execute the precompiled script with the given version
michael@0 3698 nsAutoMicroTask mt;
michael@0 3699 JSContext *cx = aContext->GetNativeContext();
michael@0 3700 AutoCxPusher pusher(cx);
michael@0 3701 JS::Rooted<JSObject*> global(cx, mScriptGlobalObject->GetGlobalJSObject());
michael@0 3702 NS_ENSURE_TRUE(global, NS_ERROR_FAILURE);
michael@0 3703 NS_ENSURE_TRUE(nsContentUtils::GetSecurityManager()->ScriptAllowed(global), NS_OK);
michael@0 3704 JS::ExposeObjectToActiveJS(global);
michael@0 3705 xpc_UnmarkGrayScript(aScriptObject);
michael@0 3706 JSAutoCompartment ac(cx, global);
michael@0 3707
michael@0 3708 // The script is in the compilation scope. Clone it into the target scope
michael@0 3709 // and execute it.
michael@0 3710 if (!JS::CloneAndExecuteScript(cx, global, aScriptObject))
michael@0 3711 nsJSUtils::ReportPendingException(cx);
michael@0 3712 return NS_OK;
michael@0 3713 }
michael@0 3714
michael@0 3715 nsresult
michael@0 3716 XULDocument::ExecuteScript(nsXULPrototypeScript *aScript)
michael@0 3717 {
michael@0 3718 NS_PRECONDITION(aScript != nullptr, "null ptr");
michael@0 3719 NS_ENSURE_TRUE(aScript, NS_ERROR_NULL_POINTER);
michael@0 3720 NS_ENSURE_TRUE(mScriptGlobalObject, NS_ERROR_NOT_INITIALIZED);
michael@0 3721
michael@0 3722 nsresult rv;
michael@0 3723 rv = mScriptGlobalObject->EnsureScriptEnvironment();
michael@0 3724 NS_ENSURE_SUCCESS(rv, rv);
michael@0 3725
michael@0 3726 nsCOMPtr<nsIScriptContext> context =
michael@0 3727 mScriptGlobalObject->GetScriptContext();
michael@0 3728 // failure getting a script context is fatal.
michael@0 3729 NS_ENSURE_TRUE(context != nullptr, NS_ERROR_UNEXPECTED);
michael@0 3730
michael@0 3731 if (aScript->GetScriptObject())
michael@0 3732 rv = ExecuteScript(context, aScript->GetScriptObject());
michael@0 3733 else
michael@0 3734 rv = NS_ERROR_UNEXPECTED;
michael@0 3735 return rv;
michael@0 3736 }
michael@0 3737
michael@0 3738
michael@0 3739 nsresult
michael@0 3740 XULDocument::CreateElementFromPrototype(nsXULPrototypeElement* aPrototype,
michael@0 3741 Element** aResult,
michael@0 3742 bool aIsRoot)
michael@0 3743 {
michael@0 3744 // Create a content model element from a prototype element.
michael@0 3745 NS_PRECONDITION(aPrototype != nullptr, "null ptr");
michael@0 3746 if (! aPrototype)
michael@0 3747 return NS_ERROR_NULL_POINTER;
michael@0 3748
michael@0 3749 *aResult = nullptr;
michael@0 3750 nsresult rv = NS_OK;
michael@0 3751
michael@0 3752 #ifdef PR_LOGGING
michael@0 3753 if (PR_LOG_TEST(gXULLog, PR_LOG_NOTICE)) {
michael@0 3754 PR_LOG(gXULLog, PR_LOG_NOTICE,
michael@0 3755 ("xul: creating <%s> from prototype",
michael@0 3756 NS_ConvertUTF16toUTF8(aPrototype->mNodeInfo->QualifiedName()).get()));
michael@0 3757 }
michael@0 3758 #endif
michael@0 3759
michael@0 3760 nsRefPtr<Element> result;
michael@0 3761
michael@0 3762 if (aPrototype->mNodeInfo->NamespaceEquals(kNameSpaceID_XUL)) {
michael@0 3763 // If it's a XUL element, it'll be lightweight until somebody
michael@0 3764 // monkeys with it.
michael@0 3765 rv = nsXULElement::Create(aPrototype, this, true, aIsRoot, getter_AddRefs(result));
michael@0 3766 if (NS_FAILED(rv)) return rv;
michael@0 3767 }
michael@0 3768 else {
michael@0 3769 // If it's not a XUL element, it's gonna be heavyweight no matter
michael@0 3770 // what. So we need to copy everything out of the prototype
michael@0 3771 // into the element. Get a nodeinfo from our nodeinfo manager
michael@0 3772 // for this node.
michael@0 3773 nsCOMPtr<nsINodeInfo> newNodeInfo;
michael@0 3774 newNodeInfo = mNodeInfoManager->GetNodeInfo(aPrototype->mNodeInfo->NameAtom(),
michael@0 3775 aPrototype->mNodeInfo->GetPrefixAtom(),
michael@0 3776 aPrototype->mNodeInfo->NamespaceID(),
michael@0 3777 nsIDOMNode::ELEMENT_NODE);
michael@0 3778 if (!newNodeInfo) return NS_ERROR_OUT_OF_MEMORY;
michael@0 3779 nsCOMPtr<nsINodeInfo> xtfNi = newNodeInfo;
michael@0 3780 rv = NS_NewElement(getter_AddRefs(result), newNodeInfo.forget(),
michael@0 3781 NOT_FROM_PARSER);
michael@0 3782 if (NS_FAILED(rv))
michael@0 3783 return rv;
michael@0 3784
michael@0 3785 rv = AddAttributes(aPrototype, result);
michael@0 3786 if (NS_FAILED(rv)) return rv;
michael@0 3787 }
michael@0 3788
michael@0 3789 result.swap(*aResult);
michael@0 3790
michael@0 3791 return NS_OK;
michael@0 3792 }
michael@0 3793
michael@0 3794 nsresult
michael@0 3795 XULDocument::CreateOverlayElement(nsXULPrototypeElement* aPrototype,
michael@0 3796 Element** aResult)
michael@0 3797 {
michael@0 3798 nsresult rv;
michael@0 3799
michael@0 3800 nsRefPtr<Element> element;
michael@0 3801 rv = CreateElementFromPrototype(aPrototype, getter_AddRefs(element), false);
michael@0 3802 if (NS_FAILED(rv)) return rv;
michael@0 3803
michael@0 3804 OverlayForwardReference* fwdref =
michael@0 3805 new OverlayForwardReference(this, element);
michael@0 3806 if (! fwdref)
michael@0 3807 return NS_ERROR_OUT_OF_MEMORY;
michael@0 3808
michael@0 3809 // transferring ownership to ya...
michael@0 3810 rv = AddForwardReference(fwdref);
michael@0 3811 if (NS_FAILED(rv)) return rv;
michael@0 3812
michael@0 3813 NS_ADDREF(*aResult = element);
michael@0 3814 return NS_OK;
michael@0 3815 }
michael@0 3816
michael@0 3817 nsresult
michael@0 3818 XULDocument::AddAttributes(nsXULPrototypeElement* aPrototype,
michael@0 3819 nsIContent* aElement)
michael@0 3820 {
michael@0 3821 nsresult rv;
michael@0 3822
michael@0 3823 for (uint32_t i = 0; i < aPrototype->mNumAttributes; ++i) {
michael@0 3824 nsXULPrototypeAttribute* protoattr = &(aPrototype->mAttributes[i]);
michael@0 3825 nsAutoString valueStr;
michael@0 3826 protoattr->mValue.ToString(valueStr);
michael@0 3827
michael@0 3828 rv = aElement->SetAttr(protoattr->mName.NamespaceID(),
michael@0 3829 protoattr->mName.LocalName(),
michael@0 3830 protoattr->mName.GetPrefix(),
michael@0 3831 valueStr,
michael@0 3832 false);
michael@0 3833 if (NS_FAILED(rv)) return rv;
michael@0 3834 }
michael@0 3835
michael@0 3836 return NS_OK;
michael@0 3837 }
michael@0 3838
michael@0 3839
michael@0 3840 nsresult
michael@0 3841 XULDocument::CheckTemplateBuilderHookup(nsIContent* aElement,
michael@0 3842 bool* aNeedsHookup)
michael@0 3843 {
michael@0 3844 // See if the element already has a `database' attribute. If it
michael@0 3845 // does, then the template builder has already been created.
michael@0 3846 //
michael@0 3847 // XXX This approach will crash and burn (well, maybe not _that_
michael@0 3848 // bad) if aElement is not a XUL element.
michael@0 3849 //
michael@0 3850 // XXXvarga Do we still want to support non XUL content?
michael@0 3851 nsCOMPtr<nsIDOMXULElement> xulElement = do_QueryInterface(aElement);
michael@0 3852 if (xulElement) {
michael@0 3853 nsCOMPtr<nsIRDFCompositeDataSource> ds;
michael@0 3854 xulElement->GetDatabase(getter_AddRefs(ds));
michael@0 3855 if (ds) {
michael@0 3856 *aNeedsHookup = false;
michael@0 3857 return NS_OK;
michael@0 3858 }
michael@0 3859 }
michael@0 3860
michael@0 3861 // Check aElement for a 'datasources' attribute, if it has
michael@0 3862 // one a XUL template builder needs to be hooked up.
michael@0 3863 *aNeedsHookup = aElement->HasAttr(kNameSpaceID_None,
michael@0 3864 nsGkAtoms::datasources);
michael@0 3865 return NS_OK;
michael@0 3866 }
michael@0 3867
michael@0 3868 /* static */ nsresult
michael@0 3869 XULDocument::CreateTemplateBuilder(nsIContent* aElement)
michael@0 3870 {
michael@0 3871 // Check if need to construct a tree builder or content builder.
michael@0 3872 bool isTreeBuilder = false;
michael@0 3873
michael@0 3874 // return successful if the element is not is a document, as an inline
michael@0 3875 // script could have removed it
michael@0 3876 nsIDocument *document = aElement->GetCurrentDoc();
michael@0 3877 NS_ENSURE_TRUE(document, NS_OK);
michael@0 3878
michael@0 3879 int32_t nameSpaceID;
michael@0 3880 nsIAtom* baseTag = document->BindingManager()->
michael@0 3881 ResolveTag(aElement, &nameSpaceID);
michael@0 3882
michael@0 3883 if ((nameSpaceID == kNameSpaceID_XUL) && (baseTag == nsGkAtoms::tree)) {
michael@0 3884 // By default, we build content for a tree and then we attach
michael@0 3885 // the tree content view. However, if the `dont-build-content'
michael@0 3886 // flag is set, then we we'll attach a tree builder which
michael@0 3887 // directly implements the tree view.
michael@0 3888
michael@0 3889 nsAutoString flags;
michael@0 3890 aElement->GetAttr(kNameSpaceID_None, nsGkAtoms::flags, flags);
michael@0 3891 if (flags.Find(NS_LITERAL_STRING("dont-build-content")) >= 0) {
michael@0 3892 isTreeBuilder = true;
michael@0 3893 }
michael@0 3894 }
michael@0 3895
michael@0 3896 if (isTreeBuilder) {
michael@0 3897 // Create and initialize a tree builder.
michael@0 3898 nsCOMPtr<nsIXULTemplateBuilder> builder =
michael@0 3899 do_CreateInstance("@mozilla.org/xul/xul-tree-builder;1");
michael@0 3900
michael@0 3901 if (! builder)
michael@0 3902 return NS_ERROR_FAILURE;
michael@0 3903
michael@0 3904 builder->Init(aElement);
michael@0 3905
michael@0 3906 // Create a <treechildren> if one isn't there already.
michael@0 3907 // XXXvarga what about attributes?
michael@0 3908 nsCOMPtr<nsIContent> bodyContent;
michael@0 3909 nsXULContentUtils::FindChildByTag(aElement, kNameSpaceID_XUL,
michael@0 3910 nsGkAtoms::treechildren,
michael@0 3911 getter_AddRefs(bodyContent));
michael@0 3912
michael@0 3913 if (! bodyContent) {
michael@0 3914 nsresult rv =
michael@0 3915 document->CreateElem(nsDependentAtomString(nsGkAtoms::treechildren),
michael@0 3916 nullptr, kNameSpaceID_XUL,
michael@0 3917 getter_AddRefs(bodyContent));
michael@0 3918 NS_ENSURE_SUCCESS(rv, rv);
michael@0 3919
michael@0 3920 aElement->AppendChildTo(bodyContent, false);
michael@0 3921 }
michael@0 3922 }
michael@0 3923 else {
michael@0 3924 // Create and initialize a content builder.
michael@0 3925 nsCOMPtr<nsIXULTemplateBuilder> builder
michael@0 3926 = do_CreateInstance("@mozilla.org/xul/xul-template-builder;1");
michael@0 3927
michael@0 3928 if (! builder)
michael@0 3929 return NS_ERROR_FAILURE;
michael@0 3930
michael@0 3931 builder->Init(aElement);
michael@0 3932 builder->CreateContents(aElement, false);
michael@0 3933 }
michael@0 3934
michael@0 3935 return NS_OK;
michael@0 3936 }
michael@0 3937
michael@0 3938
michael@0 3939 nsresult
michael@0 3940 XULDocument::AddPrototypeSheets()
michael@0 3941 {
michael@0 3942 nsresult rv;
michael@0 3943
michael@0 3944 const nsCOMArray<nsIURI>& sheets = mCurrentPrototype->GetStyleSheetReferences();
michael@0 3945
michael@0 3946 for (int32_t i = 0; i < sheets.Count(); i++) {
michael@0 3947 nsCOMPtr<nsIURI> uri = sheets[i];
michael@0 3948
michael@0 3949 nsRefPtr<nsCSSStyleSheet> incompleteSheet;
michael@0 3950 rv = CSSLoader()->LoadSheet(uri,
michael@0 3951 mCurrentPrototype->DocumentPrincipal(),
michael@0 3952 EmptyCString(), this,
michael@0 3953 getter_AddRefs(incompleteSheet));
michael@0 3954
michael@0 3955 // XXXldb We need to prevent bogus sheets from being held in the
michael@0 3956 // prototype's list, but until then, don't propagate the failure
michael@0 3957 // from LoadSheet (and thus exit the loop).
michael@0 3958 if (NS_SUCCEEDED(rv)) {
michael@0 3959 ++mPendingSheets;
michael@0 3960 if (!mOverlaySheets.AppendElement(incompleteSheet)) {
michael@0 3961 return NS_ERROR_OUT_OF_MEMORY;
michael@0 3962 }
michael@0 3963 }
michael@0 3964 }
michael@0 3965
michael@0 3966 return NS_OK;
michael@0 3967 }
michael@0 3968
michael@0 3969
michael@0 3970 //----------------------------------------------------------------------
michael@0 3971 //
michael@0 3972 // XULDocument::OverlayForwardReference
michael@0 3973 //
michael@0 3974
michael@0 3975 nsForwardReference::Result
michael@0 3976 XULDocument::OverlayForwardReference::Resolve()
michael@0 3977 {
michael@0 3978 // Resolve a forward reference from an overlay element; attempt to
michael@0 3979 // hook it up into the main document.
michael@0 3980 nsresult rv;
michael@0 3981 nsCOMPtr<nsIContent> target;
michael@0 3982
michael@0 3983 nsIPresShell *shell = mDocument->GetShell();
michael@0 3984 bool notify = shell && shell->DidInitialize();
michael@0 3985
michael@0 3986 nsAutoString id;
michael@0 3987 mOverlay->GetAttr(kNameSpaceID_None, nsGkAtoms::id, id);
michael@0 3988 if (id.IsEmpty()) {
michael@0 3989 // mOverlay is a direct child of <overlay> and has no id.
michael@0 3990 // Insert it under the root element in the base document.
michael@0 3991 Element* root = mDocument->GetRootElement();
michael@0 3992 if (!root) {
michael@0 3993 return eResolve_Error;
michael@0 3994 }
michael@0 3995
michael@0 3996 rv = mDocument->InsertElement(root, mOverlay, notify);
michael@0 3997 if (NS_FAILED(rv)) return eResolve_Error;
michael@0 3998
michael@0 3999 target = mOverlay;
michael@0 4000 }
michael@0 4001 else {
michael@0 4002 // The hook-up element has an id, try to match it with an element
michael@0 4003 // with the same id in the base document.
michael@0 4004 target = mDocument->GetElementById(id);
michael@0 4005
michael@0 4006 // If we can't find the element in the document, defer the hookup
michael@0 4007 // until later.
michael@0 4008 if (!target)
michael@0 4009 return eResolve_Later;
michael@0 4010
michael@0 4011 rv = Merge(target, mOverlay, notify);
michael@0 4012 if (NS_FAILED(rv)) return eResolve_Error;
michael@0 4013 }
michael@0 4014
michael@0 4015 // Check if 'target' is still in our document --- it might not be!
michael@0 4016 if (!notify && target->GetCurrentDoc() == mDocument) {
michael@0 4017 // Add child and any descendants to the element map
michael@0 4018 // XXX this is bogus, the content in 'target' might already be
michael@0 4019 // in the document
michael@0 4020 rv = mDocument->AddSubtreeToDocument(target);
michael@0 4021 if (NS_FAILED(rv)) return eResolve_Error;
michael@0 4022 }
michael@0 4023
michael@0 4024 #ifdef PR_LOGGING
michael@0 4025 if (PR_LOG_TEST(gXULLog, PR_LOG_NOTICE)) {
michael@0 4026 nsAutoCString idC;
michael@0 4027 idC.AssignWithConversion(id);
michael@0 4028 PR_LOG(gXULLog, PR_LOG_NOTICE,
michael@0 4029 ("xul: overlay resolved '%s'",
michael@0 4030 idC.get()));
michael@0 4031 }
michael@0 4032 #endif
michael@0 4033
michael@0 4034 mResolved = true;
michael@0 4035 return eResolve_Succeeded;
michael@0 4036 }
michael@0 4037
michael@0 4038
michael@0 4039
michael@0 4040 nsresult
michael@0 4041 XULDocument::OverlayForwardReference::Merge(nsIContent* aTargetNode,
michael@0 4042 nsIContent* aOverlayNode,
michael@0 4043 bool aNotify)
michael@0 4044 {
michael@0 4045 // This function is given:
michael@0 4046 // aTargetNode: the node in the document whose 'id' attribute
michael@0 4047 // matches a toplevel node in our overlay.
michael@0 4048 // aOverlayNode: the node in the overlay document that matches
michael@0 4049 // a node in the actual document.
michael@0 4050 // aNotify: whether or not content manipulation methods should
michael@0 4051 // use the aNotify parameter. After the initial
michael@0 4052 // reflow (i.e. in the dynamic overlay merge case),
michael@0 4053 // we want all the content manipulation methods we
michael@0 4054 // call to notify so that frames are constructed
michael@0 4055 // etc. Otherwise do not, since that's during initial
michael@0 4056 // document construction before StartLayout has been
michael@0 4057 // called which will do everything for us.
michael@0 4058 //
michael@0 4059 // This function merges the tree from the overlay into the tree in
michael@0 4060 // the document, overwriting attributes and appending child content
michael@0 4061 // nodes appropriately. (See XUL overlay reference for details)
michael@0 4062
michael@0 4063 nsresult rv;
michael@0 4064
michael@0 4065 // Merge attributes from the overlay content node to that of the
michael@0 4066 // actual document.
michael@0 4067 uint32_t i;
michael@0 4068 const nsAttrName* name;
michael@0 4069 for (i = 0; (name = aOverlayNode->GetAttrNameAt(i)); ++i) {
michael@0 4070 // We don't want to swap IDs, they should be the same.
michael@0 4071 if (name->Equals(nsGkAtoms::id))
michael@0 4072 continue;
michael@0 4073
michael@0 4074 // In certain cases merging command or observes is unsafe, so don't.
michael@0 4075 if (!aNotify) {
michael@0 4076 if (aTargetNode->NodeInfo()->Equals(nsGkAtoms::observes,
michael@0 4077 kNameSpaceID_XUL))
michael@0 4078 continue;
michael@0 4079
michael@0 4080 if (name->Equals(nsGkAtoms::observes) &&
michael@0 4081 aTargetNode->HasAttr(kNameSpaceID_None, nsGkAtoms::observes))
michael@0 4082 continue;
michael@0 4083
michael@0 4084 if (name->Equals(nsGkAtoms::command) &&
michael@0 4085 aTargetNode->HasAttr(kNameSpaceID_None, nsGkAtoms::command) &&
michael@0 4086 !aTargetNode->NodeInfo()->Equals(nsGkAtoms::key,
michael@0 4087 kNameSpaceID_XUL) &&
michael@0 4088 !aTargetNode->NodeInfo()->Equals(nsGkAtoms::menuitem,
michael@0 4089 kNameSpaceID_XUL))
michael@0 4090 continue;
michael@0 4091 }
michael@0 4092
michael@0 4093 int32_t nameSpaceID = name->NamespaceID();
michael@0 4094 nsIAtom* attr = name->LocalName();
michael@0 4095 nsIAtom* prefix = name->GetPrefix();
michael@0 4096
michael@0 4097 nsAutoString value;
michael@0 4098 aOverlayNode->GetAttr(nameSpaceID, attr, value);
michael@0 4099
michael@0 4100 // Element in the overlay has the 'removeelement' attribute set
michael@0 4101 // so remove it from the actual document.
michael@0 4102 if (attr == nsGkAtoms::removeelement &&
michael@0 4103 value.EqualsLiteral("true")) {
michael@0 4104
michael@0 4105 nsCOMPtr<nsINode> parent = aTargetNode->GetParentNode();
michael@0 4106 if (!parent) return NS_ERROR_FAILURE;
michael@0 4107 rv = RemoveElement(parent, aTargetNode);
michael@0 4108 if (NS_FAILED(rv)) return rv;
michael@0 4109
michael@0 4110 return NS_OK;
michael@0 4111 }
michael@0 4112
michael@0 4113 rv = aTargetNode->SetAttr(nameSpaceID, attr, prefix, value, aNotify);
michael@0 4114 if (!NS_FAILED(rv) && !aNotify)
michael@0 4115 rv = mDocument->BroadcastAttributeChangeFromOverlay(aTargetNode,
michael@0 4116 nameSpaceID,
michael@0 4117 attr, prefix,
michael@0 4118 value);
michael@0 4119 if (NS_FAILED(rv)) return rv;
michael@0 4120 }
michael@0 4121
michael@0 4122
michael@0 4123 // Walk our child nodes, looking for elements that have the 'id'
michael@0 4124 // attribute set. If we find any, we must do a parent check in the
michael@0 4125 // actual document to ensure that the structure matches that of
michael@0 4126 // the actual document. If it does, we can call ourselves and attempt
michael@0 4127 // to merge inside that subtree. If not, we just append the tree to
michael@0 4128 // the parent like any other.
michael@0 4129
michael@0 4130 uint32_t childCount = aOverlayNode->GetChildCount();
michael@0 4131
michael@0 4132 // This must be a strong reference since it will be the only
michael@0 4133 // reference to a content object during part of this loop.
michael@0 4134 nsCOMPtr<nsIContent> currContent;
michael@0 4135
michael@0 4136 for (i = 0; i < childCount; ++i) {
michael@0 4137 currContent = aOverlayNode->GetFirstChild();
michael@0 4138
michael@0 4139 nsIAtom *idAtom = currContent->GetID();
michael@0 4140
michael@0 4141 nsIContent *elementInDocument = nullptr;
michael@0 4142 if (idAtom) {
michael@0 4143 nsDependentAtomString id(idAtom);
michael@0 4144
michael@0 4145 if (!id.IsEmpty()) {
michael@0 4146 nsIDocument *doc = aTargetNode->GetDocument();
michael@0 4147 if (!doc) return NS_ERROR_FAILURE;
michael@0 4148
michael@0 4149 elementInDocument = doc->GetElementById(id);
michael@0 4150 }
michael@0 4151 }
michael@0 4152
michael@0 4153 // The item has an 'id' attribute set, and we need to check with
michael@0 4154 // the actual document to see if an item with this id exists at
michael@0 4155 // this locale. If so, we want to merge the subtree under that
michael@0 4156 // node. Otherwise, we just do an append as if the element had
michael@0 4157 // no id attribute.
michael@0 4158 if (elementInDocument) {
michael@0 4159 // Given two parents, aTargetNode and aOverlayNode, we want
michael@0 4160 // to call merge on currContent if we find an associated
michael@0 4161 // node in the document with the same id as currContent that
michael@0 4162 // also has aTargetNode as its parent.
michael@0 4163
michael@0 4164 nsIContent *elementParent = elementInDocument->GetParent();
michael@0 4165
michael@0 4166 nsIAtom *parentID = elementParent->GetID();
michael@0 4167 if (parentID &&
michael@0 4168 aTargetNode->AttrValueIs(kNameSpaceID_None, nsGkAtoms::id,
michael@0 4169 nsDependentAtomString(parentID),
michael@0 4170 eCaseMatters)) {
michael@0 4171 // The element matches. "Go Deep!"
michael@0 4172 rv = Merge(elementInDocument, currContent, aNotify);
michael@0 4173 if (NS_FAILED(rv)) return rv;
michael@0 4174 aOverlayNode->RemoveChildAt(0, false);
michael@0 4175
michael@0 4176 continue;
michael@0 4177 }
michael@0 4178 }
michael@0 4179
michael@0 4180 aOverlayNode->RemoveChildAt(0, false);
michael@0 4181
michael@0 4182 rv = InsertElement(aTargetNode, currContent, aNotify);
michael@0 4183 if (NS_FAILED(rv)) return rv;
michael@0 4184 }
michael@0 4185
michael@0 4186 return NS_OK;
michael@0 4187 }
michael@0 4188
michael@0 4189
michael@0 4190
michael@0 4191 XULDocument::OverlayForwardReference::~OverlayForwardReference()
michael@0 4192 {
michael@0 4193 #ifdef PR_LOGGING
michael@0 4194 if (PR_LOG_TEST(gXULLog, PR_LOG_WARNING) && !mResolved) {
michael@0 4195 nsAutoString id;
michael@0 4196 mOverlay->GetAttr(kNameSpaceID_None, nsGkAtoms::id, id);
michael@0 4197
michael@0 4198 nsAutoCString idC;
michael@0 4199 idC.AssignWithConversion(id);
michael@0 4200
michael@0 4201 nsIURI *protoURI = mDocument->mCurrentPrototype->GetURI();
michael@0 4202 nsAutoCString urlspec;
michael@0 4203 protoURI->GetSpec(urlspec);
michael@0 4204
michael@0 4205 nsCOMPtr<nsIURI> docURI;
michael@0 4206 nsAutoCString parentDoc;
michael@0 4207 nsresult rv = mDocument->mChannel->GetOriginalURI(getter_AddRefs(docURI));
michael@0 4208 if (NS_SUCCEEDED(rv))
michael@0 4209 docURI->GetSpec(parentDoc);
michael@0 4210 PR_LOG(gXULLog, PR_LOG_WARNING,
michael@0 4211 ("xul: %s overlay failed to resolve '%s' in %s",
michael@0 4212 urlspec.get(), idC.get(), parentDoc.get()));
michael@0 4213 }
michael@0 4214 #endif
michael@0 4215 }
michael@0 4216
michael@0 4217
michael@0 4218 //----------------------------------------------------------------------
michael@0 4219 //
michael@0 4220 // XULDocument::BroadcasterHookup
michael@0 4221 //
michael@0 4222
michael@0 4223 nsForwardReference::Result
michael@0 4224 XULDocument::BroadcasterHookup::Resolve()
michael@0 4225 {
michael@0 4226 nsresult rv;
michael@0 4227
michael@0 4228 bool listener;
michael@0 4229 rv = mDocument->CheckBroadcasterHookup(mObservesElement, &listener, &mResolved);
michael@0 4230 if (NS_FAILED(rv)) return eResolve_Error;
michael@0 4231
michael@0 4232 return mResolved ? eResolve_Succeeded : eResolve_Later;
michael@0 4233 }
michael@0 4234
michael@0 4235
michael@0 4236 XULDocument::BroadcasterHookup::~BroadcasterHookup()
michael@0 4237 {
michael@0 4238 #ifdef PR_LOGGING
michael@0 4239 if (PR_LOG_TEST(gXULLog, PR_LOG_WARNING) && !mResolved) {
michael@0 4240 // Tell the world we failed
michael@0 4241 nsIAtom *tag = mObservesElement->Tag();
michael@0 4242
michael@0 4243 nsAutoString broadcasterID;
michael@0 4244 nsAutoString attribute;
michael@0 4245
michael@0 4246 if (tag == nsGkAtoms::observes) {
michael@0 4247 mObservesElement->GetAttr(kNameSpaceID_None, nsGkAtoms::element, broadcasterID);
michael@0 4248 mObservesElement->GetAttr(kNameSpaceID_None, nsGkAtoms::attribute, attribute);
michael@0 4249 }
michael@0 4250 else {
michael@0 4251 mObservesElement->GetAttr(kNameSpaceID_None, nsGkAtoms::observes, broadcasterID);
michael@0 4252 attribute.AssignLiteral("*");
michael@0 4253 }
michael@0 4254
michael@0 4255 nsAutoCString attributeC,broadcasteridC;
michael@0 4256 attributeC.AssignWithConversion(attribute);
michael@0 4257 broadcasteridC.AssignWithConversion(broadcasterID);
michael@0 4258 PR_LOG(gXULLog, PR_LOG_WARNING,
michael@0 4259 ("xul: broadcaster hookup failed <%s attribute='%s'> to %s",
michael@0 4260 nsAtomCString(tag).get(),
michael@0 4261 attributeC.get(),
michael@0 4262 broadcasteridC.get()));
michael@0 4263 }
michael@0 4264 #endif
michael@0 4265 }
michael@0 4266
michael@0 4267
michael@0 4268 //----------------------------------------------------------------------
michael@0 4269 //
michael@0 4270 // XULDocument::TemplateBuilderHookup
michael@0 4271 //
michael@0 4272
michael@0 4273 nsForwardReference::Result
michael@0 4274 XULDocument::TemplateBuilderHookup::Resolve()
michael@0 4275 {
michael@0 4276 bool needsHookup;
michael@0 4277 nsresult rv = CheckTemplateBuilderHookup(mElement, &needsHookup);
michael@0 4278 if (NS_FAILED(rv))
michael@0 4279 return eResolve_Error;
michael@0 4280
michael@0 4281 if (needsHookup) {
michael@0 4282 rv = CreateTemplateBuilder(mElement);
michael@0 4283 if (NS_FAILED(rv))
michael@0 4284 return eResolve_Error;
michael@0 4285 }
michael@0 4286
michael@0 4287 return eResolve_Succeeded;
michael@0 4288 }
michael@0 4289
michael@0 4290
michael@0 4291 //----------------------------------------------------------------------
michael@0 4292
michael@0 4293 nsresult
michael@0 4294 XULDocument::BroadcastAttributeChangeFromOverlay(nsIContent* aNode,
michael@0 4295 int32_t aNameSpaceID,
michael@0 4296 nsIAtom* aAttribute,
michael@0 4297 nsIAtom* aPrefix,
michael@0 4298 const nsAString& aValue)
michael@0 4299 {
michael@0 4300 nsresult rv = NS_OK;
michael@0 4301
michael@0 4302 if (!mBroadcasterMap || !CanBroadcast(aNameSpaceID, aAttribute))
michael@0 4303 return rv;
michael@0 4304
michael@0 4305 if (!aNode->IsElement())
michael@0 4306 return rv;
michael@0 4307
michael@0 4308 BroadcasterMapEntry* entry = static_cast<BroadcasterMapEntry*>
michael@0 4309 (PL_DHashTableOperate(mBroadcasterMap, aNode->AsElement(), PL_DHASH_LOOKUP));
michael@0 4310 if (!PL_DHASH_ENTRY_IS_BUSY(entry))
michael@0 4311 return rv;
michael@0 4312
michael@0 4313 // We've got listeners: push the value.
michael@0 4314 int32_t i;
michael@0 4315 for (i = entry->mListeners.Count() - 1; i >= 0; --i) {
michael@0 4316 BroadcastListener* bl = static_cast<BroadcastListener*>
michael@0 4317 (entry->mListeners[i]);
michael@0 4318
michael@0 4319 if ((bl->mAttribute != aAttribute) &&
michael@0 4320 (bl->mAttribute != nsGkAtoms::_asterix))
michael@0 4321 continue;
michael@0 4322
michael@0 4323 nsCOMPtr<nsIContent> l = do_QueryReferent(bl->mListener);
michael@0 4324 if (l) {
michael@0 4325 rv = l->SetAttr(aNameSpaceID, aAttribute,
michael@0 4326 aPrefix, aValue, false);
michael@0 4327 if (NS_FAILED(rv)) return rv;
michael@0 4328 }
michael@0 4329 }
michael@0 4330 return rv;
michael@0 4331 }
michael@0 4332
michael@0 4333 nsresult
michael@0 4334 XULDocument::FindBroadcaster(Element* aElement,
michael@0 4335 Element** aListener,
michael@0 4336 nsString& aBroadcasterID,
michael@0 4337 nsString& aAttribute,
michael@0 4338 Element** aBroadcaster)
michael@0 4339 {
michael@0 4340 nsINodeInfo *ni = aElement->NodeInfo();
michael@0 4341 *aListener = nullptr;
michael@0 4342 *aBroadcaster = nullptr;
michael@0 4343
michael@0 4344 if (ni->Equals(nsGkAtoms::observes, kNameSpaceID_XUL)) {
michael@0 4345 // It's an <observes> element, which means that the actual
michael@0 4346 // listener is the _parent_ node. This element should have an
michael@0 4347 // 'element' attribute that specifies the ID of the
michael@0 4348 // broadcaster element, and an 'attribute' element, which
michael@0 4349 // specifies the name of the attribute to observe.
michael@0 4350 nsIContent* parent = aElement->GetParent();
michael@0 4351 if (!parent) {
michael@0 4352 // <observes> is the root element
michael@0 4353 return NS_FINDBROADCASTER_NOT_FOUND;
michael@0 4354 }
michael@0 4355
michael@0 4356 // If we're still parented by an 'overlay' tag, then we haven't
michael@0 4357 // made it into the real document yet. Defer hookup.
michael@0 4358 if (parent->NodeInfo()->Equals(nsGkAtoms::overlay,
michael@0 4359 kNameSpaceID_XUL)) {
michael@0 4360 return NS_FINDBROADCASTER_AWAIT_OVERLAYS;
michael@0 4361 }
michael@0 4362
michael@0 4363 *aListener = parent->IsElement() ? parent->AsElement() : nullptr;
michael@0 4364 NS_IF_ADDREF(*aListener);
michael@0 4365
michael@0 4366 aElement->GetAttr(kNameSpaceID_None, nsGkAtoms::element, aBroadcasterID);
michael@0 4367 if (aBroadcasterID.IsEmpty()) {
michael@0 4368 return NS_FINDBROADCASTER_NOT_FOUND;
michael@0 4369 }
michael@0 4370 aElement->GetAttr(kNameSpaceID_None, nsGkAtoms::attribute, aAttribute);
michael@0 4371 }
michael@0 4372 else {
michael@0 4373 // It's a generic element, which means that we'll use the
michael@0 4374 // value of the 'observes' attribute to determine the ID of
michael@0 4375 // the broadcaster element, and we'll watch _all_ of its
michael@0 4376 // values.
michael@0 4377 aElement->GetAttr(kNameSpaceID_None, nsGkAtoms::observes, aBroadcasterID);
michael@0 4378
michael@0 4379 // Bail if there's no aBroadcasterID
michael@0 4380 if (aBroadcasterID.IsEmpty()) {
michael@0 4381 // Try the command attribute next.
michael@0 4382 aElement->GetAttr(kNameSpaceID_None, nsGkAtoms::command, aBroadcasterID);
michael@0 4383 if (!aBroadcasterID.IsEmpty()) {
michael@0 4384 // We've got something in the command attribute. We
michael@0 4385 // only treat this as a normal broadcaster if we are
michael@0 4386 // not a menuitem or a key.
michael@0 4387
michael@0 4388 if (ni->Equals(nsGkAtoms::menuitem, kNameSpaceID_XUL) ||
michael@0 4389 ni->Equals(nsGkAtoms::key, kNameSpaceID_XUL)) {
michael@0 4390 return NS_FINDBROADCASTER_NOT_FOUND;
michael@0 4391 }
michael@0 4392 }
michael@0 4393 else {
michael@0 4394 return NS_FINDBROADCASTER_NOT_FOUND;
michael@0 4395 }
michael@0 4396 }
michael@0 4397
michael@0 4398 *aListener = aElement;
michael@0 4399 NS_ADDREF(*aListener);
michael@0 4400
michael@0 4401 aAttribute.AssignLiteral("*");
michael@0 4402 }
michael@0 4403
michael@0 4404 // Make sure we got a valid listener.
michael@0 4405 NS_ENSURE_TRUE(*aListener, NS_ERROR_UNEXPECTED);
michael@0 4406
michael@0 4407 // Try to find the broadcaster element in the document.
michael@0 4408 *aBroadcaster = GetElementById(aBroadcasterID);
michael@0 4409
michael@0 4410 // If we can't find the broadcaster, then we'll need to defer the
michael@0 4411 // hookup. We may need to resolve some of the other overlays
michael@0 4412 // first.
michael@0 4413 if (! *aBroadcaster) {
michael@0 4414 return NS_FINDBROADCASTER_AWAIT_OVERLAYS;
michael@0 4415 }
michael@0 4416
michael@0 4417 NS_ADDREF(*aBroadcaster);
michael@0 4418
michael@0 4419 return NS_FINDBROADCASTER_FOUND;
michael@0 4420 }
michael@0 4421
michael@0 4422 nsresult
michael@0 4423 XULDocument::CheckBroadcasterHookup(Element* aElement,
michael@0 4424 bool* aNeedsHookup,
michael@0 4425 bool* aDidResolve)
michael@0 4426 {
michael@0 4427 // Resolve a broadcaster hookup. Look at the element that we're
michael@0 4428 // trying to resolve: it could be an '<observes>' element, or just
michael@0 4429 // a vanilla element with an 'observes' attribute on it.
michael@0 4430 nsresult rv;
michael@0 4431
michael@0 4432 *aDidResolve = false;
michael@0 4433
michael@0 4434 nsCOMPtr<Element> listener;
michael@0 4435 nsAutoString broadcasterID;
michael@0 4436 nsAutoString attribute;
michael@0 4437 nsCOMPtr<Element> broadcaster;
michael@0 4438
michael@0 4439 rv = FindBroadcaster(aElement, getter_AddRefs(listener),
michael@0 4440 broadcasterID, attribute, getter_AddRefs(broadcaster));
michael@0 4441 switch (rv) {
michael@0 4442 case NS_FINDBROADCASTER_NOT_FOUND:
michael@0 4443 *aNeedsHookup = false;
michael@0 4444 return NS_OK;
michael@0 4445 case NS_FINDBROADCASTER_AWAIT_OVERLAYS:
michael@0 4446 *aNeedsHookup = true;
michael@0 4447 return NS_OK;
michael@0 4448 case NS_FINDBROADCASTER_FOUND:
michael@0 4449 break;
michael@0 4450 default:
michael@0 4451 return rv;
michael@0 4452 }
michael@0 4453
michael@0 4454 NS_ENSURE_ARG(broadcaster && listener);
michael@0 4455 ErrorResult domRv;
michael@0 4456 AddBroadcastListenerFor(*broadcaster, *listener, attribute, domRv);
michael@0 4457 if (domRv.Failed()) {
michael@0 4458 return domRv.ErrorCode();
michael@0 4459 }
michael@0 4460
michael@0 4461 #ifdef PR_LOGGING
michael@0 4462 // Tell the world we succeeded
michael@0 4463 if (PR_LOG_TEST(gXULLog, PR_LOG_NOTICE)) {
michael@0 4464 nsCOMPtr<nsIContent> content =
michael@0 4465 do_QueryInterface(listener);
michael@0 4466
michael@0 4467 NS_ASSERTION(content != nullptr, "not an nsIContent");
michael@0 4468 if (! content)
michael@0 4469 return rv;
michael@0 4470
michael@0 4471 nsAutoCString attributeC,broadcasteridC;
michael@0 4472 attributeC.AssignWithConversion(attribute);
michael@0 4473 broadcasteridC.AssignWithConversion(broadcasterID);
michael@0 4474 PR_LOG(gXULLog, PR_LOG_NOTICE,
michael@0 4475 ("xul: broadcaster hookup <%s attribute='%s'> to %s",
michael@0 4476 nsAtomCString(content->Tag()).get(),
michael@0 4477 attributeC.get(),
michael@0 4478 broadcasteridC.get()));
michael@0 4479 }
michael@0 4480 #endif
michael@0 4481
michael@0 4482 *aNeedsHookup = false;
michael@0 4483 *aDidResolve = true;
michael@0 4484 return NS_OK;
michael@0 4485 }
michael@0 4486
michael@0 4487 nsresult
michael@0 4488 XULDocument::InsertElement(nsINode* aParent, nsIContent* aChild,
michael@0 4489 bool aNotify)
michael@0 4490 {
michael@0 4491 // Insert aChild appropriately into aParent, accounting for a
michael@0 4492 // 'pos' attribute set on aChild.
michael@0 4493
michael@0 4494 nsAutoString posStr;
michael@0 4495 bool wasInserted = false;
michael@0 4496
michael@0 4497 // insert after an element of a given id
michael@0 4498 aChild->GetAttr(kNameSpaceID_None, nsGkAtoms::insertafter, posStr);
michael@0 4499 bool isInsertAfter = true;
michael@0 4500
michael@0 4501 if (posStr.IsEmpty()) {
michael@0 4502 aChild->GetAttr(kNameSpaceID_None, nsGkAtoms::insertbefore, posStr);
michael@0 4503 isInsertAfter = false;
michael@0 4504 }
michael@0 4505
michael@0 4506 if (!posStr.IsEmpty()) {
michael@0 4507 nsIDocument *document = aParent->OwnerDoc();
michael@0 4508
michael@0 4509 nsIContent *content = nullptr;
michael@0 4510
michael@0 4511 char* str = ToNewCString(posStr);
michael@0 4512 char* rest;
michael@0 4513 char* token = nsCRT::strtok(str, ", ", &rest);
michael@0 4514
michael@0 4515 while (token) {
michael@0 4516 content = document->GetElementById(NS_ConvertASCIItoUTF16(token));
michael@0 4517 if (content)
michael@0 4518 break;
michael@0 4519
michael@0 4520 token = nsCRT::strtok(rest, ", ", &rest);
michael@0 4521 }
michael@0 4522 nsMemory::Free(str);
michael@0 4523
michael@0 4524 if (content) {
michael@0 4525 int32_t pos = aParent->IndexOf(content);
michael@0 4526
michael@0 4527 if (pos != -1) {
michael@0 4528 pos = isInsertAfter ? pos + 1 : pos;
michael@0 4529 nsresult rv = aParent->InsertChildAt(aChild, pos, aNotify);
michael@0 4530 if (NS_FAILED(rv))
michael@0 4531 return rv;
michael@0 4532
michael@0 4533 wasInserted = true;
michael@0 4534 }
michael@0 4535 }
michael@0 4536 }
michael@0 4537
michael@0 4538 if (!wasInserted) {
michael@0 4539
michael@0 4540 aChild->GetAttr(kNameSpaceID_None, nsGkAtoms::position, posStr);
michael@0 4541 if (!posStr.IsEmpty()) {
michael@0 4542 nsresult rv;
michael@0 4543 // Positions are one-indexed.
michael@0 4544 int32_t pos = posStr.ToInteger(&rv);
michael@0 4545 // Note: if the insertion index (which is |pos - 1|) would be less
michael@0 4546 // than 0 or greater than the number of children aParent has, then
michael@0 4547 // don't insert, since the position is bogus. Just skip on to
michael@0 4548 // appending.
michael@0 4549 if (NS_SUCCEEDED(rv) && pos > 0 &&
michael@0 4550 uint32_t(pos - 1) <= aParent->GetChildCount()) {
michael@0 4551 rv = aParent->InsertChildAt(aChild, pos - 1, aNotify);
michael@0 4552 if (NS_SUCCEEDED(rv))
michael@0 4553 wasInserted = true;
michael@0 4554 // If the insertion fails, then we should still
michael@0 4555 // attempt an append. Thus, rather than returning rv
michael@0 4556 // immediately, we fall through to the final
michael@0 4557 // "catch-all" case that just does an AppendChildTo.
michael@0 4558 }
michael@0 4559 }
michael@0 4560 }
michael@0 4561
michael@0 4562 if (!wasInserted) {
michael@0 4563 return aParent->AppendChildTo(aChild, aNotify);
michael@0 4564 }
michael@0 4565 return NS_OK;
michael@0 4566 }
michael@0 4567
michael@0 4568 nsresult
michael@0 4569 XULDocument::RemoveElement(nsINode* aParent, nsINode* aChild)
michael@0 4570 {
michael@0 4571 int32_t nodeOffset = aParent->IndexOf(aChild);
michael@0 4572
michael@0 4573 aParent->RemoveChildAt(nodeOffset, true);
michael@0 4574 return NS_OK;
michael@0 4575 }
michael@0 4576
michael@0 4577 //----------------------------------------------------------------------
michael@0 4578 //
michael@0 4579 // CachedChromeStreamListener
michael@0 4580 //
michael@0 4581
michael@0 4582 XULDocument::CachedChromeStreamListener::CachedChromeStreamListener(XULDocument* aDocument, bool aProtoLoaded)
michael@0 4583 : mDocument(aDocument),
michael@0 4584 mProtoLoaded(aProtoLoaded)
michael@0 4585 {
michael@0 4586 NS_ADDREF(mDocument);
michael@0 4587 }
michael@0 4588
michael@0 4589
michael@0 4590 XULDocument::CachedChromeStreamListener::~CachedChromeStreamListener()
michael@0 4591 {
michael@0 4592 NS_RELEASE(mDocument);
michael@0 4593 }
michael@0 4594
michael@0 4595
michael@0 4596 NS_IMPL_ISUPPORTS(XULDocument::CachedChromeStreamListener,
michael@0 4597 nsIRequestObserver, nsIStreamListener)
michael@0 4598
michael@0 4599 NS_IMETHODIMP
michael@0 4600 XULDocument::CachedChromeStreamListener::OnStartRequest(nsIRequest *request,
michael@0 4601 nsISupports* acontext)
michael@0 4602 {
michael@0 4603 return NS_ERROR_PARSED_DATA_CACHED;
michael@0 4604 }
michael@0 4605
michael@0 4606
michael@0 4607 NS_IMETHODIMP
michael@0 4608 XULDocument::CachedChromeStreamListener::OnStopRequest(nsIRequest *request,
michael@0 4609 nsISupports* aContext,
michael@0 4610 nsresult aStatus)
michael@0 4611 {
michael@0 4612 if (! mProtoLoaded)
michael@0 4613 return NS_OK;
michael@0 4614
michael@0 4615 return mDocument->OnPrototypeLoadDone(true);
michael@0 4616 }
michael@0 4617
michael@0 4618
michael@0 4619 NS_IMETHODIMP
michael@0 4620 XULDocument::CachedChromeStreamListener::OnDataAvailable(nsIRequest *request,
michael@0 4621 nsISupports* aContext,
michael@0 4622 nsIInputStream* aInStr,
michael@0 4623 uint64_t aSourceOffset,
michael@0 4624 uint32_t aCount)
michael@0 4625 {
michael@0 4626 NS_NOTREACHED("CachedChromeStream doesn't receive data");
michael@0 4627 return NS_ERROR_UNEXPECTED;
michael@0 4628 }
michael@0 4629
michael@0 4630 //----------------------------------------------------------------------
michael@0 4631 //
michael@0 4632 // ParserObserver
michael@0 4633 //
michael@0 4634
michael@0 4635 XULDocument::ParserObserver::ParserObserver(XULDocument* aDocument,
michael@0 4636 nsXULPrototypeDocument* aPrototype)
michael@0 4637 : mDocument(aDocument), mPrototype(aPrototype)
michael@0 4638 {
michael@0 4639 }
michael@0 4640
michael@0 4641 XULDocument::ParserObserver::~ParserObserver()
michael@0 4642 {
michael@0 4643 }
michael@0 4644
michael@0 4645 NS_IMPL_ISUPPORTS(XULDocument::ParserObserver, nsIRequestObserver)
michael@0 4646
michael@0 4647 NS_IMETHODIMP
michael@0 4648 XULDocument::ParserObserver::OnStartRequest(nsIRequest *request,
michael@0 4649 nsISupports* aContext)
michael@0 4650 {
michael@0 4651 // Guard against buggy channels calling OnStartRequest multiple times.
michael@0 4652 if (mPrototype) {
michael@0 4653 nsCOMPtr<nsIChannel> channel = do_QueryInterface(request);
michael@0 4654 nsIScriptSecurityManager* secMan = nsContentUtils::GetSecurityManager();
michael@0 4655 if (channel && secMan) {
michael@0 4656 nsCOMPtr<nsIPrincipal> principal;
michael@0 4657 secMan->GetChannelPrincipal(channel, getter_AddRefs(principal));
michael@0 4658
michael@0 4659 // Failure there is ok -- it'll just set a (safe) null principal
michael@0 4660 mPrototype->SetDocumentPrincipal(principal);
michael@0 4661 }
michael@0 4662
michael@0 4663 // Make sure to avoid cycles
michael@0 4664 mPrototype = nullptr;
michael@0 4665 }
michael@0 4666
michael@0 4667 return NS_OK;
michael@0 4668 }
michael@0 4669
michael@0 4670 NS_IMETHODIMP
michael@0 4671 XULDocument::ParserObserver::OnStopRequest(nsIRequest *request,
michael@0 4672 nsISupports* aContext,
michael@0 4673 nsresult aStatus)
michael@0 4674 {
michael@0 4675 nsresult rv = NS_OK;
michael@0 4676
michael@0 4677 if (NS_FAILED(aStatus)) {
michael@0 4678 // If an overlay load fails, we need to nudge the prototype
michael@0 4679 // walk along.
michael@0 4680 nsCOMPtr<nsIChannel> aChannel = do_QueryInterface(request);
michael@0 4681 if (aChannel) {
michael@0 4682 nsCOMPtr<nsIURI> uri;
michael@0 4683 aChannel->GetOriginalURI(getter_AddRefs(uri));
michael@0 4684 if (uri) {
michael@0 4685 mDocument->ReportMissingOverlay(uri);
michael@0 4686 }
michael@0 4687 }
michael@0 4688
michael@0 4689 rv = mDocument->ResumeWalk();
michael@0 4690 }
michael@0 4691
michael@0 4692 // Drop the reference to the document to break cycle between the
michael@0 4693 // document, the parser, the content sink, and the parser
michael@0 4694 // observer.
michael@0 4695 mDocument = nullptr;
michael@0 4696
michael@0 4697 return rv;
michael@0 4698 }
michael@0 4699
michael@0 4700 already_AddRefed<nsPIWindowRoot>
michael@0 4701 XULDocument::GetWindowRoot()
michael@0 4702 {
michael@0 4703 nsCOMPtr<nsIInterfaceRequestor> ir(mDocumentContainer);
michael@0 4704 nsCOMPtr<nsIDOMWindow> window(do_GetInterface(ir));
michael@0 4705 nsCOMPtr<nsPIDOMWindow> piWin(do_QueryInterface(window));
michael@0 4706 return piWin ? piWin->GetTopWindowRoot() : nullptr;
michael@0 4707 }
michael@0 4708
michael@0 4709 bool
michael@0 4710 XULDocument::IsDocumentRightToLeft()
michael@0 4711 {
michael@0 4712 // setting the localedir attribute on the root element forces a
michael@0 4713 // specific direction for the document.
michael@0 4714 Element* element = GetRootElement();
michael@0 4715 if (element) {
michael@0 4716 static nsIContent::AttrValuesArray strings[] =
michael@0 4717 {&nsGkAtoms::ltr, &nsGkAtoms::rtl, nullptr};
michael@0 4718 switch (element->FindAttrValueIn(kNameSpaceID_None, nsGkAtoms::localedir,
michael@0 4719 strings, eCaseMatters)) {
michael@0 4720 case 0: return false;
michael@0 4721 case 1: return true;
michael@0 4722 default: break; // otherwise, not a valid value, so fall through
michael@0 4723 }
michael@0 4724 }
michael@0 4725
michael@0 4726 // otherwise, get the locale from the chrome registry and
michael@0 4727 // look up the intl.uidirection.<locale> preference
michael@0 4728 nsCOMPtr<nsIXULChromeRegistry> reg =
michael@0 4729 mozilla::services::GetXULChromeRegistryService();
michael@0 4730 if (!reg)
michael@0 4731 return false;
michael@0 4732
michael@0 4733 nsAutoCString package;
michael@0 4734 bool isChrome;
michael@0 4735 if (NS_SUCCEEDED(mDocumentURI->SchemeIs("chrome", &isChrome)) &&
michael@0 4736 isChrome) {
michael@0 4737 mDocumentURI->GetHostPort(package);
michael@0 4738 }
michael@0 4739 else {
michael@0 4740 // use the 'global' package for about and resource uris.
michael@0 4741 // otherwise, just default to left-to-right.
michael@0 4742 bool isAbout, isResource;
michael@0 4743 if (NS_SUCCEEDED(mDocumentURI->SchemeIs("about", &isAbout)) &&
michael@0 4744 isAbout) {
michael@0 4745 package.AssignLiteral("global");
michael@0 4746 }
michael@0 4747 else if (NS_SUCCEEDED(mDocumentURI->SchemeIs("resource", &isResource)) &&
michael@0 4748 isResource) {
michael@0 4749 package.AssignLiteral("global");
michael@0 4750 }
michael@0 4751 else {
michael@0 4752 return false;
michael@0 4753 }
michael@0 4754 }
michael@0 4755
michael@0 4756 bool isRTL = false;
michael@0 4757 reg->IsLocaleRTL(package, &isRTL);
michael@0 4758 return isRTL;
michael@0 4759 }
michael@0 4760
michael@0 4761 void
michael@0 4762 XULDocument::ResetDocumentDirection()
michael@0 4763 {
michael@0 4764 DocumentStatesChanged(NS_DOCUMENT_STATE_RTL_LOCALE);
michael@0 4765 }
michael@0 4766
michael@0 4767 void
michael@0 4768 XULDocument::DirectionChanged(const char* aPrefName, void* aData)
michael@0 4769 {
michael@0 4770 // Reset the direction and restyle the document if necessary.
michael@0 4771 XULDocument* doc = (XULDocument *)aData;
michael@0 4772 if (doc) {
michael@0 4773 doc->ResetDocumentDirection();
michael@0 4774 }
michael@0 4775 }
michael@0 4776
michael@0 4777 int
michael@0 4778 XULDocument::GetDocumentLWTheme()
michael@0 4779 {
michael@0 4780 if (mDocLWTheme == Doc_Theme_Uninitialized) {
michael@0 4781 mDocLWTheme = Doc_Theme_None; // No lightweight theme by default
michael@0 4782
michael@0 4783 Element* element = GetRootElement();
michael@0 4784 nsAutoString hasLWTheme;
michael@0 4785 if (element &&
michael@0 4786 element->GetAttr(kNameSpaceID_None, nsGkAtoms::lwtheme, hasLWTheme) &&
michael@0 4787 !(hasLWTheme.IsEmpty()) &&
michael@0 4788 hasLWTheme.EqualsLiteral("true")) {
michael@0 4789 mDocLWTheme = Doc_Theme_Neutral;
michael@0 4790 nsAutoString lwTheme;
michael@0 4791 element->GetAttr(kNameSpaceID_None, nsGkAtoms::lwthemetextcolor, lwTheme);
michael@0 4792 if (!(lwTheme.IsEmpty())) {
michael@0 4793 if (lwTheme.EqualsLiteral("dark"))
michael@0 4794 mDocLWTheme = Doc_Theme_Dark;
michael@0 4795 else if (lwTheme.EqualsLiteral("bright"))
michael@0 4796 mDocLWTheme = Doc_Theme_Bright;
michael@0 4797 }
michael@0 4798 }
michael@0 4799 }
michael@0 4800 return mDocLWTheme;
michael@0 4801 }
michael@0 4802
michael@0 4803 NS_IMETHODIMP
michael@0 4804 XULDocument::GetBoxObjectFor(nsIDOMElement* aElement, nsIBoxObject** aResult)
michael@0 4805 {
michael@0 4806 ErrorResult rv;
michael@0 4807 nsCOMPtr<Element> el = do_QueryInterface(aElement);
michael@0 4808 *aResult = GetBoxObjectFor(el, rv).take();
michael@0 4809 return rv.ErrorCode();
michael@0 4810 }
michael@0 4811
michael@0 4812 JSObject*
michael@0 4813 XULDocument::WrapNode(JSContext *aCx)
michael@0 4814 {
michael@0 4815 return XULDocumentBinding::Wrap(aCx, this);
michael@0 4816 }
michael@0 4817
michael@0 4818 } // namespace dom
michael@0 4819 } // namespace mozilla

mercurial