/src/mozilla-central/dom/xul/XULDocument.cpp
Line | Count | Source (jump to first uncovered line) |
1 | | /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ |
2 | | /* vim: set ts=4 sw=4 et tw=80: */ |
3 | | /* This Source Code Form is subject to the terms of the Mozilla Public |
4 | | * License, v. 2.0. If a copy of the MPL was not distributed with this |
5 | | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
6 | | |
7 | | /* |
8 | | |
9 | | An implementation for the XUL document. This implementation serves |
10 | | as the basis for generating an NGLayout content model. |
11 | | |
12 | | Notes |
13 | | ----- |
14 | | |
15 | | 1. We do some monkey business in the document observer methods to |
16 | | keep the element map in sync for HTML elements. Why don't we just |
17 | | do it for _all_ elements? Well, in the case of XUL elements, |
18 | | which may be lazily created during frame construction, the |
19 | | document observer methods will never be called because we'll be |
20 | | adding the XUL nodes into the content model "quietly". |
21 | | |
22 | | */ |
23 | | |
24 | | #include "mozilla/ArrayUtils.h" |
25 | | |
26 | | #include "XULDocument.h" |
27 | | |
28 | | #include "nsError.h" |
29 | | #include "nsIBoxObject.h" |
30 | | #include "nsIChromeRegistry.h" |
31 | | #include "nsView.h" |
32 | | #include "nsViewManager.h" |
33 | | #include "nsIContentViewer.h" |
34 | | #include "nsIStreamListener.h" |
35 | | #include "nsITimer.h" |
36 | | #include "nsDocShell.h" |
37 | | #include "nsGkAtoms.h" |
38 | | #include "nsXMLContentSink.h" |
39 | | #include "nsXULContentSink.h" |
40 | | #include "nsXULContentUtils.h" |
41 | | #include "nsIStringEnumerator.h" |
42 | | #include "nsDocElementCreatedNotificationRunner.h" |
43 | | #include "nsNetUtil.h" |
44 | | #include "nsParserCIID.h" |
45 | | #include "nsPIBoxObject.h" |
46 | | #include "mozilla/dom/BoxObject.h" |
47 | | #include "nsString.h" |
48 | | #include "nsPIDOMWindow.h" |
49 | | #include "nsPIWindowRoot.h" |
50 | | #include "nsXULElement.h" |
51 | | #include "nsXULPrototypeCache.h" |
52 | | #include "mozilla/Logging.h" |
53 | | #include "nsIFrame.h" |
54 | | #include "nsXBLService.h" |
55 | | #include "nsCExternalHandlerService.h" |
56 | | #include "nsMimeTypes.h" |
57 | | #include "nsIObjectInputStream.h" |
58 | | #include "nsIObjectOutputStream.h" |
59 | | #include "nsContentList.h" |
60 | | #include "nsISimpleEnumerator.h" |
61 | | #include "nsIScriptGlobalObject.h" |
62 | | #include "nsIScriptSecurityManager.h" |
63 | | #include "nsNodeInfoManager.h" |
64 | | #include "nsContentCreatorFunctions.h" |
65 | | #include "nsContentUtils.h" |
66 | | #include "nsIParser.h" |
67 | | #include "nsCharsetSource.h" |
68 | | #include "mozilla/StyleSheetInlines.h" |
69 | | #include "mozilla/css/Loader.h" |
70 | | #include "nsIScriptError.h" |
71 | | #include "nsIStyleSheetLinkingElement.h" |
72 | | #include "nsIObserverService.h" |
73 | | #include "nsNodeUtils.h" |
74 | | #include "nsIXULWindow.h" |
75 | | #include "nsXULPopupManager.h" |
76 | | #include "nsCCUncollectableMarker.h" |
77 | | #include "nsURILoader.h" |
78 | | #include "mozilla/BasicEvents.h" |
79 | | #include "mozilla/dom/DocumentL10n.h" |
80 | | #include "mozilla/dom/Element.h" |
81 | | #include "mozilla/dom/NodeInfoInlines.h" |
82 | | #include "mozilla/dom/ProcessingInstruction.h" |
83 | | #include "mozilla/dom/ScriptSettings.h" |
84 | | #include "mozilla/dom/XULDocumentBinding.h" |
85 | | #include "mozilla/EventDispatcher.h" |
86 | | #include "mozilla/LoadInfo.h" |
87 | | #include "mozilla/Preferences.h" |
88 | | #include "nsTextNode.h" |
89 | | #include "nsJSUtils.h" |
90 | | #include "js/CompilationAndEvaluation.h" |
91 | | #include "js/SourceBufferHolder.h" |
92 | | #include "mozilla/dom/URL.h" |
93 | | #include "nsIContentPolicy.h" |
94 | | #include "mozAutoDocUpdate.h" |
95 | | #include "xpcpublic.h" |
96 | | #include "mozilla/StyleSheet.h" |
97 | | #include "mozilla/StyleSheetInlines.h" |
98 | | #include "nsIConsoleService.h" |
99 | | |
100 | | using namespace mozilla; |
101 | | using namespace mozilla::dom; |
102 | | |
103 | | //---------------------------------------------------------------------- |
104 | | // |
105 | | // CIDs |
106 | | // |
107 | | |
108 | | static NS_DEFINE_CID(kParserCID, NS_PARSER_CID); |
109 | | |
110 | | //---------------------------------------------------------------------- |
111 | | // |
112 | | // Statics |
113 | | // |
114 | | |
115 | | int32_t XULDocument::gRefCnt = 0; |
116 | | |
117 | | LazyLogModule XULDocument::gXULLog("XULDocument"); |
118 | | |
119 | | //---------------------------------------------------------------------- |
120 | | |
121 | | struct BroadcastListener { |
122 | | nsWeakPtr mListener; |
123 | | RefPtr<nsAtom> mAttribute; |
124 | | }; |
125 | | |
126 | | struct BroadcasterMapEntry : public PLDHashEntryHdr |
127 | | { |
128 | | Element* mBroadcaster; // [WEAK] |
129 | | nsTArray<BroadcastListener*> mListeners; // [OWNING] of BroadcastListener objects |
130 | | }; |
131 | | |
132 | | //---------------------------------------------------------------------- |
133 | | // |
134 | | // ctors & dtors |
135 | | // |
136 | | |
137 | | namespace mozilla { |
138 | | namespace dom { |
139 | | |
140 | | XULDocument::XULDocument(void) |
141 | | : XMLDocument("application/vnd.mozilla.xul+xml"), |
142 | | mNextSrcLoadWaiter(nullptr), |
143 | | mApplyingPersistedAttrs(false), |
144 | | mIsWritingFastLoad(false), |
145 | | mDocumentLoaded(false), |
146 | | mStillWalking(false), |
147 | | mPendingSheets(0), |
148 | | mCurrentScriptProto(nullptr), |
149 | | mOffThreadCompiling(false), |
150 | | mOffThreadCompileStringBuf(nullptr), |
151 | | mOffThreadCompileStringLength(0), |
152 | | mBroadcasterMap(nullptr), |
153 | | mInitialLayoutComplete(false), |
154 | | mHandlingDelayedAttrChange(false), |
155 | | mHandlingDelayedBroadcasters(false) |
156 | 0 | { |
157 | 0 | // Override the default in nsDocument |
158 | 0 | mCharacterSet = UTF_8_ENCODING; |
159 | 0 |
|
160 | 0 | mDefaultElementType = kNameSpaceID_XUL; |
161 | 0 | mType = eXUL; |
162 | 0 |
|
163 | 0 | mDelayFrameLoaderInitialization = true; |
164 | 0 |
|
165 | 0 | mAllowXULXBL = eTriTrue; |
166 | 0 | } |
167 | | |
168 | | XULDocument::~XULDocument() |
169 | 0 | { |
170 | 0 | NS_ASSERTION(mNextSrcLoadWaiter == nullptr, |
171 | 0 | "unreferenced document still waiting for script source to load?"); |
172 | 0 |
|
173 | 0 | // Destroy our broadcaster map. |
174 | 0 | delete mBroadcasterMap; |
175 | 0 |
|
176 | 0 | Preferences::UnregisterCallback(XULDocument::DirectionChanged, |
177 | 0 | "intl.uidirection", this); |
178 | 0 |
|
179 | 0 | if (mOffThreadCompileStringBuf) { |
180 | 0 | js_free(mOffThreadCompileStringBuf); |
181 | 0 | } |
182 | 0 | } |
183 | | |
184 | | } // namespace dom |
185 | | } // namespace mozilla |
186 | | |
187 | | nsresult |
188 | | NS_NewXULDocument(nsIDocument** result) |
189 | 0 | { |
190 | 0 | MOZ_ASSERT(result != nullptr, "null ptr"); |
191 | 0 | if (! result) |
192 | 0 | return NS_ERROR_NULL_POINTER; |
193 | 0 | |
194 | 0 | RefPtr<XULDocument> doc = new XULDocument(); |
195 | 0 |
|
196 | 0 | nsresult rv; |
197 | 0 | if (NS_FAILED(rv = doc->Init())) { |
198 | 0 | return rv; |
199 | 0 | } |
200 | 0 | |
201 | 0 | doc.forget(result); |
202 | 0 | return NS_OK; |
203 | 0 | } |
204 | | |
205 | | |
206 | | namespace mozilla { |
207 | | namespace dom { |
208 | | |
209 | | //---------------------------------------------------------------------- |
210 | | // |
211 | | // nsISupports interface |
212 | | // |
213 | | |
214 | | NS_IMPL_CYCLE_COLLECTION_CLASS(XULDocument) |
215 | | |
216 | 0 | NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(XULDocument, XMLDocument) |
217 | 0 | NS_ASSERTION(!nsCCUncollectableMarker::InGeneration(cb, tmp->GetMarkedCCGeneration()), |
218 | 0 | "Shouldn't traverse XULDocument!"); |
219 | 0 | // XXX tmp->mContextStack? |
220 | 0 |
|
221 | 0 | NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCurrentPrototype) |
222 | 0 | NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPrototypes) |
223 | 0 | NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mLocalStore) |
224 | 0 |
|
225 | 0 | NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END |
226 | | |
227 | 0 | NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(XULDocument, XMLDocument) |
228 | 0 | NS_IMPL_CYCLE_COLLECTION_UNLINK(mLocalStore) |
229 | 0 | //XXX We should probably unlink all the objects we traverse. |
230 | 0 | NS_IMPL_CYCLE_COLLECTION_UNLINK_END |
231 | | |
232 | | NS_IMPL_ISUPPORTS_CYCLE_COLLECTION_INHERITED(XULDocument, |
233 | | XMLDocument, |
234 | | nsIStreamLoaderObserver, |
235 | | nsICSSLoaderObserver, |
236 | | nsIOffThreadScriptReceiver) |
237 | | |
238 | | |
239 | | //---------------------------------------------------------------------- |
240 | | // |
241 | | // nsIDocument interface |
242 | | // |
243 | | |
244 | | void |
245 | | XULDocument::Reset(nsIChannel* aChannel, nsILoadGroup* aLoadGroup) |
246 | 0 | { |
247 | 0 | MOZ_ASSERT_UNREACHABLE("Reset"); |
248 | 0 | } |
249 | | |
250 | | void |
251 | | XULDocument::ResetToURI(nsIURI* aURI, nsILoadGroup* aLoadGroup, |
252 | | nsIPrincipal* aPrincipal) |
253 | 0 | { |
254 | 0 | MOZ_ASSERT_UNREACHABLE("ResetToURI"); |
255 | 0 | } |
256 | | |
257 | | void |
258 | | XULDocument::SetContentType(const nsAString& aContentType) |
259 | 0 | { |
260 | 0 | NS_ASSERTION(aContentType.EqualsLiteral("application/vnd.mozilla.xul+xml"), |
261 | 0 | "xul-documents always has content-type application/vnd.mozilla.xul+xml"); |
262 | 0 | // Don't do anything, xul always has the mimetype |
263 | 0 | // application/vnd.mozilla.xul+xml |
264 | 0 | } |
265 | | |
266 | | // This is called when the master document begins loading, whether it's |
267 | | // being cached or not. |
268 | | nsresult |
269 | | XULDocument::StartDocumentLoad(const char* aCommand, nsIChannel* aChannel, |
270 | | nsILoadGroup* aLoadGroup, |
271 | | nsISupports* aContainer, |
272 | | nsIStreamListener **aDocListener, |
273 | | bool aReset, nsIContentSink* aSink) |
274 | 0 | { |
275 | 0 | if (MOZ_LOG_TEST(gXULLog, LogLevel::Warning)) { |
276 | 0 |
|
277 | 0 | nsCOMPtr<nsIURI> uri; |
278 | 0 | nsresult rv = aChannel->GetOriginalURI(getter_AddRefs(uri)); |
279 | 0 | if (NS_SUCCEEDED(rv)) { |
280 | 0 | nsAutoCString urlspec; |
281 | 0 | rv = uri->GetSpec(urlspec); |
282 | 0 | if (NS_SUCCEEDED(rv)) { |
283 | 0 | MOZ_LOG(gXULLog, LogLevel::Warning, |
284 | 0 | ("xul: load document '%s'", urlspec.get())); |
285 | 0 | } |
286 | 0 | } |
287 | 0 | } |
288 | 0 | // NOTE: If this ever starts calling nsDocument::StartDocumentLoad |
289 | 0 | // we'll possibly need to reset our content type afterwards. |
290 | 0 | mStillWalking = true; |
291 | 0 | mMayStartLayout = false; |
292 | 0 | mDocumentLoadGroup = do_GetWeakReference(aLoadGroup); |
293 | 0 |
|
294 | 0 | mChannel = aChannel; |
295 | 0 |
|
296 | 0 | // Get the URI. Note that this should match nsDocShell::OnLoadingSite |
297 | 0 | nsresult rv = |
298 | 0 | NS_GetFinalChannelURI(aChannel, getter_AddRefs(mDocumentURI)); |
299 | 0 | NS_ENSURE_SUCCESS(rv, rv); |
300 | 0 |
|
301 | 0 | mOriginalURI = mDocumentURI; |
302 | 0 |
|
303 | 0 | // Get the document's principal |
304 | 0 | nsCOMPtr<nsIPrincipal> principal; |
305 | 0 | nsContentUtils::GetSecurityManager()-> |
306 | 0 | GetChannelResultPrincipal(mChannel, getter_AddRefs(principal)); |
307 | 0 | principal = MaybeDowngradePrincipal(principal); |
308 | 0 |
|
309 | 0 | ResetStylesheetsToURI(mDocumentURI); |
310 | 0 |
|
311 | 0 | RetrieveRelevantHeaders(aChannel); |
312 | 0 |
|
313 | 0 | // Look in the chrome cache: we've got this puppy loaded |
314 | 0 | // already. |
315 | 0 | nsXULPrototypeDocument* proto = IsChromeURI(mDocumentURI) ? |
316 | 0 | nsXULPrototypeCache::GetInstance()->GetPrototype(mDocumentURI) : |
317 | 0 | nullptr; |
318 | 0 |
|
319 | 0 | // Same comment as nsChromeProtocolHandler::NewChannel and |
320 | 0 | // XULDocument::ResumeWalk |
321 | 0 | // - Ben Goodger |
322 | 0 | // |
323 | 0 | // We don't abort on failure here because there are too many valid |
324 | 0 | // cases that can return failure, and the null-ness of |proto| is enough |
325 | 0 | // to trigger the fail-safe parse-from-disk solution. Example failure cases |
326 | 0 | // (for reference) include: |
327 | 0 | // |
328 | 0 | // NS_ERROR_NOT_AVAILABLE: the URI cannot be found in the startup cache, |
329 | 0 | // parse from disk |
330 | 0 | // other: the startup cache file could not be found, probably |
331 | 0 | // due to being accessed before a profile has been selected (e.g. |
332 | 0 | // loading chrome for the profile manager itself). This must be |
333 | 0 | // parsed from disk. |
334 | 0 |
|
335 | 0 | if (proto) { |
336 | 0 | // If we're racing with another document to load proto, wait till the |
337 | 0 | // load has finished loading before trying to add cloned style sheets. |
338 | 0 | // XULDocument::EndLoad will call proto->NotifyLoadDone, which will |
339 | 0 | // find all racing documents and notify them via OnPrototypeLoadDone, |
340 | 0 | // which will add style sheet clones to each document. |
341 | 0 | bool loaded; |
342 | 0 | rv = proto->AwaitLoadDone(this, &loaded); |
343 | 0 | if (NS_FAILED(rv)) return rv; |
344 | 0 | |
345 | 0 | mCurrentPrototype = proto; |
346 | 0 |
|
347 | 0 | // Set up the right principal on ourselves. |
348 | 0 | SetPrincipal(proto->DocumentPrincipal()); |
349 | 0 |
|
350 | 0 | // We need a listener, even if proto is not yet loaded, in which |
351 | 0 | // event the listener's OnStopRequest method does nothing, and all |
352 | 0 | // the interesting work happens below XULDocument::EndLoad, from |
353 | 0 | // the call there to mCurrentPrototype->NotifyLoadDone(). |
354 | 0 | *aDocListener = new CachedChromeStreamListener(this, loaded); |
355 | 0 | } |
356 | 0 | else { |
357 | 0 | bool useXULCache = nsXULPrototypeCache::GetInstance()->IsEnabled(); |
358 | 0 | bool fillXULCache = (useXULCache && IsChromeURI(mDocumentURI)); |
359 | 0 |
|
360 | 0 |
|
361 | 0 | // It's just a vanilla document load. Create a parser to deal |
362 | 0 | // with the stream n' stuff. |
363 | 0 |
|
364 | 0 | nsCOMPtr<nsIParser> parser; |
365 | 0 | rv = PrepareToLoadPrototype(mDocumentURI, aCommand, principal, |
366 | 0 | getter_AddRefs(parser)); |
367 | 0 | if (NS_FAILED(rv)) return rv; |
368 | 0 | |
369 | 0 | // Predicate mIsWritingFastLoad on the XUL cache being enabled, |
370 | 0 | // so we don't have to re-check whether the cache is enabled all |
371 | 0 | // the time. |
372 | 0 | mIsWritingFastLoad = useXULCache; |
373 | 0 |
|
374 | 0 | nsCOMPtr<nsIStreamListener> listener = do_QueryInterface(parser, &rv); |
375 | 0 | NS_ASSERTION(NS_SUCCEEDED(rv), "parser doesn't support nsIStreamListener"); |
376 | 0 | if (NS_FAILED(rv)) return rv; |
377 | 0 | |
378 | 0 | *aDocListener = listener; |
379 | 0 |
|
380 | 0 | parser->Parse(mDocumentURI); |
381 | 0 |
|
382 | 0 | // Put the current prototype, created under PrepareToLoad, into the |
383 | 0 | // XUL prototype cache now. We can't do this under PrepareToLoad or |
384 | 0 | // overlay loading will break; search for PutPrototype in ResumeWalk |
385 | 0 | // and see the comment there. |
386 | 0 | if (fillXULCache) { |
387 | 0 | nsXULPrototypeCache::GetInstance()->PutPrototype(mCurrentPrototype); |
388 | 0 | } |
389 | 0 | } |
390 | 0 |
|
391 | 0 | NS_IF_ADDREF(*aDocListener); |
392 | 0 | return NS_OK; |
393 | 0 | } |
394 | | |
395 | | // This gets invoked after the prototype for this document is fully built in the |
396 | | // content sink. |
397 | | void |
398 | | XULDocument::EndLoad() |
399 | 0 | { |
400 | 0 | nsresult rv; |
401 | 0 |
|
402 | 0 | // Whack the prototype document into the cache so that the next |
403 | 0 | // time somebody asks for it, they don't need to load it by hand. |
404 | 0 |
|
405 | 0 | nsCOMPtr<nsIURI> uri = mCurrentPrototype->GetURI(); |
406 | 0 | bool isChrome = IsChromeURI(uri); |
407 | 0 |
|
408 | 0 | // Remember if the XUL cache is on |
409 | 0 | bool useXULCache = nsXULPrototypeCache::GetInstance()->IsEnabled(); |
410 | 0 |
|
411 | 0 | if (isChrome && useXULCache) { |
412 | 0 | // If it's a chrome prototype document, then notify any |
413 | 0 | // documents that raced to load the prototype, and awaited |
414 | 0 | // its load completion via proto->AwaitLoadDone(). |
415 | 0 | rv = mCurrentPrototype->NotifyLoadDone(); |
416 | 0 | if (NS_FAILED(rv)) return; |
417 | 0 | } |
418 | 0 | |
419 | 0 | OnPrototypeLoadDone(true); |
420 | 0 | if (MOZ_LOG_TEST(gXULLog, LogLevel::Warning)) { |
421 | 0 | nsAutoCString urlspec; |
422 | 0 | rv = uri->GetSpec(urlspec); |
423 | 0 | if (NS_SUCCEEDED(rv)) { |
424 | 0 | MOZ_LOG(gXULLog, LogLevel::Warning, |
425 | 0 | ("xul: Finished loading document '%s'", urlspec.get())); |
426 | 0 | } |
427 | 0 | } |
428 | 0 | } |
429 | | |
430 | | nsresult |
431 | | XULDocument::OnPrototypeLoadDone(bool aResumeWalk) |
432 | 0 | { |
433 | 0 | nsresult rv; |
434 | 0 |
|
435 | 0 | rv = PrepareToWalk(); |
436 | 0 | NS_ASSERTION(NS_SUCCEEDED(rv), "unable to prepare for walk"); |
437 | 0 | if (NS_FAILED(rv)) return rv; |
438 | 0 | |
439 | 0 | if (aResumeWalk) { |
440 | 0 | rv = ResumeWalk(); |
441 | 0 | } |
442 | 0 | return rv; |
443 | 0 | } |
444 | | |
445 | | static void |
446 | | ClearBroadcasterMapEntry(PLDHashTable* aTable, PLDHashEntryHdr* aEntry) |
447 | 0 | { |
448 | 0 | BroadcasterMapEntry* entry = |
449 | 0 | static_cast<BroadcasterMapEntry*>(aEntry); |
450 | 0 | for (size_t i = entry->mListeners.Length() - 1; i != (size_t)-1; --i) { |
451 | 0 | delete entry->mListeners[i]; |
452 | 0 | } |
453 | 0 | entry->mListeners.Clear(); |
454 | 0 |
|
455 | 0 | // N.B. that we need to manually run the dtor because we |
456 | 0 | // constructed the nsTArray object in-place. |
457 | 0 | entry->mListeners.~nsTArray<BroadcastListener*>(); |
458 | 0 | } |
459 | | |
460 | | static bool |
461 | | CanBroadcast(int32_t aNameSpaceID, nsAtom* aAttribute) |
462 | 0 | { |
463 | 0 | // Don't push changes to the |id|, |persist|, |command| or |
464 | 0 | // |observes| attribute. |
465 | 0 | if (aNameSpaceID == kNameSpaceID_None) { |
466 | 0 | if ((aAttribute == nsGkAtoms::id) || |
467 | 0 | (aAttribute == nsGkAtoms::persist) || |
468 | 0 | (aAttribute == nsGkAtoms::command) || |
469 | 0 | (aAttribute == nsGkAtoms::observes)) { |
470 | 0 | return false; |
471 | 0 | } |
472 | 0 | } |
473 | 0 | return true; |
474 | 0 | } |
475 | | |
476 | | struct nsAttrNameInfo |
477 | | { |
478 | | nsAttrNameInfo(int32_t aNamespaceID, nsAtom* aName, nsAtom* aPrefix) : |
479 | 0 | mNamespaceID(aNamespaceID), mName(aName), mPrefix(aPrefix) {} |
480 | | nsAttrNameInfo(const nsAttrNameInfo& aOther) : |
481 | | mNamespaceID(aOther.mNamespaceID), mName(aOther.mName), |
482 | 0 | mPrefix(aOther.mPrefix) {} |
483 | | int32_t mNamespaceID; |
484 | | RefPtr<nsAtom> mName; |
485 | | RefPtr<nsAtom> mPrefix; |
486 | | }; |
487 | | |
488 | | void |
489 | | XULDocument::SynchronizeBroadcastListener(Element *aBroadcaster, |
490 | | Element *aListener, |
491 | | const nsAString &aAttr) |
492 | 0 | { |
493 | 0 | if (!nsContentUtils::IsSafeToRunScript()) { |
494 | 0 | nsDelayedBroadcastUpdate delayedUpdate(aBroadcaster, aListener, |
495 | 0 | aAttr); |
496 | 0 | mDelayedBroadcasters.AppendElement(delayedUpdate); |
497 | 0 | MaybeBroadcast(); |
498 | 0 | return; |
499 | 0 | } |
500 | 0 | bool notify = mDocumentLoaded || mHandlingDelayedBroadcasters; |
501 | 0 |
|
502 | 0 | if (aAttr.EqualsLiteral("*")) { |
503 | 0 | uint32_t count = aBroadcaster->GetAttrCount(); |
504 | 0 | nsTArray<nsAttrNameInfo> attributes(count); |
505 | 0 | for (uint32_t i = 0; i < count; ++i) { |
506 | 0 | const nsAttrName* attrName = aBroadcaster->GetAttrNameAt(i); |
507 | 0 | int32_t nameSpaceID = attrName->NamespaceID(); |
508 | 0 | nsAtom* name = attrName->LocalName(); |
509 | 0 |
|
510 | 0 | // _Don't_ push the |id|, |ref|, or |persist| attribute's value! |
511 | 0 | if (! CanBroadcast(nameSpaceID, name)) |
512 | 0 | continue; |
513 | 0 | |
514 | 0 | attributes.AppendElement(nsAttrNameInfo(nameSpaceID, name, |
515 | 0 | attrName->GetPrefix())); |
516 | 0 | } |
517 | 0 |
|
518 | 0 | count = attributes.Length(); |
519 | 0 | while (count-- > 0) { |
520 | 0 | int32_t nameSpaceID = attributes[count].mNamespaceID; |
521 | 0 | nsAtom* name = attributes[count].mName; |
522 | 0 | nsAutoString value; |
523 | 0 | if (aBroadcaster->GetAttr(nameSpaceID, name, value)) { |
524 | 0 | aListener->SetAttr(nameSpaceID, name, attributes[count].mPrefix, |
525 | 0 | value, notify); |
526 | 0 | } |
527 | 0 |
|
528 | | #if 0 |
529 | | // XXX we don't fire the |onbroadcast| handler during |
530 | | // initial hookup: doing so would potentially run the |
531 | | // |onbroadcast| handler before the |onload| handler, |
532 | | // which could define JS properties that mask XBL |
533 | | // properties, etc. |
534 | | ExecuteOnBroadcastHandlerFor(aBroadcaster, aListener, name); |
535 | | #endif |
536 | | } |
537 | 0 | } |
538 | 0 | else { |
539 | 0 | // Find out if the attribute is even present at all. |
540 | 0 | RefPtr<nsAtom> name = NS_Atomize(aAttr); |
541 | 0 |
|
542 | 0 | nsAutoString value; |
543 | 0 | if (aBroadcaster->GetAttr(kNameSpaceID_None, name, value)) { |
544 | 0 | aListener->SetAttr(kNameSpaceID_None, name, value, notify); |
545 | 0 | } else { |
546 | 0 | aListener->UnsetAttr(kNameSpaceID_None, name, notify); |
547 | 0 | } |
548 | 0 |
|
549 | | #if 0 |
550 | | // XXX we don't fire the |onbroadcast| handler during initial |
551 | | // hookup: doing so would potentially run the |onbroadcast| |
552 | | // handler before the |onload| handler, which could define JS |
553 | | // properties that mask XBL properties, etc. |
554 | | ExecuteOnBroadcastHandlerFor(aBroadcaster, aListener, name); |
555 | | #endif |
556 | | } |
557 | 0 | } |
558 | | |
559 | | void |
560 | | XULDocument::AddBroadcastListenerFor(Element& aBroadcaster, Element& aListener, |
561 | | const nsAString& aAttr, ErrorResult& aRv) |
562 | 0 | { |
563 | 0 | nsresult rv = |
564 | 0 | nsContentUtils::CheckSameOrigin(this, &aBroadcaster); |
565 | 0 |
|
566 | 0 | if (NS_FAILED(rv)) { |
567 | 0 | aRv.Throw(rv); |
568 | 0 | return; |
569 | 0 | } |
570 | 0 | |
571 | 0 | rv = nsContentUtils::CheckSameOrigin(this, &aListener); |
572 | 0 |
|
573 | 0 | if (NS_FAILED(rv)) { |
574 | 0 | aRv.Throw(rv); |
575 | 0 | return; |
576 | 0 | } |
577 | 0 | |
578 | 0 | static const PLDHashTableOps gOps = { |
579 | 0 | PLDHashTable::HashVoidPtrKeyStub, |
580 | 0 | PLDHashTable::MatchEntryStub, |
581 | 0 | PLDHashTable::MoveEntryStub, |
582 | 0 | ClearBroadcasterMapEntry, |
583 | 0 | nullptr |
584 | 0 | }; |
585 | 0 |
|
586 | 0 | if (! mBroadcasterMap) { |
587 | 0 | mBroadcasterMap = new PLDHashTable(&gOps, sizeof(BroadcasterMapEntry)); |
588 | 0 | } |
589 | 0 |
|
590 | 0 | auto entry = static_cast<BroadcasterMapEntry*> |
591 | 0 | (mBroadcasterMap->Search(&aBroadcaster)); |
592 | 0 | if (!entry) { |
593 | 0 | entry = static_cast<BroadcasterMapEntry*> |
594 | 0 | (mBroadcasterMap->Add(&aBroadcaster, fallible)); |
595 | 0 |
|
596 | 0 | if (! entry) { |
597 | 0 | aRv.Throw(NS_ERROR_OUT_OF_MEMORY); |
598 | 0 | return; |
599 | 0 | } |
600 | 0 | |
601 | 0 | entry->mBroadcaster = &aBroadcaster; |
602 | 0 |
|
603 | 0 | // N.B. placement new to construct the nsTArray object in-place |
604 | 0 | new (&entry->mListeners) nsTArray<BroadcastListener*>(); |
605 | 0 | } |
606 | 0 |
|
607 | 0 | // Only add the listener if it's not there already! |
608 | 0 | RefPtr<nsAtom> attr = NS_Atomize(aAttr); |
609 | 0 |
|
610 | 0 | for (size_t i = entry->mListeners.Length() - 1; i != (size_t)-1; --i) { |
611 | 0 | BroadcastListener* bl = entry->mListeners[i]; |
612 | 0 | nsCOMPtr<Element> blListener = do_QueryReferent(bl->mListener); |
613 | 0 |
|
614 | 0 | if (blListener == &aListener && bl->mAttribute == attr) |
615 | 0 | return; |
616 | 0 | } |
617 | 0 |
|
618 | 0 | BroadcastListener* bl = new BroadcastListener; |
619 | 0 | bl->mListener = do_GetWeakReference(&aListener); |
620 | 0 | bl->mAttribute = attr; |
621 | 0 |
|
622 | 0 | entry->mListeners.AppendElement(bl); |
623 | 0 |
|
624 | 0 | SynchronizeBroadcastListener(&aBroadcaster, &aListener, aAttr); |
625 | 0 | } |
626 | | |
627 | | void |
628 | | XULDocument::RemoveBroadcastListenerFor(Element& aBroadcaster, |
629 | | Element& aListener, |
630 | | const nsAString& aAttr) |
631 | 0 | { |
632 | 0 | // If we haven't added any broadcast listeners, then there sure |
633 | 0 | // aren't any to remove. |
634 | 0 | if (! mBroadcasterMap) |
635 | 0 | return; |
636 | 0 | |
637 | 0 | auto entry = static_cast<BroadcasterMapEntry*> |
638 | 0 | (mBroadcasterMap->Search(&aBroadcaster)); |
639 | 0 | if (entry) { |
640 | 0 | RefPtr<nsAtom> attr = NS_Atomize(aAttr); |
641 | 0 | for (size_t i = entry->mListeners.Length() - 1; i != (size_t)-1; --i) { |
642 | 0 | BroadcastListener* bl = entry->mListeners[i]; |
643 | 0 | nsCOMPtr<Element> blListener = do_QueryReferent(bl->mListener); |
644 | 0 |
|
645 | 0 | if (blListener == &aListener && bl->mAttribute == attr) { |
646 | 0 | entry->mListeners.RemoveElementAt(i); |
647 | 0 | delete bl; |
648 | 0 |
|
649 | 0 | if (entry->mListeners.IsEmpty()) |
650 | 0 | mBroadcasterMap->RemoveEntry(entry); |
651 | 0 |
|
652 | 0 | break; |
653 | 0 | } |
654 | 0 | } |
655 | 0 | } |
656 | 0 | } |
657 | | |
658 | | nsresult |
659 | | XULDocument::ExecuteOnBroadcastHandlerFor(Element* aBroadcaster, |
660 | | Element* aListener, |
661 | | nsAtom* aAttr) |
662 | 0 | { |
663 | 0 | // Now we execute the onchange handler in the context of the |
664 | 0 | // observer. We need to find the observer in order to |
665 | 0 | // execute the handler. |
666 | 0 |
|
667 | 0 | for (nsIContent* child = aListener->GetFirstChild(); |
668 | 0 | child; |
669 | 0 | child = child->GetNextSibling()) { |
670 | 0 |
|
671 | 0 | // Look for an <observes> element beneath the listener. This |
672 | 0 | // ought to have an |element| attribute that refers to |
673 | 0 | // aBroadcaster, and an |attribute| element that tells us what |
674 | 0 | // attriubtes we're listening for. |
675 | 0 | if (!child->IsXULElement(nsGkAtoms::observes)) |
676 | 0 | continue; |
677 | 0 | |
678 | 0 | // Is this the element that was listening to us? |
679 | 0 | nsAutoString listeningToID; |
680 | 0 | child->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::element, listeningToID); |
681 | 0 |
|
682 | 0 | nsAutoString broadcasterID; |
683 | 0 | aBroadcaster->GetAttr(kNameSpaceID_None, nsGkAtoms::id, broadcasterID); |
684 | 0 |
|
685 | 0 | if (listeningToID != broadcasterID) |
686 | 0 | continue; |
687 | 0 | |
688 | 0 | // We are observing the broadcaster, but is this the right |
689 | 0 | // attribute? |
690 | 0 | nsAutoString listeningToAttribute; |
691 | 0 | child->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::attribute, |
692 | 0 | listeningToAttribute); |
693 | 0 |
|
694 | 0 | if (!aAttr->Equals(listeningToAttribute) && |
695 | 0 | !listeningToAttribute.EqualsLiteral("*")) { |
696 | 0 | continue; |
697 | 0 | } |
698 | 0 | |
699 | 0 | // This is the right <observes> element. Execute the |
700 | 0 | // |onbroadcast| event handler |
701 | 0 | WidgetEvent event(true, eXULBroadcast); |
702 | 0 |
|
703 | 0 | RefPtr<nsPresContext> presContext = GetPresContext(); |
704 | 0 | if (presContext) { |
705 | 0 | // Handle the DOM event |
706 | 0 | nsEventStatus status = nsEventStatus_eIgnore; |
707 | 0 | EventDispatcher::Dispatch(child, presContext, &event, nullptr, |
708 | 0 | &status); |
709 | 0 | } |
710 | 0 | } |
711 | 0 |
|
712 | 0 | return NS_OK; |
713 | 0 | } |
714 | | |
715 | | static bool |
716 | | ShouldPersistAttribute(Element* aElement, nsAtom* aAttribute) |
717 | 0 | { |
718 | 0 | if (aElement->IsXULElement(nsGkAtoms::window)) { |
719 | 0 | // This is not an element of the top document, its owner is |
720 | 0 | // not an nsXULWindow. Persist it. |
721 | 0 | if (aElement->OwnerDoc()->GetParentDocument()) { |
722 | 0 | return true; |
723 | 0 | } |
724 | 0 | // The following attributes of xul:window should be handled in |
725 | 0 | // nsXULWindow::SavePersistentAttributes instead of here. |
726 | 0 | if (aAttribute == nsGkAtoms::screenX || |
727 | 0 | aAttribute == nsGkAtoms::screenY || |
728 | 0 | aAttribute == nsGkAtoms::width || |
729 | 0 | aAttribute == nsGkAtoms::height || |
730 | 0 | aAttribute == nsGkAtoms::sizemode) { |
731 | 0 | return false; |
732 | 0 | } |
733 | 0 | } |
734 | 0 | return true; |
735 | 0 | } |
736 | | |
737 | | void |
738 | | XULDocument::AttributeChanged(Element* aElement, int32_t aNameSpaceID, |
739 | | nsAtom* aAttribute, int32_t aModType, |
740 | | const nsAttrValue* aOldValue) |
741 | 0 | { |
742 | 0 | NS_ASSERTION(aElement->OwnerDoc() == this, "unexpected doc"); |
743 | 0 |
|
744 | 0 | // Might not need this, but be safe for now. |
745 | 0 | nsCOMPtr<nsIMutationObserver> kungFuDeathGrip(this); |
746 | 0 |
|
747 | 0 | // Synchronize broadcast listeners |
748 | 0 | if (mBroadcasterMap && |
749 | 0 | CanBroadcast(aNameSpaceID, aAttribute)) { |
750 | 0 | auto entry = static_cast<BroadcasterMapEntry*> |
751 | 0 | (mBroadcasterMap->Search(aElement)); |
752 | 0 |
|
753 | 0 | if (entry) { |
754 | 0 | // We've got listeners: push the value. |
755 | 0 | nsAutoString value; |
756 | 0 | bool attrSet = aElement->GetAttr(kNameSpaceID_None, aAttribute, value); |
757 | 0 |
|
758 | 0 | for (size_t i = entry->mListeners.Length() - 1; i != (size_t)-1; --i) { |
759 | 0 | BroadcastListener* bl = entry->mListeners[i]; |
760 | 0 | if ((bl->mAttribute == aAttribute) || |
761 | 0 | (bl->mAttribute == nsGkAtoms::_asterisk)) { |
762 | 0 | nsCOMPtr<Element> listenerEl |
763 | 0 | = do_QueryReferent(bl->mListener); |
764 | 0 | if (listenerEl) { |
765 | 0 | nsAutoString currentValue; |
766 | 0 | bool hasAttr = listenerEl->GetAttr(kNameSpaceID_None, |
767 | 0 | aAttribute, |
768 | 0 | currentValue); |
769 | 0 | // We need to update listener only if we're |
770 | 0 | // (1) removing an existing attribute, |
771 | 0 | // (2) adding a new attribute or |
772 | 0 | // (3) changing the value of an attribute. |
773 | 0 | bool needsAttrChange = |
774 | 0 | attrSet != hasAttr || !value.Equals(currentValue); |
775 | 0 | nsDelayedBroadcastUpdate delayedUpdate(aElement, |
776 | 0 | listenerEl, |
777 | 0 | aAttribute, |
778 | 0 | value, |
779 | 0 | attrSet, |
780 | 0 | needsAttrChange); |
781 | 0 |
|
782 | 0 | size_t index = |
783 | 0 | mDelayedAttrChangeBroadcasts.IndexOf(delayedUpdate, |
784 | 0 | 0, nsDelayedBroadcastUpdate::Comparator()); |
785 | 0 | if (index != mDelayedAttrChangeBroadcasts.NoIndex) { |
786 | 0 | if (mHandlingDelayedAttrChange) { |
787 | 0 | NS_WARNING("Broadcasting loop!"); |
788 | 0 | continue; |
789 | 0 | } |
790 | 0 | mDelayedAttrChangeBroadcasts.RemoveElementAt(index); |
791 | 0 | } |
792 | 0 |
|
793 | 0 | mDelayedAttrChangeBroadcasts.AppendElement(delayedUpdate); |
794 | 0 | } |
795 | 0 | } |
796 | 0 | } |
797 | 0 | } |
798 | 0 | } |
799 | 0 |
|
800 | 0 | // checks for modifications in broadcasters |
801 | 0 | CheckBroadcasterHookup(aElement); |
802 | 0 |
|
803 | 0 | // See if there is anything we need to persist in the localstore. |
804 | 0 | // |
805 | 0 | // XXX Namespace handling broken :-( |
806 | 0 | nsAutoString persist; |
807 | 0 | aElement->GetAttr(kNameSpaceID_None, nsGkAtoms::persist, persist); |
808 | 0 | // Persistence of attributes of xul:window is handled in nsXULWindow. |
809 | 0 | if (ShouldPersistAttribute(aElement, aAttribute) && !persist.IsEmpty() && |
810 | 0 | // XXXldb This should check that it's a token, not just a substring. |
811 | 0 | persist.Find(nsDependentAtomString(aAttribute)) >= 0) { |
812 | 0 | nsContentUtils::AddScriptRunner( |
813 | 0 | NewRunnableMethod<Element*, int32_t, nsAtom*>( |
814 | 0 | "dom::XULDocument::Persist", |
815 | 0 | this, |
816 | 0 | &XULDocument::Persist, |
817 | 0 | aElement, |
818 | 0 | kNameSpaceID_None, |
819 | 0 | aAttribute)); |
820 | 0 | } |
821 | 0 | } |
822 | | |
823 | | void |
824 | | XULDocument::ContentAppended(nsIContent* aFirstNewContent) |
825 | 0 | { |
826 | 0 | NS_ASSERTION(aFirstNewContent->OwnerDoc() == this, "unexpected doc"); |
827 | 0 |
|
828 | 0 | // Might not need this, but be safe for now. |
829 | 0 | nsCOMPtr<nsIMutationObserver> kungFuDeathGrip(this); |
830 | 0 |
|
831 | 0 | // Update our element map |
832 | 0 | nsresult rv = NS_OK; |
833 | 0 | for (nsIContent* cur = aFirstNewContent; cur && NS_SUCCEEDED(rv); |
834 | 0 | cur = cur->GetNextSibling()) { |
835 | 0 | rv = AddSubtreeToDocument(cur); |
836 | 0 | } |
837 | 0 | } |
838 | | |
839 | | void |
840 | | XULDocument::ContentInserted(nsIContent* aChild) |
841 | 0 | { |
842 | 0 | NS_ASSERTION(aChild->OwnerDoc() == this, "unexpected doc"); |
843 | 0 |
|
844 | 0 | // Might not need this, but be safe for now. |
845 | 0 | nsCOMPtr<nsIMutationObserver> kungFuDeathGrip(this); |
846 | 0 |
|
847 | 0 | AddSubtreeToDocument(aChild); |
848 | 0 | } |
849 | | |
850 | | void |
851 | | XULDocument::ContentRemoved(nsIContent* aChild, nsIContent* aPreviousSibling) |
852 | 0 | { |
853 | 0 | NS_ASSERTION(aChild->OwnerDoc() == this, "unexpected doc"); |
854 | 0 |
|
855 | 0 | // Might not need this, but be safe for now. |
856 | 0 | nsCOMPtr<nsIMutationObserver> kungFuDeathGrip(this); |
857 | 0 |
|
858 | 0 | RemoveSubtreeFromDocument(aChild); |
859 | 0 | } |
860 | | |
861 | | //---------------------------------------------------------------------- |
862 | | // |
863 | | // nsIDocument interface |
864 | | // |
865 | | |
866 | | |
867 | | void |
868 | | XULDocument::Persist(Element* aElement, int32_t aNameSpaceID, |
869 | | nsAtom* aAttribute) |
870 | 0 | { |
871 | 0 | // For non-chrome documents, persistance is simply broken |
872 | 0 | if (!nsContentUtils::IsSystemPrincipal(NodePrincipal())) |
873 | 0 | return; |
874 | 0 | |
875 | 0 | if (!mLocalStore) { |
876 | 0 | mLocalStore = do_GetService("@mozilla.org/xul/xulstore;1"); |
877 | 0 | if (NS_WARN_IF(!mLocalStore)) { |
878 | 0 | return; |
879 | 0 | } |
880 | 0 | } |
881 | 0 | |
882 | 0 | nsAutoString id; |
883 | 0 |
|
884 | 0 | aElement->GetAttr(kNameSpaceID_None, nsGkAtoms::id, id); |
885 | 0 | nsAtomString attrstr(aAttribute); |
886 | 0 |
|
887 | 0 | nsAutoString valuestr; |
888 | 0 | aElement->GetAttr(kNameSpaceID_None, aAttribute, valuestr); |
889 | 0 |
|
890 | 0 | nsAutoCString utf8uri; |
891 | 0 | nsresult rv = mDocumentURI->GetSpec(utf8uri); |
892 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
893 | 0 | return; |
894 | 0 | } |
895 | 0 | NS_ConvertUTF8toUTF16 uri(utf8uri); |
896 | 0 |
|
897 | 0 | bool hasAttr; |
898 | 0 | rv = mLocalStore->HasValue(uri, id, attrstr, &hasAttr); |
899 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
900 | 0 | return; |
901 | 0 | } |
902 | 0 | |
903 | 0 | if (hasAttr && valuestr.IsEmpty()) { |
904 | 0 | mLocalStore->RemoveValue(uri, id, attrstr); |
905 | 0 | return; |
906 | 0 | } |
907 | 0 | |
908 | 0 | // Persisting attributes to top level windows is handled by nsXULWindow. |
909 | 0 | if (aElement->IsXULElement(nsGkAtoms::window)) { |
910 | 0 | if (nsCOMPtr<nsIXULWindow> win = GetXULWindowIfToplevelChrome()) { |
911 | 0 | return; |
912 | 0 | } |
913 | 0 | } |
914 | 0 | |
915 | 0 | mLocalStore->SetValue(uri, id, attrstr, valuestr); |
916 | 0 | } |
917 | | |
918 | | nsresult |
919 | | XULDocument::AddElementToDocumentPre(Element* aElement) |
920 | 0 | { |
921 | 0 | // Do a bunch of work that's necessary when an element gets added |
922 | 0 | // to the XUL Document. |
923 | 0 | nsresult rv; |
924 | 0 |
|
925 | 0 | // 1. Add the element to the id map, since it seems this can be |
926 | 0 | // called when creating elements from prototypes. |
927 | 0 | nsAtom* id = aElement->GetID(); |
928 | 0 | if (id) { |
929 | 0 | // FIXME: Shouldn't BindToTree take care of this? |
930 | 0 | nsAutoScriptBlocker scriptBlocker; |
931 | 0 | AddToIdTable(aElement, id); |
932 | 0 | } |
933 | 0 |
|
934 | 0 | // 2. Check for a broadcaster hookup attribute, in which case |
935 | 0 | // we'll hook the node up as a listener on a broadcaster. |
936 | 0 | rv = CheckBroadcasterHookup(aElement); |
937 | 0 | if (NS_FAILED(rv)) return rv; |
938 | 0 | |
939 | 0 | return NS_OK; |
940 | 0 | } |
941 | | |
942 | | nsresult |
943 | | XULDocument::AddElementToDocumentPost(Element* aElement) |
944 | 0 | { |
945 | 0 | if (aElement == GetRootElement()) { |
946 | 0 | ResetDocumentDirection(); |
947 | 0 | } |
948 | 0 |
|
949 | 0 | if (aElement->IsXULElement(nsGkAtoms::link)) { |
950 | 0 | LocalizationLinkAdded(aElement); |
951 | 0 | } else if (aElement->IsXULElement(nsGkAtoms::linkset)) { |
952 | 0 | OnL10nResourceContainerParsed(); |
953 | 0 | } |
954 | 0 |
|
955 | 0 | return NS_OK; |
956 | 0 | } |
957 | | |
958 | | nsresult |
959 | | XULDocument::AddSubtreeToDocument(nsIContent* aContent) |
960 | 0 | { |
961 | 0 | NS_ASSERTION(aContent->GetUncomposedDoc() == this, "Element not in doc!"); |
962 | 0 | // From here on we only care about elements. |
963 | 0 | Element* aElement = Element::FromNode(aContent); |
964 | 0 | if (!aElement) { |
965 | 0 | return NS_OK; |
966 | 0 | } |
967 | 0 | |
968 | 0 | // Do pre-order addition magic |
969 | 0 | nsresult rv = AddElementToDocumentPre(aElement); |
970 | 0 | if (NS_FAILED(rv)) return rv; |
971 | 0 | |
972 | 0 | // Recurse to children |
973 | 0 | for (nsIContent* child = aElement->GetLastChild(); |
974 | 0 | child; |
975 | 0 | child = child->GetPreviousSibling()) { |
976 | 0 |
|
977 | 0 | rv = AddSubtreeToDocument(child); |
978 | 0 | if (NS_FAILED(rv)) |
979 | 0 | return rv; |
980 | 0 | } |
981 | 0 |
|
982 | 0 | // Do post-order addition magic |
983 | 0 | return AddElementToDocumentPost(aElement); |
984 | 0 | } |
985 | | |
986 | | nsresult |
987 | | XULDocument::RemoveSubtreeFromDocument(nsIContent* aContent) |
988 | 0 | { |
989 | 0 | // From here on we only care about elements. |
990 | 0 | Element* aElement = Element::FromNode(aContent); |
991 | 0 | if (!aElement) { |
992 | 0 | return NS_OK; |
993 | 0 | } |
994 | 0 | |
995 | 0 | // Do a bunch of cleanup to remove an element from the XUL |
996 | 0 | // document. |
997 | 0 | nsresult rv; |
998 | 0 |
|
999 | 0 | // Remove any children from the document. |
1000 | 0 | for (nsIContent* child = aElement->GetLastChild(); |
1001 | 0 | child; |
1002 | 0 | child = child->GetPreviousSibling()) { |
1003 | 0 |
|
1004 | 0 | rv = RemoveSubtreeFromDocument(child); |
1005 | 0 | if (NS_FAILED(rv)) |
1006 | 0 | return rv; |
1007 | 0 | } |
1008 | 0 |
|
1009 | 0 | // Remove the element from the id map, since we added it in |
1010 | 0 | // AddElementToDocumentPre(). |
1011 | 0 | nsAtom* id = aElement->GetID(); |
1012 | 0 | if (id) { |
1013 | 0 | // FIXME: Shouldn't UnbindFromTree take care of this? |
1014 | 0 | nsAutoScriptBlocker scriptBlocker; |
1015 | 0 | RemoveFromIdTable(aElement, id); |
1016 | 0 | } |
1017 | 0 |
|
1018 | 0 | // Remove the element from our broadcaster map, since it is no longer |
1019 | 0 | // in the document. |
1020 | 0 | nsCOMPtr<Element> broadcaster, listener; |
1021 | 0 | nsAutoString attribute, broadcasterID; |
1022 | 0 | rv = FindBroadcaster(aElement, getter_AddRefs(listener), |
1023 | 0 | broadcasterID, attribute, getter_AddRefs(broadcaster)); |
1024 | 0 | if (rv == NS_FINDBROADCASTER_FOUND) { |
1025 | 0 | RemoveBroadcastListenerFor(*broadcaster, *listener, attribute); |
1026 | 0 | } |
1027 | 0 |
|
1028 | 0 | return NS_OK; |
1029 | 0 | } |
1030 | | |
1031 | | //---------------------------------------------------------------------- |
1032 | | // |
1033 | | // nsINode interface |
1034 | | // |
1035 | | |
1036 | | nsresult |
1037 | | XULDocument::Clone(mozilla::dom::NodeInfo* aNodeInfo, nsINode** aResult) const |
1038 | 0 | { |
1039 | 0 | // We don't allow cloning of a XUL document |
1040 | 0 | *aResult = nullptr; |
1041 | 0 | return NS_ERROR_DOM_NOT_SUPPORTED_ERR; |
1042 | 0 | } |
1043 | | |
1044 | | |
1045 | | //---------------------------------------------------------------------- |
1046 | | // |
1047 | | // Implementation methods |
1048 | | // |
1049 | | |
1050 | | nsresult |
1051 | | XULDocument::Init() |
1052 | 0 | { |
1053 | 0 | nsresult rv = XMLDocument::Init(); |
1054 | 0 | NS_ENSURE_SUCCESS(rv, rv); |
1055 | 0 |
|
1056 | 0 | if (gRefCnt++ == 0) { |
1057 | 0 | // ensure that the XUL prototype cache is instantiated successfully, |
1058 | 0 | // so that we can use nsXULPrototypeCache::GetInstance() without |
1059 | 0 | // null-checks in the rest of the class. |
1060 | 0 | nsXULPrototypeCache* cache = nsXULPrototypeCache::GetInstance(); |
1061 | 0 | if (!cache) { |
1062 | 0 | NS_ERROR("Could not instantiate nsXULPrototypeCache"); |
1063 | 0 | return NS_ERROR_FAILURE; |
1064 | 0 | } |
1065 | 0 | } |
1066 | 0 |
|
1067 | 0 | Preferences::RegisterCallback(XULDocument::DirectionChanged, |
1068 | 0 | "intl.uidirection", this); |
1069 | 0 |
|
1070 | 0 | return NS_OK; |
1071 | 0 | } |
1072 | | |
1073 | | |
1074 | | nsresult |
1075 | | XULDocument::StartLayout(void) |
1076 | 0 | { |
1077 | 0 | mMayStartLayout = true; |
1078 | 0 | nsCOMPtr<nsIPresShell> shell = GetShell(); |
1079 | 0 | if (shell) { |
1080 | 0 | // Resize-reflow this time |
1081 | 0 | nsPresContext *cx = shell->GetPresContext(); |
1082 | 0 | NS_ASSERTION(cx != nullptr, "no pres context"); |
1083 | 0 | if (! cx) |
1084 | 0 | return NS_ERROR_UNEXPECTED; |
1085 | 0 | |
1086 | 0 | nsCOMPtr<nsIDocShell> docShell = cx->GetDocShell(); |
1087 | 0 | NS_ASSERTION(docShell != nullptr, "container is not a docshell"); |
1088 | 0 | if (! docShell) |
1089 | 0 | return NS_ERROR_UNEXPECTED; |
1090 | 0 | |
1091 | 0 | nsresult rv = shell->Initialize(); |
1092 | 0 | NS_ENSURE_SUCCESS(rv, rv); |
1093 | 0 | } |
1094 | 0 |
|
1095 | 0 | return NS_OK; |
1096 | 0 | } |
1097 | | |
1098 | | nsresult |
1099 | | XULDocument::PrepareToLoadPrototype(nsIURI* aURI, const char* aCommand, |
1100 | | nsIPrincipal* aDocumentPrincipal, |
1101 | | nsIParser** aResult) |
1102 | 0 | { |
1103 | 0 | nsresult rv; |
1104 | 0 |
|
1105 | 0 | // Create a new prototype document. |
1106 | 0 | rv = NS_NewXULPrototypeDocument(getter_AddRefs(mCurrentPrototype)); |
1107 | 0 | if (NS_FAILED(rv)) return rv; |
1108 | 0 | |
1109 | 0 | rv = mCurrentPrototype->InitPrincipal(aURI, aDocumentPrincipal); |
1110 | 0 | if (NS_FAILED(rv)) { |
1111 | 0 | mCurrentPrototype = nullptr; |
1112 | 0 | return rv; |
1113 | 0 | } |
1114 | 0 | |
1115 | 0 | SetPrincipal(aDocumentPrincipal); |
1116 | 0 |
|
1117 | 0 | // Create a XUL content sink, a parser, and kick off a load for |
1118 | 0 | // the document. |
1119 | 0 | RefPtr<XULContentSinkImpl> sink = new XULContentSinkImpl(); |
1120 | 0 |
|
1121 | 0 | rv = sink->Init(this, mCurrentPrototype); |
1122 | 0 | NS_ASSERTION(NS_SUCCEEDED(rv), "Unable to initialize datasource sink"); |
1123 | 0 | if (NS_FAILED(rv)) return rv; |
1124 | 0 | |
1125 | 0 | nsCOMPtr<nsIParser> parser = do_CreateInstance(kParserCID, &rv); |
1126 | 0 | NS_ASSERTION(NS_SUCCEEDED(rv), "unable to create parser"); |
1127 | 0 | if (NS_FAILED(rv)) return rv; |
1128 | 0 | |
1129 | 0 | parser->SetCommand(nsCRT::strcmp(aCommand, "view-source") ? eViewNormal : |
1130 | 0 | eViewSource); |
1131 | 0 |
|
1132 | 0 | parser->SetDocumentCharset(UTF_8_ENCODING, |
1133 | 0 | kCharsetFromDocTypeDefault); |
1134 | 0 | parser->SetContentSink(sink); // grabs a reference to the parser |
1135 | 0 |
|
1136 | 0 | parser.forget(aResult); |
1137 | 0 | return NS_OK; |
1138 | 0 | } |
1139 | | |
1140 | | |
1141 | | nsresult |
1142 | | XULDocument::ApplyPersistentAttributes() |
1143 | 0 | { |
1144 | 0 | // For non-chrome documents, persistance is simply broken |
1145 | 0 | if (!nsContentUtils::IsSystemPrincipal(NodePrincipal())) |
1146 | 0 | return NS_ERROR_NOT_AVAILABLE; |
1147 | 0 | |
1148 | 0 | // Add all of the 'persisted' attributes into the content |
1149 | 0 | // model. |
1150 | 0 | if (!mLocalStore) { |
1151 | 0 | mLocalStore = do_GetService("@mozilla.org/xul/xulstore;1"); |
1152 | 0 | if (NS_WARN_IF(!mLocalStore)) { |
1153 | 0 | return NS_ERROR_NOT_INITIALIZED; |
1154 | 0 | } |
1155 | 0 | } |
1156 | 0 | |
1157 | 0 | mApplyingPersistedAttrs = true; |
1158 | 0 | ApplyPersistentAttributesInternal(); |
1159 | 0 | mApplyingPersistedAttrs = false; |
1160 | 0 |
|
1161 | 0 | return NS_OK; |
1162 | 0 | } |
1163 | | |
1164 | | |
1165 | | nsresult |
1166 | | XULDocument::ApplyPersistentAttributesInternal() |
1167 | 0 | { |
1168 | 0 | nsCOMArray<Element> elements; |
1169 | 0 |
|
1170 | 0 | nsAutoCString utf8uri; |
1171 | 0 | nsresult rv = mDocumentURI->GetSpec(utf8uri); |
1172 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
1173 | 0 | return rv; |
1174 | 0 | } |
1175 | 0 | NS_ConvertUTF8toUTF16 uri(utf8uri); |
1176 | 0 |
|
1177 | 0 | // Get a list of element IDs for which persisted values are available |
1178 | 0 | nsCOMPtr<nsIStringEnumerator> ids; |
1179 | 0 | rv = mLocalStore->GetIDsEnumerator(uri, getter_AddRefs(ids)); |
1180 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
1181 | 0 | return rv; |
1182 | 0 | } |
1183 | 0 | |
1184 | 0 | while (1) { |
1185 | 0 | bool hasmore = false; |
1186 | 0 | ids->HasMore(&hasmore); |
1187 | 0 | if (!hasmore) { |
1188 | 0 | break; |
1189 | 0 | } |
1190 | 0 | |
1191 | 0 | nsAutoString id; |
1192 | 0 | ids->GetNext(id); |
1193 | 0 |
|
1194 | 0 | nsIdentifierMapEntry* entry = mIdentifierMap.GetEntry(id); |
1195 | 0 | if (!entry) { |
1196 | 0 | continue; |
1197 | 0 | } |
1198 | 0 | |
1199 | 0 | // We want to hold strong refs to the elements while applying |
1200 | 0 | // persistent attributes, just in case. |
1201 | 0 | elements.Clear(); |
1202 | 0 | elements.SetCapacity(entry->GetIdElements().Length()); |
1203 | 0 | for (Element* element : entry->GetIdElements()) { |
1204 | 0 | elements.AppendObject(element); |
1205 | 0 | } |
1206 | 0 | if (elements.IsEmpty()) { |
1207 | 0 | continue; |
1208 | 0 | } |
1209 | 0 | |
1210 | 0 | rv = ApplyPersistentAttributesToElements(id, elements); |
1211 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
1212 | 0 | return rv; |
1213 | 0 | } |
1214 | 0 | } |
1215 | 0 |
|
1216 | 0 | return NS_OK; |
1217 | 0 | } |
1218 | | |
1219 | | nsresult |
1220 | | XULDocument::ApplyPersistentAttributesToElements(const nsAString &aID, |
1221 | | nsCOMArray<Element>& aElements) |
1222 | 0 | { |
1223 | 0 | nsAutoCString utf8uri; |
1224 | 0 | nsresult rv = mDocumentURI->GetSpec(utf8uri); |
1225 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
1226 | 0 | return rv; |
1227 | 0 | } |
1228 | 0 | NS_ConvertUTF8toUTF16 uri(utf8uri); |
1229 | 0 |
|
1230 | 0 | // Get a list of attributes for which persisted values are available |
1231 | 0 | nsCOMPtr<nsIStringEnumerator> attrs; |
1232 | 0 | rv = mLocalStore->GetAttributeEnumerator(uri, aID, getter_AddRefs(attrs)); |
1233 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
1234 | 0 | return rv; |
1235 | 0 | } |
1236 | 0 | |
1237 | 0 | while (1) { |
1238 | 0 | bool hasmore = PR_FALSE; |
1239 | 0 | attrs->HasMore(&hasmore); |
1240 | 0 | if (!hasmore) { |
1241 | 0 | break; |
1242 | 0 | } |
1243 | 0 | |
1244 | 0 | nsAutoString attrstr; |
1245 | 0 | attrs->GetNext(attrstr); |
1246 | 0 |
|
1247 | 0 | nsAutoString value; |
1248 | 0 | rv = mLocalStore->GetValue(uri, aID, attrstr, value); |
1249 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
1250 | 0 | return rv; |
1251 | 0 | } |
1252 | 0 | |
1253 | 0 | RefPtr<nsAtom> attr = NS_Atomize(attrstr); |
1254 | 0 | if (NS_WARN_IF(!attr)) { |
1255 | 0 | return NS_ERROR_OUT_OF_MEMORY; |
1256 | 0 | } |
1257 | 0 | |
1258 | 0 | uint32_t cnt = aElements.Count(); |
1259 | 0 | for (int32_t i = int32_t(cnt) - 1; i >= 0; --i) { |
1260 | 0 | RefPtr<Element> element = aElements.SafeObjectAt(i); |
1261 | 0 | if (!element) { |
1262 | 0 | continue; |
1263 | 0 | } |
1264 | 0 | |
1265 | 0 | // Applying persistent attributes to top level windows is handled |
1266 | 0 | // by nsXULWindow. |
1267 | 0 | if (element->IsXULElement(nsGkAtoms::window)) { |
1268 | 0 | if (nsCOMPtr<nsIXULWindow> win = GetXULWindowIfToplevelChrome()) { |
1269 | 0 | continue; |
1270 | 0 | } |
1271 | 0 | } |
1272 | 0 | |
1273 | 0 | Unused << element->SetAttr(kNameSpaceID_None, attr, value, true); |
1274 | 0 | } |
1275 | 0 | } |
1276 | 0 |
|
1277 | 0 | return NS_OK; |
1278 | 0 | } |
1279 | | |
1280 | | void |
1281 | | XULDocument::TraceProtos(JSTracer* aTrc) |
1282 | 0 | { |
1283 | 0 | uint32_t i, count = mPrototypes.Length(); |
1284 | 0 | for (i = 0; i < count; ++i) { |
1285 | 0 | mPrototypes[i]->TraceProtos(aTrc); |
1286 | 0 | } |
1287 | 0 |
|
1288 | 0 | if (mCurrentPrototype) { |
1289 | 0 | mCurrentPrototype->TraceProtos(aTrc); |
1290 | 0 | } |
1291 | 0 | } |
1292 | | |
1293 | | //---------------------------------------------------------------------- |
1294 | | // |
1295 | | // XULDocument::ContextStack |
1296 | | // |
1297 | | |
1298 | | XULDocument::ContextStack::ContextStack() |
1299 | | : mTop(nullptr), mDepth(0) |
1300 | 0 | { |
1301 | 0 | } |
1302 | | |
1303 | | XULDocument::ContextStack::~ContextStack() |
1304 | 0 | { |
1305 | 0 | while (mTop) { |
1306 | 0 | Entry* doomed = mTop; |
1307 | 0 | mTop = mTop->mNext; |
1308 | 0 | NS_IF_RELEASE(doomed->mElement); |
1309 | 0 | delete doomed; |
1310 | 0 | } |
1311 | 0 | } |
1312 | | |
1313 | | nsresult |
1314 | | XULDocument::ContextStack::Push(nsXULPrototypeElement* aPrototype, |
1315 | | nsIContent* aElement) |
1316 | 0 | { |
1317 | 0 | Entry* entry = new Entry; |
1318 | 0 | entry->mPrototype = aPrototype; |
1319 | 0 | entry->mElement = aElement; |
1320 | 0 | NS_IF_ADDREF(entry->mElement); |
1321 | 0 | entry->mIndex = 0; |
1322 | 0 |
|
1323 | 0 | entry->mNext = mTop; |
1324 | 0 | mTop = entry; |
1325 | 0 |
|
1326 | 0 | ++mDepth; |
1327 | 0 | return NS_OK; |
1328 | 0 | } |
1329 | | |
1330 | | nsresult |
1331 | | XULDocument::ContextStack::Pop() |
1332 | 0 | { |
1333 | 0 | if (mDepth == 0) |
1334 | 0 | return NS_ERROR_UNEXPECTED; |
1335 | 0 | |
1336 | 0 | Entry* doomed = mTop; |
1337 | 0 | mTop = mTop->mNext; |
1338 | 0 | --mDepth; |
1339 | 0 |
|
1340 | 0 | NS_IF_RELEASE(doomed->mElement); |
1341 | 0 | delete doomed; |
1342 | 0 | return NS_OK; |
1343 | 0 | } |
1344 | | |
1345 | | nsresult |
1346 | | XULDocument::ContextStack::Peek(nsXULPrototypeElement** aPrototype, |
1347 | | nsIContent** aElement, |
1348 | | int32_t* aIndex) |
1349 | 0 | { |
1350 | 0 | if (mDepth == 0) |
1351 | 0 | return NS_ERROR_UNEXPECTED; |
1352 | 0 | |
1353 | 0 | *aPrototype = mTop->mPrototype; |
1354 | 0 | *aElement = mTop->mElement; |
1355 | 0 | NS_IF_ADDREF(*aElement); |
1356 | 0 | *aIndex = mTop->mIndex; |
1357 | 0 |
|
1358 | 0 | return NS_OK; |
1359 | 0 | } |
1360 | | |
1361 | | |
1362 | | nsresult |
1363 | | XULDocument::ContextStack::SetTopIndex(int32_t aIndex) |
1364 | 0 | { |
1365 | 0 | if (mDepth == 0) |
1366 | 0 | return NS_ERROR_UNEXPECTED; |
1367 | 0 | |
1368 | 0 | mTop->mIndex = aIndex; |
1369 | 0 | return NS_OK; |
1370 | 0 | } |
1371 | | |
1372 | | |
1373 | | //---------------------------------------------------------------------- |
1374 | | // |
1375 | | // Content model walking routines |
1376 | | // |
1377 | | |
1378 | | nsresult |
1379 | | XULDocument::PrepareToWalk() |
1380 | 0 | { |
1381 | 0 | // Prepare to walk the mCurrentPrototype |
1382 | 0 | nsresult rv; |
1383 | 0 |
|
1384 | 0 | // Keep an owning reference to the prototype document so that its |
1385 | 0 | // elements aren't yanked from beneath us. |
1386 | 0 | mPrototypes.AppendElement(mCurrentPrototype); |
1387 | 0 |
|
1388 | 0 | // Get the prototype's root element and initialize the context |
1389 | 0 | // stack for the prototype walk. |
1390 | 0 | nsXULPrototypeElement* proto = mCurrentPrototype->GetRootElement(); |
1391 | 0 |
|
1392 | 0 | if (! proto) { |
1393 | 0 | if (MOZ_LOG_TEST(gXULLog, LogLevel::Error)) { |
1394 | 0 | nsCOMPtr<nsIURI> url = mCurrentPrototype->GetURI(); |
1395 | 0 |
|
1396 | 0 | nsAutoCString urlspec; |
1397 | 0 | rv = url->GetSpec(urlspec); |
1398 | 0 | if (NS_FAILED(rv)) return rv; |
1399 | 0 | |
1400 | 0 | MOZ_LOG(gXULLog, LogLevel::Error, |
1401 | 0 | ("xul: error parsing '%s'", urlspec.get())); |
1402 | 0 | } |
1403 | 0 |
|
1404 | 0 | return NS_OK; |
1405 | 0 | } |
1406 | 0 | |
1407 | 0 | nsINode* nodeToInsertBefore = nsINode::GetFirstChild(); |
1408 | 0 |
|
1409 | 0 | const nsTArray<RefPtr<nsXULPrototypePI> >& processingInstructions = |
1410 | 0 | mCurrentPrototype->GetProcessingInstructions(); |
1411 | 0 |
|
1412 | 0 | uint32_t total = processingInstructions.Length(); |
1413 | 0 | for (uint32_t i = 0; i < total; ++i) { |
1414 | 0 | rv = CreateAndInsertPI(processingInstructions[i], |
1415 | 0 | this, nodeToInsertBefore); |
1416 | 0 | if (NS_FAILED(rv)) return rv; |
1417 | 0 | } |
1418 | 0 |
|
1419 | 0 | // Do one-time initialization. |
1420 | 0 | RefPtr<Element> root; |
1421 | 0 |
|
1422 | 0 | // Add the root element |
1423 | 0 | rv = CreateElementFromPrototype(proto, getter_AddRefs(root), true); |
1424 | 0 | if (NS_FAILED(rv)) return rv; |
1425 | 0 | |
1426 | 0 | rv = AppendChildTo(root, false); |
1427 | 0 | if (NS_FAILED(rv)) return rv; |
1428 | 0 | |
1429 | 0 | // Block onload until we've finished building the complete |
1430 | 0 | // document content model. |
1431 | 0 | BlockOnload(); |
1432 | 0 |
|
1433 | 0 | nsContentUtils::AddScriptRunner( |
1434 | 0 | new nsDocElementCreatedNotificationRunner(this)); |
1435 | 0 |
|
1436 | 0 | // There'd better not be anything on the context stack at this |
1437 | 0 | // point! This is the basis case for our "induction" in |
1438 | 0 | // ResumeWalk(), below, which'll assume that there's always a |
1439 | 0 | // content element on the context stack if we're in the document. |
1440 | 0 | NS_ASSERTION(mContextStack.Depth() == 0, "something's on the context stack already"); |
1441 | 0 | if (mContextStack.Depth() != 0) |
1442 | 0 | return NS_ERROR_UNEXPECTED; |
1443 | 0 | |
1444 | 0 | rv = mContextStack.Push(proto, root); |
1445 | 0 | if (NS_FAILED(rv)) return rv; |
1446 | 0 | |
1447 | 0 | return NS_OK; |
1448 | 0 | } |
1449 | | |
1450 | | nsresult |
1451 | | XULDocument::CreateAndInsertPI(const nsXULPrototypePI* aProtoPI, |
1452 | | nsINode* aParent, nsINode* aBeforeThis) |
1453 | 0 | { |
1454 | 0 | MOZ_ASSERT(aProtoPI, "null ptr"); |
1455 | 0 | MOZ_ASSERT(aParent, "null ptr"); |
1456 | 0 |
|
1457 | 0 | RefPtr<ProcessingInstruction> node = |
1458 | 0 | NS_NewXMLProcessingInstruction(mNodeInfoManager, aProtoPI->mTarget, |
1459 | 0 | aProtoPI->mData); |
1460 | 0 |
|
1461 | 0 | nsresult rv; |
1462 | 0 | if (aProtoPI->mTarget.EqualsLiteral("xml-stylesheet")) { |
1463 | 0 | rv = InsertXMLStylesheetPI(aProtoPI, aParent, aBeforeThis, node); |
1464 | 0 | } else { |
1465 | 0 | // No special processing, just add the PI to the document. |
1466 | 0 | rv = aParent->InsertChildBefore(node->AsContent(), |
1467 | 0 | aBeforeThis |
1468 | 0 | ? aBeforeThis->AsContent() : nullptr, |
1469 | 0 | false); |
1470 | 0 | } |
1471 | 0 |
|
1472 | 0 | return rv; |
1473 | 0 | } |
1474 | | |
1475 | | nsresult |
1476 | | XULDocument::InsertXMLStylesheetPI(const nsXULPrototypePI* aProtoPI, |
1477 | | nsINode* aParent, |
1478 | | nsINode* aBeforeThis, |
1479 | | nsIContent* aPINode) |
1480 | 0 | { |
1481 | 0 | nsCOMPtr<nsIStyleSheetLinkingElement> ssle(do_QueryInterface(aPINode)); |
1482 | 0 | NS_ASSERTION(ssle, "passed XML Stylesheet node does not " |
1483 | 0 | "implement nsIStyleSheetLinkingElement!"); |
1484 | 0 |
|
1485 | 0 | nsresult rv; |
1486 | 0 |
|
1487 | 0 | ssle->InitStyleLinkElement(false); |
1488 | 0 | // We want to be notified when the style sheet finishes loading, so |
1489 | 0 | // disable style sheet loading for now. |
1490 | 0 | ssle->SetEnableUpdates(false); |
1491 | 0 | ssle->OverrideBaseURI(mCurrentPrototype->GetURI()); |
1492 | 0 |
|
1493 | 0 | rv = aParent->InsertChildBefore(aPINode->AsContent(), |
1494 | 0 | aBeforeThis |
1495 | 0 | ? aBeforeThis->AsContent() : nullptr, |
1496 | 0 | false); |
1497 | 0 | if (NS_FAILED(rv)) return rv; |
1498 | 0 | |
1499 | 0 | ssle->SetEnableUpdates(true); |
1500 | 0 |
|
1501 | 0 | // load the stylesheet if necessary, passing ourselves as |
1502 | 0 | // nsICSSObserver |
1503 | 0 | auto result = ssle->UpdateStyleSheet(this); |
1504 | 0 | if (result.isErr()) { |
1505 | 0 | // Ignore errors from UpdateStyleSheet; we don't want failure to |
1506 | 0 | // do that to break the XUL document load. But do propagate out |
1507 | 0 | // NS_ERROR_OUT_OF_MEMORY. |
1508 | 0 | if (result.unwrapErr() == NS_ERROR_OUT_OF_MEMORY) { |
1509 | 0 | return result.unwrapErr(); |
1510 | 0 | } |
1511 | 0 | return NS_OK; |
1512 | 0 | } |
1513 | 0 | |
1514 | 0 | auto update = result.unwrap(); |
1515 | 0 | if (update.ShouldBlock()) { |
1516 | 0 | ++mPendingSheets; |
1517 | 0 | } |
1518 | 0 |
|
1519 | 0 | return NS_OK; |
1520 | 0 | } |
1521 | | |
1522 | | nsresult |
1523 | | XULDocument::ResumeWalk() |
1524 | 0 | { |
1525 | 0 | // Walk the prototype and build the delegate content model. The |
1526 | 0 | // walk is performed in a top-down, left-to-right fashion. That |
1527 | 0 | // is, a parent is built before any of its children; a node is |
1528 | 0 | // only built after all of its siblings to the left are fully |
1529 | 0 | // constructed. |
1530 | 0 | // |
1531 | 0 | // It is interruptable so that transcluded documents (e.g., |
1532 | 0 | // <html:script src="..." />) can be properly re-loaded if the |
1533 | 0 | // cached copy of the document becomes stale. |
1534 | 0 | nsresult rv; |
1535 | 0 | nsCOMPtr<nsIURI> docURI = |
1536 | 0 | mCurrentPrototype ? mCurrentPrototype->GetURI() : nullptr; |
1537 | 0 |
|
1538 | 0 | while (1) { |
1539 | 0 | // Begin (or resume) walking the current prototype. |
1540 | 0 |
|
1541 | 0 | while (mContextStack.Depth() > 0) { |
1542 | 0 | // Look at the top of the stack to determine what we're |
1543 | 0 | // currently working on. |
1544 | 0 | // This will always be a node already constructed and |
1545 | 0 | // inserted to the actual document. |
1546 | 0 | nsXULPrototypeElement* proto; |
1547 | 0 | nsCOMPtr<nsIContent> element; |
1548 | 0 | int32_t indx; // all children of proto before indx (not |
1549 | 0 | // inclusive) have already been constructed |
1550 | 0 | rv = mContextStack.Peek(&proto, getter_AddRefs(element), &indx); |
1551 | 0 | if (NS_FAILED(rv)) return rv; |
1552 | 0 | |
1553 | 0 | if (indx >= (int32_t)proto->mChildren.Length()) { |
1554 | 0 | if (element) { |
1555 | 0 | // We've processed all of the prototype's children. If |
1556 | 0 | // we're in the master prototype, do post-order |
1557 | 0 | // document-level hookup. |
1558 | 0 | AddElementToDocumentPost(element->AsElement()); |
1559 | 0 |
|
1560 | 0 | if (element->NodeInfo()->Equals(nsGkAtoms::style, |
1561 | 0 | kNameSpaceID_XHTML) || |
1562 | 0 | element->NodeInfo()->Equals(nsGkAtoms::style, |
1563 | 0 | kNameSpaceID_SVG)) { |
1564 | 0 | // XXX sucks that we have to do this - |
1565 | 0 | // see bug 370111 |
1566 | 0 | nsCOMPtr<nsIStyleSheetLinkingElement> ssle = |
1567 | 0 | do_QueryInterface(element); |
1568 | 0 | NS_ASSERTION(ssle, "<html:style> doesn't implement " |
1569 | 0 | "nsIStyleSheetLinkingElement?"); |
1570 | 0 | Unused << ssle->UpdateStyleSheet(nullptr); |
1571 | 0 | } |
1572 | 0 | } |
1573 | 0 | // Now pop the context stack back up to the parent |
1574 | 0 | // element and continue the prototype walk. |
1575 | 0 | mContextStack.Pop(); |
1576 | 0 | continue; |
1577 | 0 | } |
1578 | 0 |
|
1579 | 0 | // Grab the next child, and advance the current context stack |
1580 | 0 | // to the next sibling to our right. |
1581 | 0 | nsXULPrototypeNode* childproto = proto->mChildren[indx]; |
1582 | 0 | mContextStack.SetTopIndex(++indx); |
1583 | 0 |
|
1584 | 0 | NS_ASSERTION(element, "no element on context stack"); |
1585 | 0 |
|
1586 | 0 | switch (childproto->mType) { |
1587 | 0 | case nsXULPrototypeNode::eType_Element: { |
1588 | 0 | // An 'element', which may contain more content. |
1589 | 0 | nsXULPrototypeElement* protoele = |
1590 | 0 | static_cast<nsXULPrototypeElement*>(childproto); |
1591 | 0 |
|
1592 | 0 | RefPtr<Element> child; |
1593 | 0 |
|
1594 | 0 |
|
1595 | 0 | rv = CreateElementFromPrototype(protoele, |
1596 | 0 | getter_AddRefs(child), |
1597 | 0 | false); |
1598 | 0 | if (NS_FAILED(rv)) return rv; |
1599 | 0 | |
1600 | 0 | // ...and append it to the content model. |
1601 | 0 | rv = element->AppendChildTo(child, false); |
1602 | 0 | if (NS_FAILED(rv)) return rv; |
1603 | 0 | |
1604 | 0 | // do pre-order document-level hookup. |
1605 | 0 | AddElementToDocumentPre(child); |
1606 | 0 |
|
1607 | 0 | // If it has children, push the element onto the context |
1608 | 0 | // stack and begin to process them. |
1609 | 0 | if (protoele->mChildren.Length() > 0) { |
1610 | 0 | rv = mContextStack.Push(protoele, child); |
1611 | 0 | if (NS_FAILED(rv)) return rv; |
1612 | 0 | } |
1613 | 0 | else { |
1614 | 0 | // If there are no children, do post-order document hookup |
1615 | 0 | // immediately. |
1616 | 0 | AddElementToDocumentPost(child); |
1617 | 0 | } |
1618 | 0 | } |
1619 | 0 | break; |
1620 | 0 |
|
1621 | 0 | case nsXULPrototypeNode::eType_Script: { |
1622 | 0 | // A script reference. Execute the script immediately; |
1623 | 0 | // this may have side effects in the content model. |
1624 | 0 | nsXULPrototypeScript* scriptproto = |
1625 | 0 | static_cast<nsXULPrototypeScript*>(childproto); |
1626 | 0 |
|
1627 | 0 | if (scriptproto->mSrcURI) { |
1628 | 0 | // A transcluded script reference; this may |
1629 | 0 | // "block" our prototype walk if the script isn't |
1630 | 0 | // cached, or the cached copy of the script is |
1631 | 0 | // stale and must be reloaded. |
1632 | 0 | bool blocked; |
1633 | 0 | rv = LoadScript(scriptproto, &blocked); |
1634 | 0 | // If the script cannot be loaded, just keep going! |
1635 | 0 |
|
1636 | 0 | if (NS_SUCCEEDED(rv) && blocked) |
1637 | 0 | return NS_OK; |
1638 | 0 | } |
1639 | 0 | else if (scriptproto->HasScriptObject()) { |
1640 | 0 | // An inline script |
1641 | 0 | rv = ExecuteScript(scriptproto); |
1642 | 0 | if (NS_FAILED(rv)) return rv; |
1643 | 0 | } |
1644 | 0 | } |
1645 | 0 | break; |
1646 | 0 |
|
1647 | 0 | case nsXULPrototypeNode::eType_Text: { |
1648 | 0 | // A simple text node. |
1649 | 0 | RefPtr<nsTextNode> text = |
1650 | 0 | new nsTextNode(mNodeInfoManager); |
1651 | 0 |
|
1652 | 0 | nsXULPrototypeText* textproto = |
1653 | 0 | static_cast<nsXULPrototypeText*>(childproto); |
1654 | 0 | text->SetText(textproto->mValue, false); |
1655 | 0 |
|
1656 | 0 | rv = element->AppendChildTo(text, false); |
1657 | 0 | NS_ENSURE_SUCCESS(rv, rv); |
1658 | 0 | } |
1659 | 0 | break; |
1660 | 0 |
|
1661 | 0 | case nsXULPrototypeNode::eType_PI: { |
1662 | 0 | nsXULPrototypePI* piProto = |
1663 | 0 | static_cast<nsXULPrototypePI*>(childproto); |
1664 | 0 |
|
1665 | 0 | // <?xml-stylesheet?> doesn't have an effect |
1666 | 0 | // outside the prolog, like it used to. Issue a warning. |
1667 | 0 |
|
1668 | 0 | if (piProto->mTarget.EqualsLiteral("xml-stylesheet")) { |
1669 | 0 |
|
1670 | 0 | const char16_t* params[] = { piProto->mTarget.get() }; |
1671 | 0 |
|
1672 | 0 | nsContentUtils::ReportToConsole( |
1673 | 0 | nsIScriptError::warningFlag, |
1674 | 0 | NS_LITERAL_CSTRING("XUL Document"), nullptr, |
1675 | 0 | nsContentUtils::eXUL_PROPERTIES, |
1676 | 0 | "PINotInProlog", |
1677 | 0 | params, ArrayLength(params), |
1678 | 0 | docURI); |
1679 | 0 | } |
1680 | 0 |
|
1681 | 0 | nsIContent* parent = element.get(); |
1682 | 0 |
|
1683 | 0 | if (parent) { |
1684 | 0 | // an inline script could have removed the root element |
1685 | 0 | rv = CreateAndInsertPI(piProto, parent, nullptr); |
1686 | 0 | NS_ENSURE_SUCCESS(rv, rv); |
1687 | 0 | } |
1688 | 0 | } |
1689 | 0 | break; |
1690 | 0 |
|
1691 | 0 | default: |
1692 | 0 | MOZ_ASSERT_UNREACHABLE("Unexpected nsXULPrototypeNode::Type"); |
1693 | 0 | } |
1694 | 0 | } |
1695 | 0 |
|
1696 | 0 | // Once we get here, the context stack will have been |
1697 | 0 | // depleted. That means that the entire prototype has been |
1698 | 0 | // walked and content has been constructed. |
1699 | 0 | break; |
1700 | 0 | } |
1701 | 0 |
|
1702 | 0 | // If we get here, there is nothing left for us to walk. The content |
1703 | 0 | // model is built and ready for layout. |
1704 | 0 |
|
1705 | 0 | ApplyPersistentAttributes(); |
1706 | 0 |
|
1707 | 0 | mStillWalking = false; |
1708 | 0 | if (mPendingSheets == 0) { |
1709 | 0 | rv = DoneWalking(); |
1710 | 0 | } |
1711 | 0 | return rv; |
1712 | 0 | } |
1713 | | |
1714 | | nsresult |
1715 | | XULDocument::DoneWalking() |
1716 | 0 | { |
1717 | 0 | MOZ_ASSERT(mPendingSheets == 0, "there are sheets to be loaded"); |
1718 | 0 | MOZ_ASSERT(!mStillWalking, "walk not done"); |
1719 | 0 |
|
1720 | 0 | // XXXldb This is where we should really be setting the chromehidden |
1721 | 0 | // attribute. |
1722 | 0 |
|
1723 | 0 | if (!mDocumentLoaded) { |
1724 | 0 | // Make sure we don't reenter here from StartLayout(). Note that |
1725 | 0 | // setting mDocumentLoaded to true here means that if StartLayout() |
1726 | 0 | // causes ResumeWalk() to be reentered, we'll take the other branch of |
1727 | 0 | // the |if (!mDocumentLoaded)| check above and since |
1728 | 0 | // mInitialLayoutComplete will be false will follow the else branch |
1729 | 0 | // there too. See the big comment there for how such reentry can |
1730 | 0 | // happen. |
1731 | 0 | mDocumentLoaded = true; |
1732 | 0 |
|
1733 | 0 | NotifyPossibleTitleChange(false); |
1734 | 0 |
|
1735 | 0 | // For performance reasons, we want to trigger the DocumentL10n's `TriggerInitialDocumentTranslation` within the same |
1736 | 0 | // microtask that will be created for a `MozBeforeInitialXULLayout` |
1737 | 0 | // event listener. |
1738 | 0 | AddEventListener(NS_LITERAL_STRING("MozBeforeInitialXULLayout"), mDocumentL10n, true, false); |
1739 | 0 |
|
1740 | 0 | nsContentUtils::DispatchTrustedEvent( |
1741 | 0 | this, |
1742 | 0 | static_cast<nsIDocument*>(this), |
1743 | 0 | NS_LITERAL_STRING("MozBeforeInitialXULLayout"), |
1744 | 0 | CanBubble::eYes, |
1745 | 0 | Cancelable::eNo); |
1746 | 0 |
|
1747 | 0 | RemoveEventListener(NS_LITERAL_STRING("MozBeforeInitialXULLayout"), mDocumentL10n, true); |
1748 | 0 |
|
1749 | 0 | // Before starting layout, check whether we're a toplevel chrome |
1750 | 0 | // window. If we are, setup some state so that we don't have to restyle |
1751 | 0 | // the whole tree after StartLayout. |
1752 | 0 | if (nsCOMPtr<nsIXULWindow> win = GetXULWindowIfToplevelChrome()) { |
1753 | 0 | // We're the chrome document! |
1754 | 0 | win->BeforeStartLayout(); |
1755 | 0 | } |
1756 | 0 |
|
1757 | 0 | StartLayout(); |
1758 | 0 |
|
1759 | 0 | if (mIsWritingFastLoad && IsChromeURI(mDocumentURI)) |
1760 | 0 | nsXULPrototypeCache::GetInstance()->WritePrototype(mCurrentPrototype); |
1761 | 0 |
|
1762 | 0 | NS_ASSERTION(mDelayFrameLoaderInitialization, |
1763 | 0 | "mDelayFrameLoaderInitialization should be true!"); |
1764 | 0 | mDelayFrameLoaderInitialization = false; |
1765 | 0 | NS_WARNING_ASSERTION( |
1766 | 0 | mUpdateNestLevel == 0, |
1767 | 0 | "Constructing XUL document in middle of an update?"); |
1768 | 0 | if (mUpdateNestLevel == 0) { |
1769 | 0 | MaybeInitializeFinalizeFrameLoaders(); |
1770 | 0 | } |
1771 | 0 |
|
1772 | 0 | NS_DOCUMENT_NOTIFY_OBSERVERS(EndLoad, (this)); |
1773 | 0 |
|
1774 | 0 | // DispatchContentLoadedEvents undoes the onload-blocking we |
1775 | 0 | // did in PrepareToWalk(). |
1776 | 0 | DispatchContentLoadedEvents(); |
1777 | 0 |
|
1778 | 0 | mInitialLayoutComplete = true; |
1779 | 0 | } |
1780 | 0 |
|
1781 | 0 | return NS_OK; |
1782 | 0 | } |
1783 | | |
1784 | | NS_IMETHODIMP |
1785 | | XULDocument::StyleSheetLoaded(StyleSheet* aSheet, |
1786 | | bool aWasDeferred, |
1787 | | nsresult aStatus) |
1788 | 0 | { |
1789 | 0 | if (!aWasDeferred) { |
1790 | 0 | // Don't care about when alternate sheets finish loading |
1791 | 0 | MOZ_ASSERT(mPendingSheets > 0, |
1792 | 0 | "Unexpected StyleSheetLoaded notification"); |
1793 | 0 |
|
1794 | 0 | --mPendingSheets; |
1795 | 0 |
|
1796 | 0 | if (!mStillWalking && mPendingSheets == 0) { |
1797 | 0 | return DoneWalking(); |
1798 | 0 | } |
1799 | 0 | } |
1800 | 0 | |
1801 | 0 | return NS_OK; |
1802 | 0 | } |
1803 | | |
1804 | | void |
1805 | | XULDocument::MaybeBroadcast() |
1806 | 0 | { |
1807 | 0 | // Only broadcast when not in an update and when safe to run scripts. |
1808 | 0 | if (mUpdateNestLevel == 0 && |
1809 | 0 | (mDelayedAttrChangeBroadcasts.Length() || |
1810 | 0 | mDelayedBroadcasters.Length())) { |
1811 | 0 | if (!nsContentUtils::IsSafeToRunScript()) { |
1812 | 0 | if (!mInDestructor) { |
1813 | 0 | nsContentUtils::AddScriptRunner( |
1814 | 0 | NewRunnableMethod("dom::XULDocument::MaybeBroadcast", |
1815 | 0 | this, |
1816 | 0 | &XULDocument::MaybeBroadcast)); |
1817 | 0 | } |
1818 | 0 | return; |
1819 | 0 | } |
1820 | 0 | if (!mHandlingDelayedAttrChange) { |
1821 | 0 | mHandlingDelayedAttrChange = true; |
1822 | 0 | for (uint32_t i = 0; i < mDelayedAttrChangeBroadcasts.Length(); ++i) { |
1823 | 0 | nsAtom* attrName = mDelayedAttrChangeBroadcasts[i].mAttrName; |
1824 | 0 | if (mDelayedAttrChangeBroadcasts[i].mNeedsAttrChange) { |
1825 | 0 | nsCOMPtr<Element> listener = |
1826 | 0 | do_QueryInterface(mDelayedAttrChangeBroadcasts[i].mListener); |
1827 | 0 | const nsString& value = mDelayedAttrChangeBroadcasts[i].mAttr; |
1828 | 0 | if (mDelayedAttrChangeBroadcasts[i].mSetAttr) { |
1829 | 0 | listener->SetAttr(kNameSpaceID_None, attrName, value, |
1830 | 0 | true); |
1831 | 0 | } else { |
1832 | 0 | listener->UnsetAttr(kNameSpaceID_None, attrName, |
1833 | 0 | true); |
1834 | 0 | } |
1835 | 0 | } |
1836 | 0 | ExecuteOnBroadcastHandlerFor(mDelayedAttrChangeBroadcasts[i].mBroadcaster, |
1837 | 0 | mDelayedAttrChangeBroadcasts[i].mListener, |
1838 | 0 | attrName); |
1839 | 0 | } |
1840 | 0 | mDelayedAttrChangeBroadcasts.Clear(); |
1841 | 0 | mHandlingDelayedAttrChange = false; |
1842 | 0 | } |
1843 | 0 |
|
1844 | 0 | uint32_t length = mDelayedBroadcasters.Length(); |
1845 | 0 | if (length) { |
1846 | 0 | bool oldValue = mHandlingDelayedBroadcasters; |
1847 | 0 | mHandlingDelayedBroadcasters = true; |
1848 | 0 | nsTArray<nsDelayedBroadcastUpdate> delayedBroadcasters; |
1849 | 0 | mDelayedBroadcasters.SwapElements(delayedBroadcasters); |
1850 | 0 | for (uint32_t i = 0; i < length; ++i) { |
1851 | 0 | SynchronizeBroadcastListener(delayedBroadcasters[i].mBroadcaster, |
1852 | 0 | delayedBroadcasters[i].mListener, |
1853 | 0 | delayedBroadcasters[i].mAttr); |
1854 | 0 | } |
1855 | 0 | mHandlingDelayedBroadcasters = oldValue; |
1856 | 0 | } |
1857 | 0 | } |
1858 | 0 | } |
1859 | | |
1860 | | void |
1861 | | XULDocument::EndUpdate() |
1862 | 0 | { |
1863 | 0 | XMLDocument::EndUpdate(); |
1864 | 0 | MaybeBroadcast(); |
1865 | 0 | } |
1866 | | |
1867 | | nsresult |
1868 | | XULDocument::LoadScript(nsXULPrototypeScript* aScriptProto, bool* aBlock) |
1869 | 0 | { |
1870 | 0 | // Load a transcluded script |
1871 | 0 | nsresult rv; |
1872 | 0 |
|
1873 | 0 | bool isChromeDoc = IsChromeURI(mDocumentURI); |
1874 | 0 |
|
1875 | 0 | if (isChromeDoc && aScriptProto->HasScriptObject()) { |
1876 | 0 | rv = ExecuteScript(aScriptProto); |
1877 | 0 |
|
1878 | 0 | // Ignore return value from execution, and don't block |
1879 | 0 | *aBlock = false; |
1880 | 0 | return NS_OK; |
1881 | 0 | } |
1882 | 0 | |
1883 | 0 | // Try the XUL script cache, in case two XUL documents source the same |
1884 | 0 | // .js file (e.g., strres.js from navigator.xul and utilityOverlay.xul). |
1885 | 0 | // XXXbe the cache relies on aScriptProto's GC root! |
1886 | 0 | bool useXULCache = nsXULPrototypeCache::GetInstance()->IsEnabled(); |
1887 | 0 |
|
1888 | 0 | if (isChromeDoc && useXULCache) { |
1889 | 0 | JSScript* newScriptObject = |
1890 | 0 | nsXULPrototypeCache::GetInstance()->GetScript( |
1891 | 0 | aScriptProto->mSrcURI); |
1892 | 0 | if (newScriptObject) { |
1893 | 0 | // The script language for a proto must remain constant - we |
1894 | 0 | // can't just change it for this unexpected language. |
1895 | 0 | aScriptProto->Set(newScriptObject); |
1896 | 0 | } |
1897 | 0 |
|
1898 | 0 | if (aScriptProto->HasScriptObject()) { |
1899 | 0 | rv = ExecuteScript(aScriptProto); |
1900 | 0 |
|
1901 | 0 | // Ignore return value from execution, and don't block |
1902 | 0 | *aBlock = false; |
1903 | 0 | return NS_OK; |
1904 | 0 | } |
1905 | 0 | } |
1906 | 0 | |
1907 | 0 | // Release script objects from FastLoad since we decided against using them |
1908 | 0 | aScriptProto->UnlinkJSObjects(); |
1909 | 0 |
|
1910 | 0 | // Set the current script prototype so that OnStreamComplete can report |
1911 | 0 | // the right file if there are errors in the script. |
1912 | 0 | NS_ASSERTION(!mCurrentScriptProto, |
1913 | 0 | "still loading a script when starting another load?"); |
1914 | 0 | mCurrentScriptProto = aScriptProto; |
1915 | 0 |
|
1916 | 0 | if (isChromeDoc && aScriptProto->mSrcLoading) { |
1917 | 0 | // Another XULDocument load has started, which is still in progress. |
1918 | 0 | // Remember to ResumeWalk this document when the load completes. |
1919 | 0 | mNextSrcLoadWaiter = aScriptProto->mSrcLoadWaiters; |
1920 | 0 | aScriptProto->mSrcLoadWaiters = this; |
1921 | 0 | NS_ADDREF_THIS(); |
1922 | 0 | } |
1923 | 0 | else { |
1924 | 0 | nsCOMPtr<nsILoadGroup> group = do_QueryReferent(mDocumentLoadGroup); |
1925 | 0 |
|
1926 | 0 | // Note: the loader will keep itself alive while it's loading. |
1927 | 0 | nsCOMPtr<nsIStreamLoader> loader; |
1928 | 0 | rv = NS_NewStreamLoader(getter_AddRefs(loader), |
1929 | 0 | aScriptProto->mSrcURI, |
1930 | 0 | this, // aObserver |
1931 | 0 | this, // aRequestingContext |
1932 | 0 | nsILoadInfo::SEC_REQUIRE_SAME_ORIGIN_DATA_INHERITS, |
1933 | 0 | nsIContentPolicy::TYPE_INTERNAL_SCRIPT, |
1934 | 0 | group); |
1935 | 0 |
|
1936 | 0 | if (NS_FAILED(rv)) { |
1937 | 0 | mCurrentScriptProto = nullptr; |
1938 | 0 | return rv; |
1939 | 0 | } |
1940 | 0 | |
1941 | 0 | aScriptProto->mSrcLoading = true; |
1942 | 0 | } |
1943 | 0 |
|
1944 | 0 | // Block until OnStreamComplete resumes us. |
1945 | 0 | *aBlock = true; |
1946 | 0 | return NS_OK; |
1947 | 0 | } |
1948 | | |
1949 | | NS_IMETHODIMP |
1950 | | XULDocument::OnStreamComplete(nsIStreamLoader* aLoader, |
1951 | | nsISupports* context, |
1952 | | nsresult aStatus, |
1953 | | uint32_t stringLen, |
1954 | | const uint8_t* string) |
1955 | 0 | { |
1956 | 0 | nsCOMPtr<nsIRequest> request; |
1957 | 0 | aLoader->GetRequest(getter_AddRefs(request)); |
1958 | 0 | nsCOMPtr<nsIChannel> channel = do_QueryInterface(request); |
1959 | 0 |
|
1960 | | #ifdef DEBUG |
1961 | | // print a load error on bad status |
1962 | | if (NS_FAILED(aStatus)) { |
1963 | | if (channel) { |
1964 | | nsCOMPtr<nsIURI> uri; |
1965 | | channel->GetURI(getter_AddRefs(uri)); |
1966 | | if (uri) { |
1967 | | printf("Failed to load %s\n", uri->GetSpecOrDefault().get()); |
1968 | | } |
1969 | | } |
1970 | | } |
1971 | | #endif |
1972 | |
|
1973 | 0 | // This is the completion routine that will be called when a |
1974 | 0 | // transcluded script completes. Compile and execute the script |
1975 | 0 | // if the load was successful, then continue building content |
1976 | 0 | // from the prototype. |
1977 | 0 | nsresult rv = aStatus; |
1978 | 0 |
|
1979 | 0 | NS_ASSERTION(mCurrentScriptProto && mCurrentScriptProto->mSrcLoading, |
1980 | 0 | "script source not loading on unichar stream complete?"); |
1981 | 0 | if (!mCurrentScriptProto) { |
1982 | 0 | // XXX Wallpaper for bug 270042 |
1983 | 0 | return NS_OK; |
1984 | 0 | } |
1985 | 0 | |
1986 | 0 | if (NS_SUCCEEDED(aStatus)) { |
1987 | 0 | // If the including XUL document is a FastLoad document, and we're |
1988 | 0 | // compiling an out-of-line script (one with src=...), then we must |
1989 | 0 | // be writing a new FastLoad file. If we were reading this script |
1990 | 0 | // from the FastLoad file, XULContentSinkImpl::OpenScript (over in |
1991 | 0 | // nsXULContentSink.cpp) would have already deserialized a non-null |
1992 | 0 | // script->mScriptObject, causing control flow at the top of LoadScript |
1993 | 0 | // not to reach here. |
1994 | 0 | nsCOMPtr<nsIURI> uri = mCurrentScriptProto->mSrcURI; |
1995 | 0 |
|
1996 | 0 | // XXX should also check nsIHttpChannel::requestSucceeded |
1997 | 0 |
|
1998 | 0 | MOZ_ASSERT(!mOffThreadCompiling && (mOffThreadCompileStringLength == 0 && |
1999 | 0 | !mOffThreadCompileStringBuf), |
2000 | 0 | "XULDocument can't load multiple scripts at once"); |
2001 | 0 |
|
2002 | 0 | rv = ScriptLoader::ConvertToUTF16(channel, string, stringLen, |
2003 | 0 | EmptyString(), this, |
2004 | 0 | mOffThreadCompileStringBuf, |
2005 | 0 | mOffThreadCompileStringLength); |
2006 | 0 | if (NS_SUCCEEDED(rv)) { |
2007 | 0 | // Attempt to give ownership of the buffer to the JS engine. If |
2008 | 0 | // we hit offthread compilation, however, we will have to take it |
2009 | 0 | // back below in order to keep the memory alive until compilation |
2010 | 0 | // completes. |
2011 | 0 | JS::SourceBufferHolder srcBuf(mOffThreadCompileStringBuf, |
2012 | 0 | mOffThreadCompileStringLength, |
2013 | 0 | JS::SourceBufferHolder::GiveOwnership); |
2014 | 0 | mOffThreadCompileStringBuf = nullptr; |
2015 | 0 | mOffThreadCompileStringLength = 0; |
2016 | 0 |
|
2017 | 0 | rv = mCurrentScriptProto->Compile(srcBuf, uri, 1, this, this); |
2018 | 0 | if (NS_SUCCEEDED(rv) && !mCurrentScriptProto->HasScriptObject()) { |
2019 | 0 | // We will be notified via OnOffThreadCompileComplete when the |
2020 | 0 | // compile finishes. The JS engine has taken ownership of the |
2021 | 0 | // source buffer. |
2022 | 0 | MOZ_RELEASE_ASSERT(!srcBuf.ownsChars()); |
2023 | 0 | mOffThreadCompiling = true; |
2024 | 0 | BlockOnload(); |
2025 | 0 | return NS_OK; |
2026 | 0 | } |
2027 | 0 | } |
2028 | 0 | } |
2029 | 0 | |
2030 | 0 | return OnScriptCompileComplete(mCurrentScriptProto->GetScriptObject(), rv); |
2031 | 0 | } |
2032 | | |
2033 | | NS_IMETHODIMP |
2034 | | XULDocument::OnScriptCompileComplete(JSScript* aScript, nsresult aStatus) |
2035 | 0 | { |
2036 | 0 | // When compiling off thread the script will not have been attached to the |
2037 | 0 | // script proto yet. |
2038 | 0 | if (aScript && !mCurrentScriptProto->HasScriptObject()) |
2039 | 0 | mCurrentScriptProto->Set(aScript); |
2040 | 0 |
|
2041 | 0 | // Allow load events to be fired once off thread compilation finishes. |
2042 | 0 | if (mOffThreadCompiling) { |
2043 | 0 | mOffThreadCompiling = false; |
2044 | 0 | UnblockOnload(false); |
2045 | 0 | } |
2046 | 0 |
|
2047 | 0 | // After compilation finishes the script's characters are no longer needed. |
2048 | 0 | if (mOffThreadCompileStringBuf) { |
2049 | 0 | js_free(mOffThreadCompileStringBuf); |
2050 | 0 | mOffThreadCompileStringBuf = nullptr; |
2051 | 0 | mOffThreadCompileStringLength = 0; |
2052 | 0 | } |
2053 | 0 |
|
2054 | 0 | // Clear mCurrentScriptProto now, but save it first for use below in |
2055 | 0 | // the execute code, and in the while loop that resumes walks of other |
2056 | 0 | // documents that raced to load this script. |
2057 | 0 | nsXULPrototypeScript* scriptProto = mCurrentScriptProto; |
2058 | 0 | mCurrentScriptProto = nullptr; |
2059 | 0 |
|
2060 | 0 | // Clear the prototype's loading flag before executing the script or |
2061 | 0 | // resuming document walks, in case any of those control flows starts a |
2062 | 0 | // new script load. |
2063 | 0 | scriptProto->mSrcLoading = false; |
2064 | 0 |
|
2065 | 0 | nsresult rv = aStatus; |
2066 | 0 | if (NS_SUCCEEDED(rv)) { |
2067 | 0 | rv = ExecuteScript(scriptProto); |
2068 | 0 |
|
2069 | 0 | // If the XUL cache is enabled, save the script object there in |
2070 | 0 | // case different XUL documents source the same script. |
2071 | 0 | // |
2072 | 0 | // But don't save the script in the cache unless the master XUL |
2073 | 0 | // document URL is a chrome: URL. It is valid for a URL such as |
2074 | 0 | // about:config to translate into a master document URL, whose |
2075 | 0 | // prototype document nodes -- including prototype scripts that |
2076 | 0 | // hold GC roots protecting their mJSObject pointers -- are not |
2077 | 0 | // cached in the XUL prototype cache. See StartDocumentLoad, |
2078 | 0 | // the fillXULCache logic. |
2079 | 0 | // |
2080 | 0 | // A document such as about:config is free to load a script via |
2081 | 0 | // a URL such as chrome://global/content/config.js, and we must |
2082 | 0 | // not cache that script object without a prototype cache entry |
2083 | 0 | // containing a companion nsXULPrototypeScript node that owns a |
2084 | 0 | // GC root protecting the script object. Otherwise, the script |
2085 | 0 | // cache entry will dangle once the uncached prototype document |
2086 | 0 | // is released when its owning XULDocument is unloaded. |
2087 | 0 | // |
2088 | 0 | // (See http://bugzilla.mozilla.org/show_bug.cgi?id=98207 for |
2089 | 0 | // the true crime story.) |
2090 | 0 | bool useXULCache = nsXULPrototypeCache::GetInstance()->IsEnabled(); |
2091 | 0 |
|
2092 | 0 | if (useXULCache && IsChromeURI(mDocumentURI) && scriptProto->HasScriptObject()) { |
2093 | 0 | JS::Rooted<JSScript*> script(RootingCx(), scriptProto->GetScriptObject()); |
2094 | 0 | nsXULPrototypeCache::GetInstance()->PutScript( |
2095 | 0 | scriptProto->mSrcURI, script); |
2096 | 0 | } |
2097 | 0 | // ignore any evaluation errors |
2098 | 0 | } |
2099 | 0 |
|
2100 | 0 | rv = ResumeWalk(); |
2101 | 0 |
|
2102 | 0 | // Load a pointer to the prototype-script's list of XULDocuments who |
2103 | 0 | // raced to load the same script |
2104 | 0 | XULDocument** docp = &scriptProto->mSrcLoadWaiters; |
2105 | 0 |
|
2106 | 0 | // Resume walking other documents that waited for this one's load, first |
2107 | 0 | // executing the script we just compiled, in each doc's script context |
2108 | 0 | XULDocument* doc; |
2109 | 0 | while ((doc = *docp) != nullptr) { |
2110 | 0 | NS_ASSERTION(doc->mCurrentScriptProto == scriptProto, |
2111 | 0 | "waiting for wrong script to load?"); |
2112 | 0 | doc->mCurrentScriptProto = nullptr; |
2113 | 0 |
|
2114 | 0 | // Unlink doc from scriptProto's list before executing and resuming |
2115 | 0 | *docp = doc->mNextSrcLoadWaiter; |
2116 | 0 | doc->mNextSrcLoadWaiter = nullptr; |
2117 | 0 |
|
2118 | 0 | if (aStatus == NS_BINDING_ABORTED && !scriptProto->HasScriptObject()) { |
2119 | 0 | // If the previous doc load was aborted, we want to try loading |
2120 | 0 | // again for the next doc. Otherwise, one abort would lead to all |
2121 | 0 | // subsequent waiting docs to abort as well. |
2122 | 0 | bool block = false; |
2123 | 0 | doc->LoadScript(scriptProto, &block); |
2124 | 0 | NS_RELEASE(doc); |
2125 | 0 | return rv; |
2126 | 0 | } |
2127 | 0 |
|
2128 | 0 | // Execute only if we loaded and compiled successfully, then resume |
2129 | 0 | if (NS_SUCCEEDED(aStatus) && scriptProto->HasScriptObject()) { |
2130 | 0 | doc->ExecuteScript(scriptProto); |
2131 | 0 | } |
2132 | 0 | doc->ResumeWalk(); |
2133 | 0 | NS_RELEASE(doc); |
2134 | 0 | } |
2135 | 0 |
|
2136 | 0 | return rv; |
2137 | 0 | } |
2138 | | |
2139 | | nsresult |
2140 | | XULDocument::ExecuteScript(nsXULPrototypeScript *aScript) |
2141 | 0 | { |
2142 | 0 | MOZ_ASSERT(aScript != nullptr, "null ptr"); |
2143 | 0 | NS_ENSURE_TRUE(aScript, NS_ERROR_NULL_POINTER); |
2144 | 0 | NS_ENSURE_TRUE(mScriptGlobalObject, NS_ERROR_NOT_INITIALIZED); |
2145 | 0 |
|
2146 | 0 | nsresult rv; |
2147 | 0 | rv = mScriptGlobalObject->EnsureScriptEnvironment(); |
2148 | 0 | NS_ENSURE_SUCCESS(rv, rv); |
2149 | 0 |
|
2150 | 0 | // Execute the precompiled script with the given version |
2151 | 0 | nsAutoMicroTask mt; |
2152 | 0 |
|
2153 | 0 | // We're about to run script via JS::CloneAndExecuteScript, so we need an |
2154 | 0 | // AutoEntryScript. This is Gecko specific and not in any spec. |
2155 | 0 | AutoEntryScript aes(mScriptGlobalObject, "precompiled XUL <script> element"); |
2156 | 0 | JSContext* cx = aes.cx(); |
2157 | 0 |
|
2158 | 0 | JS::Rooted<JSScript*> scriptObject(cx, aScript->GetScriptObject()); |
2159 | 0 | NS_ENSURE_TRUE(scriptObject, NS_ERROR_UNEXPECTED); |
2160 | 0 |
|
2161 | 0 | JS::Rooted<JSObject*> global(cx, JS::CurrentGlobalOrNull(cx)); |
2162 | 0 | NS_ENSURE_TRUE(xpc::Scriptability::Get(global).Allowed(), NS_OK); |
2163 | 0 |
|
2164 | 0 | JS::ExposeObjectToActiveJS(global); |
2165 | 0 | JSAutoRealm ar(cx, global); |
2166 | 0 |
|
2167 | 0 | // The script is in the compilation scope. Clone it into the target scope |
2168 | 0 | // and execute it. On failure, ~AutoScriptEntry will handle exceptions, so |
2169 | 0 | // there is no need to manually check the return value. |
2170 | 0 | JS::RootedValue rval(cx); |
2171 | 0 | JS::CloneAndExecuteScript(cx, scriptObject, &rval); |
2172 | 0 |
|
2173 | 0 | return NS_OK; |
2174 | 0 | } |
2175 | | |
2176 | | |
2177 | | nsresult |
2178 | | XULDocument::CreateElementFromPrototype(nsXULPrototypeElement* aPrototype, |
2179 | | Element** aResult, |
2180 | | bool aIsRoot) |
2181 | 0 | { |
2182 | 0 | // Create a content model element from a prototype element. |
2183 | 0 | MOZ_ASSERT(aPrototype != nullptr, "null ptr"); |
2184 | 0 | if (! aPrototype) |
2185 | 0 | return NS_ERROR_NULL_POINTER; |
2186 | 0 | |
2187 | 0 | *aResult = nullptr; |
2188 | 0 | nsresult rv = NS_OK; |
2189 | 0 |
|
2190 | 0 | if (MOZ_LOG_TEST(gXULLog, LogLevel::Debug)) { |
2191 | 0 | MOZ_LOG(gXULLog, LogLevel::Debug, |
2192 | 0 | ("xul: creating <%s> from prototype", |
2193 | 0 | NS_ConvertUTF16toUTF8(aPrototype->mNodeInfo->QualifiedName()).get())); |
2194 | 0 | } |
2195 | 0 |
|
2196 | 0 | RefPtr<Element> result; |
2197 | 0 |
|
2198 | 0 | if (aPrototype->mNodeInfo->NamespaceEquals(kNameSpaceID_XUL)) { |
2199 | 0 | // If it's a XUL element, it'll be lightweight until somebody |
2200 | 0 | // monkeys with it. |
2201 | 0 | rv = nsXULElement::CreateFromPrototype(aPrototype, this, true, aIsRoot, getter_AddRefs(result)); |
2202 | 0 | if (NS_FAILED(rv)) return rv; |
2203 | 0 | } |
2204 | 0 | else { |
2205 | 0 | // If it's not a XUL element, it's gonna be heavyweight no matter |
2206 | 0 | // what. So we need to copy everything out of the prototype |
2207 | 0 | // into the element. Get a nodeinfo from our nodeinfo manager |
2208 | 0 | // for this node. |
2209 | 0 | RefPtr<mozilla::dom::NodeInfo> newNodeInfo; |
2210 | 0 | newNodeInfo = mNodeInfoManager->GetNodeInfo(aPrototype->mNodeInfo->NameAtom(), |
2211 | 0 | aPrototype->mNodeInfo->GetPrefixAtom(), |
2212 | 0 | aPrototype->mNodeInfo->NamespaceID(), |
2213 | 0 | ELEMENT_NODE); |
2214 | 0 | if (!newNodeInfo) return NS_ERROR_OUT_OF_MEMORY; |
2215 | 0 | RefPtr<mozilla::dom::NodeInfo> xtfNi = newNodeInfo; |
2216 | 0 | rv = NS_NewElement(getter_AddRefs(result), newNodeInfo.forget(), |
2217 | 0 | NOT_FROM_PARSER); |
2218 | 0 | if (NS_FAILED(rv)) |
2219 | 0 | return rv; |
2220 | 0 | |
2221 | 0 | rv = AddAttributes(aPrototype, result); |
2222 | 0 | if (NS_FAILED(rv)) return rv; |
2223 | 0 | } |
2224 | 0 | |
2225 | 0 | result.forget(aResult); |
2226 | 0 |
|
2227 | 0 | return NS_OK; |
2228 | 0 | } |
2229 | | |
2230 | | nsresult |
2231 | | XULDocument::AddAttributes(nsXULPrototypeElement* aPrototype, |
2232 | | Element* aElement) |
2233 | 0 | { |
2234 | 0 | nsresult rv; |
2235 | 0 |
|
2236 | 0 | for (uint32_t i = 0; i < aPrototype->mNumAttributes; ++i) { |
2237 | 0 | nsXULPrototypeAttribute* protoattr = &(aPrototype->mAttributes[i]); |
2238 | 0 | nsAutoString valueStr; |
2239 | 0 | protoattr->mValue.ToString(valueStr); |
2240 | 0 |
|
2241 | 0 | rv = aElement->SetAttr(protoattr->mName.NamespaceID(), |
2242 | 0 | protoattr->mName.LocalName(), |
2243 | 0 | protoattr->mName.GetPrefix(), |
2244 | 0 | valueStr, |
2245 | 0 | false); |
2246 | 0 | if (NS_FAILED(rv)) return rv; |
2247 | 0 | } |
2248 | 0 |
|
2249 | 0 | return NS_OK; |
2250 | 0 | } |
2251 | | |
2252 | | |
2253 | | //---------------------------------------------------------------------- |
2254 | | |
2255 | | nsresult |
2256 | | XULDocument::FindBroadcaster(Element* aElement, |
2257 | | Element** aListener, |
2258 | | nsString& aBroadcasterID, |
2259 | | nsString& aAttribute, |
2260 | | Element** aBroadcaster) |
2261 | 0 | { |
2262 | 0 | mozilla::dom::NodeInfo *ni = aElement->NodeInfo(); |
2263 | 0 | *aListener = nullptr; |
2264 | 0 | *aBroadcaster = nullptr; |
2265 | 0 |
|
2266 | 0 | if (ni->Equals(nsGkAtoms::observes, kNameSpaceID_XUL)) { |
2267 | 0 | // It's an <observes> element, which means that the actual |
2268 | 0 | // listener is the _parent_ node. This element should have an |
2269 | 0 | // 'element' attribute that specifies the ID of the |
2270 | 0 | // broadcaster element, and an 'attribute' element, which |
2271 | 0 | // specifies the name of the attribute to observe. |
2272 | 0 | nsIContent* parent = aElement->GetParent(); |
2273 | 0 | if (!parent) { |
2274 | 0 | // <observes> is the root element |
2275 | 0 | return NS_FINDBROADCASTER_NOT_FOUND; |
2276 | 0 | } |
2277 | 0 | |
2278 | 0 | *aListener = Element::FromNode(parent); |
2279 | 0 | NS_IF_ADDREF(*aListener); |
2280 | 0 |
|
2281 | 0 | aElement->GetAttr(kNameSpaceID_None, nsGkAtoms::element, aBroadcasterID); |
2282 | 0 | if (aBroadcasterID.IsEmpty()) { |
2283 | 0 | return NS_FINDBROADCASTER_NOT_FOUND; |
2284 | 0 | } |
2285 | 0 | aElement->GetAttr(kNameSpaceID_None, nsGkAtoms::attribute, aAttribute); |
2286 | 0 | } |
2287 | 0 | else { |
2288 | 0 | // It's a generic element, which means that we'll use the |
2289 | 0 | // value of the 'observes' attribute to determine the ID of |
2290 | 0 | // the broadcaster element, and we'll watch _all_ of its |
2291 | 0 | // values. |
2292 | 0 | aElement->GetAttr(kNameSpaceID_None, nsGkAtoms::observes, aBroadcasterID); |
2293 | 0 |
|
2294 | 0 | // Bail if there's no aBroadcasterID |
2295 | 0 | if (aBroadcasterID.IsEmpty()) { |
2296 | 0 | // Try the command attribute next. |
2297 | 0 | aElement->GetAttr(kNameSpaceID_None, nsGkAtoms::command, aBroadcasterID); |
2298 | 0 | if (!aBroadcasterID.IsEmpty()) { |
2299 | 0 | // We've got something in the command attribute. We |
2300 | 0 | // only treat this as a normal broadcaster if we are |
2301 | 0 | // not a menuitem or a key. |
2302 | 0 |
|
2303 | 0 | if (ni->Equals(nsGkAtoms::menuitem, kNameSpaceID_XUL) || |
2304 | 0 | ni->Equals(nsGkAtoms::key, kNameSpaceID_XUL)) { |
2305 | 0 | return NS_FINDBROADCASTER_NOT_FOUND; |
2306 | 0 | } |
2307 | 0 | } |
2308 | 0 | else { |
2309 | 0 | return NS_FINDBROADCASTER_NOT_FOUND; |
2310 | 0 | } |
2311 | 0 | } |
2312 | 0 | |
2313 | 0 | *aListener = aElement; |
2314 | 0 | NS_ADDREF(*aListener); |
2315 | 0 |
|
2316 | 0 | aAttribute.Assign('*'); |
2317 | 0 | } |
2318 | 0 |
|
2319 | 0 | // Make sure we got a valid listener. |
2320 | 0 | NS_ENSURE_TRUE(*aListener, NS_ERROR_UNEXPECTED); |
2321 | 0 |
|
2322 | 0 | // Try to find the broadcaster element in the document. |
2323 | 0 | *aBroadcaster = GetElementById(aBroadcasterID); |
2324 | 0 |
|
2325 | 0 | // The broadcaster element is missing. |
2326 | 0 | if (! *aBroadcaster) { |
2327 | 0 | return NS_FINDBROADCASTER_NOT_FOUND; |
2328 | 0 | } |
2329 | 0 | |
2330 | 0 | NS_ADDREF(*aBroadcaster); |
2331 | 0 |
|
2332 | 0 | return NS_FINDBROADCASTER_FOUND; |
2333 | 0 | } |
2334 | | |
2335 | | nsresult |
2336 | | XULDocument::CheckBroadcasterHookup(Element* aElement) |
2337 | 0 | { |
2338 | 0 | // Resolve a broadcaster hookup. Look at the element that we're |
2339 | 0 | // trying to resolve: it could be an '<observes>' element, or just |
2340 | 0 | // a vanilla element with an 'observes' attribute on it. |
2341 | 0 | nsresult rv; |
2342 | 0 |
|
2343 | 0 | nsCOMPtr<Element> listener; |
2344 | 0 | nsAutoString broadcasterID; |
2345 | 0 | nsAutoString attribute; |
2346 | 0 | nsCOMPtr<Element> broadcaster; |
2347 | 0 |
|
2348 | 0 | rv = FindBroadcaster(aElement, getter_AddRefs(listener), |
2349 | 0 | broadcasterID, attribute, getter_AddRefs(broadcaster)); |
2350 | 0 | switch (rv) { |
2351 | 0 | case NS_FINDBROADCASTER_NOT_FOUND: |
2352 | 0 | return NS_OK; |
2353 | 0 | case NS_FINDBROADCASTER_FOUND: |
2354 | 0 | break; |
2355 | 0 | default: |
2356 | 0 | return rv; |
2357 | 0 | } |
2358 | 0 | |
2359 | 0 | NS_ENSURE_ARG(broadcaster && listener); |
2360 | 0 | ErrorResult domRv; |
2361 | 0 | AddBroadcastListenerFor(*broadcaster, *listener, attribute, domRv); |
2362 | 0 | if (domRv.Failed()) { |
2363 | 0 | return domRv.StealNSResult(); |
2364 | 0 | } |
2365 | 0 | |
2366 | 0 | // Tell the world we succeeded |
2367 | 0 | if (MOZ_LOG_TEST(gXULLog, LogLevel::Debug)) { |
2368 | 0 | nsCOMPtr<nsIContent> content = |
2369 | 0 | do_QueryInterface(listener); |
2370 | 0 |
|
2371 | 0 | NS_ASSERTION(content != nullptr, "not an nsIContent"); |
2372 | 0 | if (! content) |
2373 | 0 | return rv; |
2374 | 0 | |
2375 | 0 | nsAutoCString attributeC,broadcasteridC; |
2376 | 0 | LossyCopyUTF16toASCII(attribute, attributeC); |
2377 | 0 | LossyCopyUTF16toASCII(broadcasterID, broadcasteridC); |
2378 | 0 | MOZ_LOG(gXULLog, LogLevel::Debug, |
2379 | 0 | ("xul: broadcaster hookup <%s attribute='%s'> to %s", |
2380 | 0 | nsAtomCString(content->NodeInfo()->NameAtom()).get(), |
2381 | 0 | attributeC.get(), |
2382 | 0 | broadcasteridC.get())); |
2383 | 0 | } |
2384 | 0 |
|
2385 | 0 | return NS_OK; |
2386 | 0 | } |
2387 | | |
2388 | | //---------------------------------------------------------------------- |
2389 | | // |
2390 | | // CachedChromeStreamListener |
2391 | | // |
2392 | | |
2393 | | XULDocument::CachedChromeStreamListener::CachedChromeStreamListener(XULDocument* aDocument, bool aProtoLoaded) |
2394 | | : mDocument(aDocument), |
2395 | | mProtoLoaded(aProtoLoaded) |
2396 | 0 | { |
2397 | 0 | } |
2398 | | |
2399 | | |
2400 | | XULDocument::CachedChromeStreamListener::~CachedChromeStreamListener() |
2401 | 0 | { |
2402 | 0 | } |
2403 | | |
2404 | | |
2405 | | NS_IMPL_ISUPPORTS(XULDocument::CachedChromeStreamListener, |
2406 | | nsIRequestObserver, nsIStreamListener) |
2407 | | |
2408 | | NS_IMETHODIMP |
2409 | | XULDocument::CachedChromeStreamListener::OnStartRequest(nsIRequest *request, |
2410 | | nsISupports* acontext) |
2411 | 0 | { |
2412 | 0 | return NS_ERROR_PARSED_DATA_CACHED; |
2413 | 0 | } |
2414 | | |
2415 | | |
2416 | | NS_IMETHODIMP |
2417 | | XULDocument::CachedChromeStreamListener::OnStopRequest(nsIRequest *request, |
2418 | | nsISupports* aContext, |
2419 | | nsresult aStatus) |
2420 | 0 | { |
2421 | 0 | if (! mProtoLoaded) |
2422 | 0 | return NS_OK; |
2423 | 0 | |
2424 | 0 | return mDocument->OnPrototypeLoadDone(true); |
2425 | 0 | } |
2426 | | |
2427 | | |
2428 | | NS_IMETHODIMP |
2429 | | XULDocument::CachedChromeStreamListener::OnDataAvailable(nsIRequest *request, |
2430 | | nsISupports* aContext, |
2431 | | nsIInputStream* aInStr, |
2432 | | uint64_t aSourceOffset, |
2433 | | uint32_t aCount) |
2434 | 0 | { |
2435 | 0 | MOZ_ASSERT_UNREACHABLE("CachedChromeStream doesn't receive data"); |
2436 | 0 | return NS_ERROR_UNEXPECTED; |
2437 | 0 | } |
2438 | | |
2439 | | bool |
2440 | | XULDocument::IsDocumentRightToLeft() |
2441 | 0 | { |
2442 | 0 | // setting the localedir attribute on the root element forces a |
2443 | 0 | // specific direction for the document. |
2444 | 0 | Element* element = GetRootElement(); |
2445 | 0 | if (element) { |
2446 | 0 | static Element::AttrValuesArray strings[] = |
2447 | 0 | {&nsGkAtoms::ltr, &nsGkAtoms::rtl, nullptr}; |
2448 | 0 | switch (element->FindAttrValueIn(kNameSpaceID_None, nsGkAtoms::localedir, |
2449 | 0 | strings, eCaseMatters)) { |
2450 | 0 | case 0: return false; |
2451 | 0 | case 1: return true; |
2452 | 0 | default: break; // otherwise, not a valid value, so fall through |
2453 | 0 | } |
2454 | 0 | } |
2455 | 0 | |
2456 | 0 | // otherwise, get the locale from the chrome registry and |
2457 | 0 | // look up the intl.uidirection.<locale> preference |
2458 | 0 | nsCOMPtr<nsIXULChromeRegistry> reg = |
2459 | 0 | mozilla::services::GetXULChromeRegistryService(); |
2460 | 0 | if (!reg) |
2461 | 0 | return false; |
2462 | 0 | |
2463 | 0 | nsAutoCString package; |
2464 | 0 | bool isChrome; |
2465 | 0 | if (NS_SUCCEEDED(mDocumentURI->SchemeIs("chrome", &isChrome)) && |
2466 | 0 | isChrome) { |
2467 | 0 | mDocumentURI->GetHostPort(package); |
2468 | 0 | } |
2469 | 0 | else { |
2470 | 0 | // use the 'global' package for about and resource uris. |
2471 | 0 | // otherwise, just default to left-to-right. |
2472 | 0 | bool isAbout, isResource; |
2473 | 0 | if (NS_SUCCEEDED(mDocumentURI->SchemeIs("about", &isAbout)) && |
2474 | 0 | isAbout) { |
2475 | 0 | package.AssignLiteral("global"); |
2476 | 0 | } |
2477 | 0 | else if (NS_SUCCEEDED(mDocumentURI->SchemeIs("resource", &isResource)) && |
2478 | 0 | isResource) { |
2479 | 0 | package.AssignLiteral("global"); |
2480 | 0 | } |
2481 | 0 | else { |
2482 | 0 | return false; |
2483 | 0 | } |
2484 | 0 | } |
2485 | 0 | |
2486 | 0 | bool isRTL = false; |
2487 | 0 | reg->IsLocaleRTL(package, &isRTL); |
2488 | 0 | return isRTL; |
2489 | 0 | } |
2490 | | |
2491 | | void |
2492 | | XULDocument::ResetDocumentDirection() |
2493 | 0 | { |
2494 | 0 | DocumentStatesChanged(NS_DOCUMENT_STATE_RTL_LOCALE); |
2495 | 0 | } |
2496 | | |
2497 | | void |
2498 | | XULDocument::DirectionChanged(const char* aPrefName, XULDocument* aDoc) |
2499 | 0 | { |
2500 | 0 | // Reset the direction and restyle the document if necessary. |
2501 | 0 | if (aDoc) { |
2502 | 0 | aDoc->ResetDocumentDirection(); |
2503 | 0 | } |
2504 | 0 | } |
2505 | | |
2506 | | JSObject* |
2507 | | XULDocument::WrapNode(JSContext *aCx, JS::Handle<JSObject*> aGivenProto) |
2508 | 0 | { |
2509 | 0 | return XULDocument_Binding::Wrap(aCx, this, aGivenProto); |
2510 | 0 | } |
2511 | | |
2512 | | } // namespace dom |
2513 | | } // namespace mozilla |