/src/mozilla-central/dom/html/nsHTMLContentSink.cpp
Line | Count | Source (jump to first uncovered line) |
1 | | /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ |
2 | | /* vim: set ts=8 sts=2 et sw=2 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 | | * This file is near-OBSOLETE. It is used for about:blank only and for the |
9 | | * HTML element factory. |
10 | | * Don't bother adding new stuff in this file. |
11 | | */ |
12 | | |
13 | | #include "mozilla/ArrayUtils.h" |
14 | | |
15 | | #include "nsContentSink.h" |
16 | | #include "nsCOMPtr.h" |
17 | | #include "nsHTMLTags.h" |
18 | | #include "nsReadableUtils.h" |
19 | | #include "nsUnicharUtils.h" |
20 | | #include "nsIHTMLContentSink.h" |
21 | | #include "nsIInterfaceRequestor.h" |
22 | | #include "nsIInterfaceRequestorUtils.h" |
23 | | #include "nsIURI.h" |
24 | | #include "nsIContentViewer.h" |
25 | | #include "mozilla/dom/NodeInfo.h" |
26 | | #include "mozilla/dom/ScriptLoader.h" |
27 | | #include "nsIAppShell.h" |
28 | | #include "nsCRT.h" |
29 | | #include "prtime.h" |
30 | | #include "mozilla/Logging.h" |
31 | | #include "nsNodeUtils.h" |
32 | | #include "nsIContent.h" |
33 | | #include "mozilla/dom/CustomElementRegistry.h" |
34 | | #include "mozilla/dom/Element.h" |
35 | | #include "mozilla/Preferences.h" |
36 | | |
37 | | #include "nsGenericHTMLElement.h" |
38 | | |
39 | | #include "nsIScriptElement.h" |
40 | | |
41 | | #include "nsIComponentManager.h" |
42 | | #include "nsIServiceManager.h" |
43 | | |
44 | | #include "nsGkAtoms.h" |
45 | | #include "nsContentUtils.h" |
46 | | #include "nsIChannel.h" |
47 | | #include "nsIHttpChannel.h" |
48 | | #include "nsIDocShell.h" |
49 | | #include "nsIDocument.h" |
50 | | #include "nsStubDocumentObserver.h" |
51 | | #include "nsIHTMLDocument.h" |
52 | | #include "nsICookieService.h" |
53 | | #include "nsTArray.h" |
54 | | #include "nsIScriptSecurityManager.h" |
55 | | #include "nsIPrincipal.h" |
56 | | #include "nsTextFragment.h" |
57 | | #include "nsIScriptGlobalObject.h" |
58 | | #include "nsNameSpaceManager.h" |
59 | | |
60 | | #include "nsIStyleSheetLinkingElement.h" |
61 | | #include "nsITimer.h" |
62 | | #include "nsError.h" |
63 | | #include "nsContentPolicyUtils.h" |
64 | | #include "nsIScriptContext.h" |
65 | | #include "nsStyleLinkElement.h" |
66 | | |
67 | | #include "nsWeakReference.h" // nsHTMLElementFactory supports weak references |
68 | | #include "nsIPrompt.h" |
69 | | #include "nsLayoutCID.h" |
70 | | #include "nsIDocShellTreeItem.h" |
71 | | |
72 | | #include "nsEscape.h" |
73 | | #include "nsNodeInfoManager.h" |
74 | | #include "nsContentCreatorFunctions.h" |
75 | | #include "mozAutoDocUpdate.h" |
76 | | #include "nsTextNode.h" |
77 | | |
78 | | using namespace mozilla; |
79 | | using namespace mozilla::dom; |
80 | | |
81 | | //---------------------------------------------------------------------- |
82 | | |
83 | | nsGenericHTMLElement* |
84 | | NS_NewHTMLNOTUSEDElement(already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo, |
85 | | FromParser aFromParser) |
86 | 0 | { |
87 | 0 | MOZ_ASSERT_UNREACHABLE("The element ctor should never be called"); |
88 | 0 | return nullptr; |
89 | 0 | } |
90 | | |
91 | | #define HTML_TAG(_tag, _classname, _interfacename) NS_NewHTML##_classname##Element, |
92 | | #define HTML_OTHER(_tag) NS_NewHTMLNOTUSEDElement, |
93 | | static const HTMLContentCreatorFunction sHTMLContentCreatorFunctions[] = { |
94 | | NS_NewHTMLUnknownElement, |
95 | | #include "nsHTMLTagList.h" |
96 | | #undef HTML_TAG |
97 | | #undef HTML_OTHER |
98 | | NS_NewHTMLUnknownElement |
99 | | }; |
100 | | |
101 | | class SinkContext; |
102 | | class HTMLContentSink; |
103 | | |
104 | | /** |
105 | | * This class is near-OBSOLETE. It is used for about:blank only. |
106 | | * Don't bother adding new stuff in this file. |
107 | | */ |
108 | | class HTMLContentSink : public nsContentSink, |
109 | | public nsIHTMLContentSink |
110 | | { |
111 | | public: |
112 | | friend class SinkContext; |
113 | | |
114 | | HTMLContentSink(); |
115 | | |
116 | | nsresult Init(nsIDocument* aDoc, nsIURI* aURI, nsISupports* aContainer, |
117 | | nsIChannel* aChannel); |
118 | | |
119 | | // nsISupports |
120 | | NS_DECL_ISUPPORTS_INHERITED |
121 | | NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(HTMLContentSink, nsContentSink) |
122 | | |
123 | | // nsIContentSink |
124 | | NS_IMETHOD WillParse(void) override; |
125 | | NS_IMETHOD WillBuildModel(nsDTDMode aDTDMode) override; |
126 | | NS_IMETHOD DidBuildModel(bool aTerminated) override; |
127 | | NS_IMETHOD WillInterrupt(void) override; |
128 | | NS_IMETHOD WillResume(void) override; |
129 | | NS_IMETHOD SetParser(nsParserBase* aParser) override; |
130 | | virtual void FlushPendingNotifications(FlushType aType) override; |
131 | | virtual void SetDocumentCharset(NotNull<const Encoding*> aEncoding) override; |
132 | | virtual nsISupports *GetTarget() override; |
133 | | virtual bool IsScriptExecuting() override; |
134 | | virtual void ContinueInterruptedParsingAsync() override; |
135 | | |
136 | | // nsIHTMLContentSink |
137 | | NS_IMETHOD OpenContainer(ElementType aNodeType) override; |
138 | | NS_IMETHOD CloseContainer(ElementType aTag) override; |
139 | | |
140 | | protected: |
141 | | virtual ~HTMLContentSink(); |
142 | | |
143 | | nsCOMPtr<nsIHTMLDocument> mHTMLDocument; |
144 | | |
145 | | // The maximum length of a text run |
146 | | int32_t mMaxTextRun; |
147 | | |
148 | | RefPtr<nsGenericHTMLElement> mRoot; |
149 | | RefPtr<nsGenericHTMLElement> mBody; |
150 | | RefPtr<nsGenericHTMLElement> mHead; |
151 | | |
152 | | AutoTArray<SinkContext*, 8> mContextStack; |
153 | | SinkContext* mCurrentContext; |
154 | | SinkContext* mHeadContext; |
155 | | |
156 | | // Boolean indicating whether we've seen a <head> tag that might have had |
157 | | // attributes once already. |
158 | | bool mHaveSeenHead; |
159 | | |
160 | | // Boolean indicating whether we've notified insertion of our root content |
161 | | // yet. We want to make sure to only do this once. |
162 | | bool mNotifiedRootInsertion; |
163 | | |
164 | | nsresult FlushTags() override; |
165 | | |
166 | | // Routines for tags that require special handling |
167 | | nsresult CloseHTML(); |
168 | | nsresult OpenBody(); |
169 | | nsresult CloseBody(); |
170 | | |
171 | | void CloseHeadContext(); |
172 | | |
173 | | // nsContentSink overrides |
174 | | void UpdateChildCounts() override; |
175 | | |
176 | | void NotifyInsert(nsIContent* aContent, |
177 | | nsIContent* aChildContent); |
178 | | void NotifyRootInsertion(); |
179 | | |
180 | | private: |
181 | | void ContinueInterruptedParsingIfEnabled(); |
182 | | }; |
183 | | |
184 | | class SinkContext |
185 | | { |
186 | | public: |
187 | | explicit SinkContext(HTMLContentSink* aSink); |
188 | | ~SinkContext(); |
189 | | |
190 | | nsresult Begin(nsHTMLTag aNodeType, nsGenericHTMLElement* aRoot, |
191 | | uint32_t aNumFlushed, int32_t aInsertionPoint); |
192 | | nsresult OpenBody(); |
193 | | nsresult CloseBody(); |
194 | | nsresult End(); |
195 | | |
196 | | nsresult GrowStack(); |
197 | | nsresult FlushTags(); |
198 | | |
199 | | bool IsCurrentContainer(nsHTMLTag mType); |
200 | | |
201 | | void DidAddContent(nsIContent* aContent); |
202 | | void UpdateChildCounts(); |
203 | | |
204 | | private: |
205 | | // Function to check whether we've notified for the current content. |
206 | | // What this actually does is check whether we've notified for all |
207 | | // of the parent's kids. |
208 | | bool HaveNotifiedForCurrentContent() const; |
209 | | |
210 | | public: |
211 | | HTMLContentSink* mSink; |
212 | | int32_t mNotifyLevel; |
213 | | |
214 | | struct Node { |
215 | | nsHTMLTag mType; |
216 | | nsGenericHTMLElement* mContent; |
217 | | uint32_t mNumFlushed; |
218 | | int32_t mInsertionPoint; |
219 | | |
220 | | nsIContent *Add(nsIContent *child); |
221 | | }; |
222 | | |
223 | | Node* mStack; |
224 | | int32_t mStackSize; |
225 | | int32_t mStackPos; |
226 | | }; |
227 | | |
228 | | nsresult |
229 | | NS_NewHTMLElement(Element** aResult, already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo, |
230 | | FromParser aFromParser, nsAtom* aIsAtom, |
231 | | mozilla::dom::CustomElementDefinition* aDefinition) |
232 | 0 | { |
233 | 0 | RefPtr<mozilla::dom::NodeInfo> nodeInfo = aNodeInfo; |
234 | 0 |
|
235 | 0 | NS_ASSERTION(nodeInfo->NamespaceEquals(kNameSpaceID_XHTML), |
236 | 0 | "Trying to create HTML elements that don't have the XHTML namespace"); |
237 | 0 |
|
238 | 0 | return nsContentUtils::NewXULOrHTMLElement(aResult, nodeInfo, aFromParser, aIsAtom, aDefinition); |
239 | 0 | } |
240 | | |
241 | | already_AddRefed<nsGenericHTMLElement> |
242 | | CreateHTMLElement(uint32_t aNodeType, |
243 | | already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo, |
244 | | FromParser aFromParser) |
245 | 0 | { |
246 | 0 | NS_ASSERTION(aNodeType <= NS_HTML_TAG_MAX || |
247 | 0 | aNodeType == eHTMLTag_userdefined, |
248 | 0 | "aNodeType is out of bounds"); |
249 | 0 |
|
250 | 0 | HTMLContentCreatorFunction cb = sHTMLContentCreatorFunctions[aNodeType]; |
251 | 0 |
|
252 | 0 | NS_ASSERTION(cb != NS_NewHTMLNOTUSEDElement, |
253 | 0 | "Don't know how to construct tag element!"); |
254 | 0 |
|
255 | 0 | RefPtr<nsGenericHTMLElement> result = cb(std::move(aNodeInfo), aFromParser); |
256 | 0 |
|
257 | 0 | return result.forget(); |
258 | 0 | } |
259 | | |
260 | | //---------------------------------------------------------------------- |
261 | | |
262 | | SinkContext::SinkContext(HTMLContentSink* aSink) |
263 | | : mSink(aSink), |
264 | | mNotifyLevel(0), |
265 | | mStack(nullptr), |
266 | | mStackSize(0), |
267 | | mStackPos(0) |
268 | 0 | { |
269 | 0 | MOZ_COUNT_CTOR(SinkContext); |
270 | 0 | } |
271 | | |
272 | | SinkContext::~SinkContext() |
273 | 0 | { |
274 | 0 | MOZ_COUNT_DTOR(SinkContext); |
275 | 0 |
|
276 | 0 | if (mStack) { |
277 | 0 | for (int32_t i = 0; i < mStackPos; i++) { |
278 | 0 | NS_RELEASE(mStack[i].mContent); |
279 | 0 | } |
280 | 0 | delete [] mStack; |
281 | 0 | } |
282 | 0 | } |
283 | | |
284 | | nsresult |
285 | | SinkContext::Begin(nsHTMLTag aNodeType, |
286 | | nsGenericHTMLElement* aRoot, |
287 | | uint32_t aNumFlushed, |
288 | | int32_t aInsertionPoint) |
289 | 0 | { |
290 | 0 | if (mStackSize < 1) { |
291 | 0 | nsresult rv = GrowStack(); |
292 | 0 | if (NS_FAILED(rv)) { |
293 | 0 | return rv; |
294 | 0 | } |
295 | 0 | } |
296 | 0 | |
297 | 0 | mStack[0].mType = aNodeType; |
298 | 0 | mStack[0].mContent = aRoot; |
299 | 0 | mStack[0].mNumFlushed = aNumFlushed; |
300 | 0 | mStack[0].mInsertionPoint = aInsertionPoint; |
301 | 0 | NS_ADDREF(aRoot); |
302 | 0 | mStackPos = 1; |
303 | 0 |
|
304 | 0 | return NS_OK; |
305 | 0 | } |
306 | | |
307 | | bool |
308 | | SinkContext::IsCurrentContainer(nsHTMLTag aTag) |
309 | 0 | { |
310 | 0 | if (aTag == mStack[mStackPos - 1].mType) { |
311 | 0 | return true; |
312 | 0 | } |
313 | 0 | |
314 | 0 | return false; |
315 | 0 | } |
316 | | |
317 | | void |
318 | | SinkContext::DidAddContent(nsIContent* aContent) |
319 | 0 | { |
320 | 0 | if ((mStackPos == 2) && (mSink->mBody == mStack[1].mContent)) { |
321 | 0 | // We just finished adding something to the body |
322 | 0 | mNotifyLevel = 0; |
323 | 0 | } |
324 | 0 |
|
325 | 0 | // If we just added content to a node for which |
326 | 0 | // an insertion happen, we need to do an immediate |
327 | 0 | // notification for that insertion. |
328 | 0 | if (0 < mStackPos && |
329 | 0 | mStack[mStackPos - 1].mInsertionPoint != -1 && |
330 | 0 | mStack[mStackPos - 1].mNumFlushed < |
331 | 0 | mStack[mStackPos - 1].mContent->GetChildCount()) { |
332 | 0 | nsIContent* parent = mStack[mStackPos - 1].mContent; |
333 | 0 | mSink->NotifyInsert(parent, aContent); |
334 | 0 | mStack[mStackPos - 1].mNumFlushed = parent->GetChildCount(); |
335 | 0 | } else if (mSink->IsTimeToNotify()) { |
336 | 0 | FlushTags(); |
337 | 0 | } |
338 | 0 | } |
339 | | |
340 | | nsresult |
341 | | SinkContext::OpenBody() |
342 | 0 | { |
343 | 0 | if (mStackPos <= 0) { |
344 | 0 | NS_ERROR("container w/o parent"); |
345 | 0 |
|
346 | 0 | return NS_ERROR_FAILURE; |
347 | 0 | } |
348 | 0 |
|
349 | 0 | nsresult rv; |
350 | 0 | if (mStackPos + 1 > mStackSize) { |
351 | 0 | rv = GrowStack(); |
352 | 0 | if (NS_FAILED(rv)) { |
353 | 0 | return rv; |
354 | 0 | } |
355 | 0 | } |
356 | 0 | |
357 | 0 | RefPtr<mozilla::dom::NodeInfo> nodeInfo = |
358 | 0 | mSink->mNodeInfoManager->GetNodeInfo(nsGkAtoms::body, nullptr, |
359 | 0 | kNameSpaceID_XHTML, |
360 | 0 | nsINode::ELEMENT_NODE); |
361 | 0 | NS_ENSURE_TRUE(nodeInfo, NS_ERROR_UNEXPECTED); |
362 | 0 |
|
363 | 0 | // Make the content object |
364 | 0 | RefPtr<nsGenericHTMLElement> body = |
365 | 0 | NS_NewHTMLBodyElement(nodeInfo.forget(), FROM_PARSER_NETWORK); |
366 | 0 | if (!body) { |
367 | 0 | return NS_ERROR_OUT_OF_MEMORY; |
368 | 0 | } |
369 | 0 | |
370 | 0 | mStack[mStackPos].mType = eHTMLTag_body; |
371 | 0 | body.forget(&mStack[mStackPos].mContent); |
372 | 0 | mStack[mStackPos].mNumFlushed = 0; |
373 | 0 | mStack[mStackPos].mInsertionPoint = -1; |
374 | 0 | ++mStackPos; |
375 | 0 | mStack[mStackPos - 2].Add(mStack[mStackPos - 1].mContent); |
376 | 0 |
|
377 | 0 | return NS_OK; |
378 | 0 | } |
379 | | |
380 | | bool |
381 | | SinkContext::HaveNotifiedForCurrentContent() const |
382 | 0 | { |
383 | 0 | if (0 < mStackPos) { |
384 | 0 | nsIContent* parent = mStack[mStackPos - 1].mContent; |
385 | 0 | return mStack[mStackPos-1].mNumFlushed == parent->GetChildCount(); |
386 | 0 | } |
387 | 0 | |
388 | 0 | return true; |
389 | 0 | } |
390 | | |
391 | | nsIContent * |
392 | | SinkContext::Node::Add(nsIContent *child) |
393 | 0 | { |
394 | 0 | NS_ASSERTION(mContent, "No parent to insert/append into!"); |
395 | 0 | if (mInsertionPoint != -1) { |
396 | 0 | NS_ASSERTION(mNumFlushed == mContent->GetChildCount(), |
397 | 0 | "Inserting multiple children without flushing."); |
398 | 0 | nsCOMPtr<nsIContent> nodeToInsertBefore = |
399 | 0 | mContent->GetChildAt_Deprecated(mInsertionPoint++); |
400 | 0 | mContent->InsertChildBefore(child, nodeToInsertBefore, false); |
401 | 0 | } else { |
402 | 0 | mContent->AppendChildTo(child, false); |
403 | 0 | } |
404 | 0 | return child; |
405 | 0 | } |
406 | | |
407 | | nsresult |
408 | | SinkContext::CloseBody() |
409 | 0 | { |
410 | 0 | NS_ASSERTION(mStackPos > 0, |
411 | 0 | "stack out of bounds. wrong context probably!"); |
412 | 0 |
|
413 | 0 | if (mStackPos <= 0) { |
414 | 0 | return NS_OK; // Fix crash - Ref. bug 45975 or 45007 |
415 | 0 | } |
416 | 0 | |
417 | 0 | --mStackPos; |
418 | 0 | NS_ASSERTION(mStack[mStackPos].mType == eHTMLTag_body, |
419 | 0 | "Tag mismatch. Closing tag on wrong context or something?"); |
420 | 0 |
|
421 | 0 | nsGenericHTMLElement* content = mStack[mStackPos].mContent; |
422 | 0 |
|
423 | 0 | content->Compact(); |
424 | 0 |
|
425 | 0 | // If we're in a state where we do append notifications as |
426 | 0 | // we go up the tree, and we're at the level where the next |
427 | 0 | // notification needs to be done, do the notification. |
428 | 0 | if (mNotifyLevel >= mStackPos) { |
429 | 0 | // Check to see if new content has been added after our last |
430 | 0 | // notification |
431 | 0 |
|
432 | 0 | if (mStack[mStackPos].mNumFlushed < content->GetChildCount()) { |
433 | 0 | mSink->NotifyAppend(content, mStack[mStackPos].mNumFlushed); |
434 | 0 | mStack[mStackPos].mNumFlushed = content->GetChildCount(); |
435 | 0 | } |
436 | 0 |
|
437 | 0 | // Indicate that notification has now happened at this level |
438 | 0 | mNotifyLevel = mStackPos - 1; |
439 | 0 | } |
440 | 0 |
|
441 | 0 | DidAddContent(content); |
442 | 0 | NS_IF_RELEASE(content); |
443 | 0 |
|
444 | 0 | return NS_OK; |
445 | 0 | } |
446 | | |
447 | | nsresult |
448 | | SinkContext::End() |
449 | 0 | { |
450 | 0 | for (int32_t i = 0; i < mStackPos; i++) { |
451 | 0 | NS_RELEASE(mStack[i].mContent); |
452 | 0 | } |
453 | 0 |
|
454 | 0 | mStackPos = 0; |
455 | 0 |
|
456 | 0 | return NS_OK; |
457 | 0 | } |
458 | | |
459 | | nsresult |
460 | | SinkContext::GrowStack() |
461 | 0 | { |
462 | 0 | int32_t newSize = mStackSize * 2; |
463 | 0 | if (newSize == 0) { |
464 | 0 | newSize = 32; |
465 | 0 | } |
466 | 0 |
|
467 | 0 | Node* stack = new Node[newSize]; |
468 | 0 |
|
469 | 0 | if (mStackPos != 0) { |
470 | 0 | memcpy(stack, mStack, sizeof(Node) * mStackPos); |
471 | 0 | delete [] mStack; |
472 | 0 | } |
473 | 0 |
|
474 | 0 | mStack = stack; |
475 | 0 | mStackSize = newSize; |
476 | 0 |
|
477 | 0 | return NS_OK; |
478 | 0 | } |
479 | | |
480 | | /** |
481 | | * NOTE!! Forked into nsXMLContentSink. Please keep in sync. |
482 | | * |
483 | | * Flush all elements that have been seen so far such that |
484 | | * they are visible in the tree. Specifically, make sure |
485 | | * that they are all added to their respective parents. |
486 | | * Also, do notification at the top for all content that |
487 | | * has been newly added so that the frame tree is complete. |
488 | | */ |
489 | | nsresult |
490 | | SinkContext::FlushTags() |
491 | 0 | { |
492 | 0 | mSink->mDeferredFlushTags = false; |
493 | 0 | bool oldBeganUpdate = mSink->mBeganUpdate; |
494 | 0 | uint32_t oldUpdates = mSink->mUpdatesInNotification; |
495 | 0 |
|
496 | 0 | ++(mSink->mInNotification); |
497 | 0 | mSink->mUpdatesInNotification = 0; |
498 | 0 | { |
499 | 0 | // Scope so we call EndUpdate before we decrease mInNotification |
500 | 0 | mozAutoDocUpdate updateBatch(mSink->mDocument, true); |
501 | 0 | mSink->mBeganUpdate = true; |
502 | 0 |
|
503 | 0 | // Start from the base of the stack (growing downward) and do |
504 | 0 | // a notification from the node that is closest to the root of |
505 | 0 | // tree for any content that has been added. |
506 | 0 |
|
507 | 0 | // Note that we can start at stackPos == 0 here, because it's the caller's |
508 | 0 | // responsibility to handle flushing interactions between contexts (see |
509 | 0 | // HTMLContentSink::BeginContext). |
510 | 0 | int32_t stackPos = 0; |
511 | 0 | bool flushed = false; |
512 | 0 | uint32_t childCount; |
513 | 0 | nsGenericHTMLElement* content; |
514 | 0 |
|
515 | 0 | while (stackPos < mStackPos) { |
516 | 0 | content = mStack[stackPos].mContent; |
517 | 0 | childCount = content->GetChildCount(); |
518 | 0 |
|
519 | 0 | if (!flushed && (mStack[stackPos].mNumFlushed < childCount)) { |
520 | 0 | if (mStack[stackPos].mInsertionPoint != -1) { |
521 | 0 | // We might have popped the child off our stack already |
522 | 0 | // but not notified on it yet, which is why we have to get it |
523 | 0 | // directly from its parent node. |
524 | 0 |
|
525 | 0 | int32_t childIndex = mStack[stackPos].mInsertionPoint - 1; |
526 | 0 | nsIContent* child = content->GetChildAt_Deprecated(childIndex); |
527 | 0 | // Child not on stack anymore; can't assert it's correct |
528 | 0 | NS_ASSERTION(!(mStackPos > (stackPos + 1)) || |
529 | 0 | (child == mStack[stackPos + 1].mContent), |
530 | 0 | "Flushing the wrong child."); |
531 | 0 | mSink->NotifyInsert(content, child); |
532 | 0 | } else { |
533 | 0 | mSink->NotifyAppend(content, mStack[stackPos].mNumFlushed); |
534 | 0 | } |
535 | 0 |
|
536 | 0 | flushed = true; |
537 | 0 | } |
538 | 0 |
|
539 | 0 | mStack[stackPos].mNumFlushed = childCount; |
540 | 0 | stackPos++; |
541 | 0 | } |
542 | 0 | mNotifyLevel = mStackPos - 1; |
543 | 0 | } |
544 | 0 | --(mSink->mInNotification); |
545 | 0 |
|
546 | 0 | if (mSink->mUpdatesInNotification > 1) { |
547 | 0 | UpdateChildCounts(); |
548 | 0 | } |
549 | 0 |
|
550 | 0 | mSink->mUpdatesInNotification = oldUpdates; |
551 | 0 | mSink->mBeganUpdate = oldBeganUpdate; |
552 | 0 |
|
553 | 0 | return NS_OK; |
554 | 0 | } |
555 | | |
556 | | /** |
557 | | * NOTE!! Forked into nsXMLContentSink. Please keep in sync. |
558 | | */ |
559 | | void |
560 | | SinkContext::UpdateChildCounts() |
561 | 0 | { |
562 | 0 | // Start from the top of the stack (growing upwards) and see if any |
563 | 0 | // new content has been appended. If so, we recognize that reflows |
564 | 0 | // have been generated for it and we should make sure that no |
565 | 0 | // further reflows occur. Note that we have to include stackPos == 0 |
566 | 0 | // to properly notify on kids of <html>. |
567 | 0 | int32_t stackPos = mStackPos - 1; |
568 | 0 | while (stackPos >= 0) { |
569 | 0 | Node & node = mStack[stackPos]; |
570 | 0 | node.mNumFlushed = node.mContent->GetChildCount(); |
571 | 0 |
|
572 | 0 | stackPos--; |
573 | 0 | } |
574 | 0 |
|
575 | 0 | mNotifyLevel = mStackPos - 1; |
576 | 0 | } |
577 | | |
578 | | nsresult |
579 | | NS_NewHTMLContentSink(nsIHTMLContentSink** aResult, |
580 | | nsIDocument* aDoc, |
581 | | nsIURI* aURI, |
582 | | nsISupports* aContainer, |
583 | | nsIChannel* aChannel) |
584 | 0 | { |
585 | 0 | NS_ENSURE_ARG_POINTER(aResult); |
586 | 0 |
|
587 | 0 | RefPtr<HTMLContentSink> it = new HTMLContentSink(); |
588 | 0 |
|
589 | 0 | nsresult rv = it->Init(aDoc, aURI, aContainer, aChannel); |
590 | 0 |
|
591 | 0 | NS_ENSURE_SUCCESS(rv, rv); |
592 | 0 |
|
593 | 0 | *aResult = it; |
594 | 0 | NS_ADDREF(*aResult); |
595 | 0 |
|
596 | 0 | return NS_OK; |
597 | 0 | } |
598 | | |
599 | | HTMLContentSink::HTMLContentSink() |
600 | | : mMaxTextRun(0) |
601 | | , mCurrentContext(nullptr) |
602 | | , mHeadContext(nullptr) |
603 | | , mHaveSeenHead(false) |
604 | | , mNotifiedRootInsertion(false) |
605 | 0 | { |
606 | 0 | } |
607 | | |
608 | | HTMLContentSink::~HTMLContentSink() |
609 | 0 | { |
610 | 0 | if (mNotificationTimer) { |
611 | 0 | mNotificationTimer->Cancel(); |
612 | 0 | } |
613 | 0 |
|
614 | 0 | int32_t numContexts = mContextStack.Length(); |
615 | 0 |
|
616 | 0 | if (mCurrentContext == mHeadContext && numContexts > 0) { |
617 | 0 | // Pop off the second html context if it's not done earlier |
618 | 0 | mContextStack.RemoveElementAt(--numContexts); |
619 | 0 | } |
620 | 0 |
|
621 | 0 | int32_t i; |
622 | 0 | for (i = 0; i < numContexts; i++) { |
623 | 0 | SinkContext* sc = mContextStack.ElementAt(i); |
624 | 0 | if (sc) { |
625 | 0 | sc->End(); |
626 | 0 | if (sc == mCurrentContext) { |
627 | 0 | mCurrentContext = nullptr; |
628 | 0 | } |
629 | 0 |
|
630 | 0 | delete sc; |
631 | 0 | } |
632 | 0 | } |
633 | 0 |
|
634 | 0 | if (mCurrentContext == mHeadContext) { |
635 | 0 | mCurrentContext = nullptr; |
636 | 0 | } |
637 | 0 |
|
638 | 0 | delete mCurrentContext; |
639 | 0 |
|
640 | 0 | delete mHeadContext; |
641 | 0 | } |
642 | | |
643 | | NS_IMPL_CYCLE_COLLECTION_INHERITED(HTMLContentSink, nsContentSink, |
644 | | mHTMLDocument, |
645 | | mRoot, |
646 | | mBody, |
647 | | mHead) |
648 | | |
649 | | NS_IMPL_ISUPPORTS_CYCLE_COLLECTION_INHERITED(HTMLContentSink, |
650 | | nsContentSink, |
651 | | nsIContentSink, |
652 | | nsIHTMLContentSink) |
653 | | |
654 | | nsresult |
655 | | HTMLContentSink::Init(nsIDocument* aDoc, |
656 | | nsIURI* aURI, |
657 | | nsISupports* aContainer, |
658 | | nsIChannel* aChannel) |
659 | 0 | { |
660 | 0 | NS_ENSURE_TRUE(aContainer, NS_ERROR_NULL_POINTER); |
661 | 0 |
|
662 | 0 | nsresult rv = nsContentSink::Init(aDoc, aURI, aContainer, aChannel); |
663 | 0 | if (NS_FAILED(rv)) { |
664 | 0 | return rv; |
665 | 0 | } |
666 | 0 | |
667 | 0 | aDoc->AddObserver(this); |
668 | 0 | mIsDocumentObserver = true; |
669 | 0 | mHTMLDocument = do_QueryInterface(aDoc); |
670 | 0 |
|
671 | 0 | NS_ASSERTION(mDocShell, "oops no docshell!"); |
672 | 0 |
|
673 | 0 | // Changed from 8192 to greatly improve page loading performance on |
674 | 0 | // large pages. See bugzilla bug 77540. |
675 | 0 | mMaxTextRun = Preferences::GetInt("content.maxtextrun", 8191); |
676 | 0 |
|
677 | 0 | RefPtr<mozilla::dom::NodeInfo> nodeInfo; |
678 | 0 | nodeInfo = mNodeInfoManager->GetNodeInfo(nsGkAtoms::html, nullptr, |
679 | 0 | kNameSpaceID_XHTML, |
680 | 0 | nsINode::ELEMENT_NODE); |
681 | 0 |
|
682 | 0 | // Make root part |
683 | 0 | mRoot = NS_NewHTMLHtmlElement(nodeInfo.forget()); |
684 | 0 | if (!mRoot) { |
685 | 0 | return NS_ERROR_OUT_OF_MEMORY; |
686 | 0 | } |
687 | 0 | |
688 | 0 | NS_ASSERTION(mDocument->GetChildCount() == 0, |
689 | 0 | "Document should have no kids here!"); |
690 | 0 | rv = mDocument->AppendChildTo(mRoot, false); |
691 | 0 | NS_ENSURE_SUCCESS(rv, rv); |
692 | 0 |
|
693 | 0 | // Make head part |
694 | 0 | nodeInfo = mNodeInfoManager->GetNodeInfo(nsGkAtoms::head, |
695 | 0 | nullptr, kNameSpaceID_XHTML, |
696 | 0 | nsINode::ELEMENT_NODE); |
697 | 0 |
|
698 | 0 | mHead = NS_NewHTMLHeadElement(nodeInfo.forget()); |
699 | 0 | if (NS_FAILED(rv)) { |
700 | 0 | return NS_ERROR_OUT_OF_MEMORY; |
701 | 0 | } |
702 | 0 | |
703 | 0 | mRoot->AppendChildTo(mHead, false); |
704 | 0 |
|
705 | 0 | mCurrentContext = new SinkContext(this); |
706 | 0 | mCurrentContext->Begin(eHTMLTag_html, mRoot, 0, -1); |
707 | 0 | mContextStack.AppendElement(mCurrentContext); |
708 | 0 |
|
709 | 0 | return NS_OK; |
710 | 0 | } |
711 | | |
712 | | NS_IMETHODIMP |
713 | | HTMLContentSink::WillParse(void) |
714 | 0 | { |
715 | 0 | return WillParseImpl(); |
716 | 0 | } |
717 | | |
718 | | NS_IMETHODIMP |
719 | | HTMLContentSink::WillBuildModel(nsDTDMode aDTDMode) |
720 | 0 | { |
721 | 0 | WillBuildModelImpl(); |
722 | 0 |
|
723 | 0 | if (mHTMLDocument) { |
724 | 0 | nsCompatibility mode = eCompatibility_NavQuirks; |
725 | 0 | switch (aDTDMode) { |
726 | 0 | case eDTDMode_full_standards: |
727 | 0 | mode = eCompatibility_FullStandards; |
728 | 0 | break; |
729 | 0 | case eDTDMode_almost_standards: |
730 | 0 | mode = eCompatibility_AlmostStandards; |
731 | 0 | break; |
732 | 0 | default: |
733 | 0 | break; |
734 | 0 | } |
735 | 0 | mHTMLDocument->SetCompatibilityMode(mode); |
736 | 0 | } |
737 | 0 |
|
738 | 0 | // Notify document that the load is beginning |
739 | 0 | mDocument->BeginLoad(); |
740 | 0 |
|
741 | 0 | return NS_OK; |
742 | 0 | } |
743 | | |
744 | | NS_IMETHODIMP |
745 | | HTMLContentSink::DidBuildModel(bool aTerminated) |
746 | 0 | { |
747 | 0 | DidBuildModelImpl(aTerminated); |
748 | 0 |
|
749 | 0 | // Reflow the last batch of content |
750 | 0 | if (mBody) { |
751 | 0 | mCurrentContext->FlushTags(); |
752 | 0 | } else if (!mLayoutStarted) { |
753 | 0 | // We never saw the body, and layout never got started. Force |
754 | 0 | // layout *now*, to get an initial reflow. |
755 | 0 | // NOTE: only force the layout if we are NOT destroying the |
756 | 0 | // docshell. If we are destroying it, then starting layout will |
757 | 0 | // likely cause us to crash, or at best waste a lot of time as we |
758 | 0 | // are just going to tear it down anyway. |
759 | 0 | bool bDestroying = true; |
760 | 0 | if (mDocShell) { |
761 | 0 | mDocShell->IsBeingDestroyed(&bDestroying); |
762 | 0 | } |
763 | 0 |
|
764 | 0 | if (!bDestroying) { |
765 | 0 | StartLayout(false); |
766 | 0 | } |
767 | 0 | } |
768 | 0 |
|
769 | 0 | ScrollToRef(); |
770 | 0 |
|
771 | 0 | // Make sure we no longer respond to document mutations. We've flushed all |
772 | 0 | // our notifications out, so there's no need to do anything else here. |
773 | 0 |
|
774 | 0 | // XXXbz I wonder whether we could End() our contexts here too, or something, |
775 | 0 | // just to make sure we no longer notify... Or is the mIsDocumentObserver |
776 | 0 | // thing sufficient? |
777 | 0 | mDocument->RemoveObserver(this); |
778 | 0 | mIsDocumentObserver = false; |
779 | 0 |
|
780 | 0 | mDocument->EndLoad(); |
781 | 0 |
|
782 | 0 | DropParserAndPerfHint(); |
783 | 0 |
|
784 | 0 | return NS_OK; |
785 | 0 | } |
786 | | |
787 | | NS_IMETHODIMP |
788 | | HTMLContentSink::SetParser(nsParserBase* aParser) |
789 | 0 | { |
790 | 0 | MOZ_ASSERT(aParser, "Should have a parser here!"); |
791 | 0 | mParser = aParser; |
792 | 0 | return NS_OK; |
793 | 0 | } |
794 | | |
795 | | nsresult |
796 | | HTMLContentSink::CloseHTML() |
797 | 0 | { |
798 | 0 | if (mHeadContext) { |
799 | 0 | if (mCurrentContext == mHeadContext) { |
800 | 0 | uint32_t numContexts = mContextStack.Length(); |
801 | 0 |
|
802 | 0 | // Pop off the second html context if it's not done earlier |
803 | 0 | mCurrentContext = mContextStack.ElementAt(--numContexts); |
804 | 0 | mContextStack.RemoveElementAt(numContexts); |
805 | 0 | } |
806 | 0 |
|
807 | 0 | mHeadContext->End(); |
808 | 0 |
|
809 | 0 | delete mHeadContext; |
810 | 0 | mHeadContext = nullptr; |
811 | 0 | } |
812 | 0 |
|
813 | 0 | return NS_OK; |
814 | 0 | } |
815 | | |
816 | | nsresult |
817 | | HTMLContentSink::OpenBody() |
818 | 0 | { |
819 | 0 | CloseHeadContext(); // do this just in case if the HEAD was left open! |
820 | 0 |
|
821 | 0 | // if we already have a body we're done |
822 | 0 | if (mBody) { |
823 | 0 | return NS_OK; |
824 | 0 | } |
825 | 0 | |
826 | 0 | nsresult rv = mCurrentContext->OpenBody(); |
827 | 0 |
|
828 | 0 | if (NS_FAILED(rv)) { |
829 | 0 | return rv; |
830 | 0 | } |
831 | 0 | |
832 | 0 | mBody = mCurrentContext->mStack[mCurrentContext->mStackPos - 1].mContent; |
833 | 0 |
|
834 | 0 | if (mCurrentContext->mStackPos > 1) { |
835 | 0 | int32_t parentIndex = mCurrentContext->mStackPos - 2; |
836 | 0 | nsGenericHTMLElement *parent = mCurrentContext->mStack[parentIndex].mContent; |
837 | 0 | int32_t numFlushed = mCurrentContext->mStack[parentIndex].mNumFlushed; |
838 | 0 | int32_t childCount = parent->GetChildCount(); |
839 | 0 | NS_ASSERTION(numFlushed < childCount, "Already notified on the body?"); |
840 | 0 |
|
841 | 0 | int32_t insertionPoint = |
842 | 0 | mCurrentContext->mStack[parentIndex].mInsertionPoint; |
843 | 0 |
|
844 | 0 | // XXX: I have yet to see a case where numFlushed is non-zero and |
845 | 0 | // insertionPoint is not -1, but this code will try to handle |
846 | 0 | // those cases too. |
847 | 0 |
|
848 | 0 | uint32_t oldUpdates = mUpdatesInNotification; |
849 | 0 | mUpdatesInNotification = 0; |
850 | 0 | if (insertionPoint != -1) { |
851 | 0 | NotifyInsert(parent, mBody); |
852 | 0 | } else { |
853 | 0 | NotifyAppend(parent, numFlushed); |
854 | 0 | } |
855 | 0 | mCurrentContext->mStack[parentIndex].mNumFlushed = childCount; |
856 | 0 | if (mUpdatesInNotification > 1) { |
857 | 0 | UpdateChildCounts(); |
858 | 0 | } |
859 | 0 | mUpdatesInNotification = oldUpdates; |
860 | 0 | } |
861 | 0 |
|
862 | 0 | StartLayout(false); |
863 | 0 |
|
864 | 0 | return NS_OK; |
865 | 0 | } |
866 | | |
867 | | nsresult |
868 | | HTMLContentSink::CloseBody() |
869 | 0 | { |
870 | 0 | // Flush out anything that's left |
871 | 0 | mCurrentContext->FlushTags(); |
872 | 0 | mCurrentContext->CloseBody(); |
873 | 0 |
|
874 | 0 | return NS_OK; |
875 | 0 | } |
876 | | |
877 | | NS_IMETHODIMP |
878 | | HTMLContentSink::OpenContainer(ElementType aElementType) |
879 | 0 | { |
880 | 0 | nsresult rv = NS_OK; |
881 | 0 |
|
882 | 0 | switch (aElementType) { |
883 | 0 | case eBody: |
884 | 0 | rv = OpenBody(); |
885 | 0 | break; |
886 | 0 | case eHTML: |
887 | 0 | if (mRoot) { |
888 | 0 | // If we've already hit this code once, then we're done |
889 | 0 | if (!mNotifiedRootInsertion) { |
890 | 0 | NotifyRootInsertion(); |
891 | 0 | } |
892 | 0 | ProcessOfflineManifest(mRoot); |
893 | 0 | } |
894 | 0 | break; |
895 | 0 | } |
896 | 0 |
|
897 | 0 | return rv; |
898 | 0 | } |
899 | | |
900 | | NS_IMETHODIMP |
901 | | HTMLContentSink::CloseContainer(const ElementType aTag) |
902 | 0 | { |
903 | 0 | nsresult rv = NS_OK; |
904 | 0 |
|
905 | 0 | switch (aTag) { |
906 | 0 | case eBody: |
907 | 0 | rv = CloseBody(); |
908 | 0 | break; |
909 | 0 | case eHTML: |
910 | 0 | rv = CloseHTML(); |
911 | 0 | break; |
912 | 0 | } |
913 | 0 | |
914 | 0 | return rv; |
915 | 0 | } |
916 | | |
917 | | NS_IMETHODIMP |
918 | | HTMLContentSink::WillInterrupt() |
919 | 0 | { |
920 | 0 | return WillInterruptImpl(); |
921 | 0 | } |
922 | | |
923 | | NS_IMETHODIMP |
924 | | HTMLContentSink::WillResume() |
925 | 0 | { |
926 | 0 | return WillResumeImpl(); |
927 | 0 | } |
928 | | |
929 | | void |
930 | | HTMLContentSink::CloseHeadContext() |
931 | 0 | { |
932 | 0 | if (mCurrentContext) { |
933 | 0 | if (!mCurrentContext->IsCurrentContainer(eHTMLTag_head)) |
934 | 0 | return; |
935 | 0 | |
936 | 0 | mCurrentContext->FlushTags(); |
937 | 0 | } |
938 | 0 |
|
939 | 0 | if (!mContextStack.IsEmpty()) |
940 | 0 | { |
941 | 0 | uint32_t n = mContextStack.Length() - 1; |
942 | 0 | mCurrentContext = mContextStack.ElementAt(n); |
943 | 0 | mContextStack.RemoveElementAt(n); |
944 | 0 | } |
945 | 0 | } |
946 | | |
947 | | void |
948 | | HTMLContentSink::NotifyInsert(nsIContent* aContent, |
949 | | nsIContent* aChildContent) |
950 | 0 | { |
951 | 0 | if (aContent && aContent->GetUncomposedDoc() != mDocument) { |
952 | 0 | // aContent is not actually in our document anymore.... Just bail out of |
953 | 0 | // here; notifying on our document for this insert would be wrong. |
954 | 0 | return; |
955 | 0 | } |
956 | 0 | |
957 | 0 | mInNotification++; |
958 | 0 |
|
959 | 0 | { |
960 | 0 | // Scope so we call EndUpdate before we decrease mInNotification |
961 | 0 | MOZ_AUTO_DOC_UPDATE(mDocument, !mBeganUpdate); |
962 | 0 | nsNodeUtils::ContentInserted(NODE_FROM(aContent, mDocument), |
963 | 0 | aChildContent); |
964 | 0 | mLastNotificationTime = PR_Now(); |
965 | 0 | } |
966 | 0 |
|
967 | 0 | mInNotification--; |
968 | 0 | } |
969 | | |
970 | | void |
971 | | HTMLContentSink::NotifyRootInsertion() |
972 | 0 | { |
973 | 0 | MOZ_ASSERT(!mNotifiedRootInsertion, "Double-notifying on root?"); |
974 | 0 | NS_ASSERTION(!mLayoutStarted, |
975 | 0 | "How did we start layout without notifying on root?"); |
976 | 0 | // Now make sure to notify that we have now inserted our root. If |
977 | 0 | // there has been no initial reflow yet it'll be a no-op, but if |
978 | 0 | // there has been one we need this to get its frames constructed. |
979 | 0 | // Note that if mNotifiedRootInsertion is true we don't notify here, |
980 | 0 | // since that just means there are multiple <html> tags in the |
981 | 0 | // document; in those cases we just want to put all the attrs on one |
982 | 0 | // tag. |
983 | 0 | mNotifiedRootInsertion = true; |
984 | 0 | NotifyInsert(nullptr, mRoot); |
985 | 0 |
|
986 | 0 | // Now update the notification information in all our |
987 | 0 | // contexts, since we just inserted the root and notified on |
988 | 0 | // our whole tree |
989 | 0 | UpdateChildCounts(); |
990 | 0 | } |
991 | | |
992 | | void |
993 | | HTMLContentSink::UpdateChildCounts() |
994 | 0 | { |
995 | 0 | uint32_t numContexts = mContextStack.Length(); |
996 | 0 | for (uint32_t i = 0; i < numContexts; i++) { |
997 | 0 | SinkContext* sc = mContextStack.ElementAt(i); |
998 | 0 |
|
999 | 0 | sc->UpdateChildCounts(); |
1000 | 0 | } |
1001 | 0 |
|
1002 | 0 | mCurrentContext->UpdateChildCounts(); |
1003 | 0 | } |
1004 | | |
1005 | | void |
1006 | | HTMLContentSink::FlushPendingNotifications(FlushType aType) |
1007 | 0 | { |
1008 | 0 | // Only flush tags if we're not doing the notification ourselves |
1009 | 0 | // (since we aren't reentrant) |
1010 | 0 | if (!mInNotification) { |
1011 | 0 | // Only flush if we're still a document observer (so that our child counts |
1012 | 0 | // should be correct). |
1013 | 0 | if (mIsDocumentObserver) { |
1014 | 0 | if (aType >= FlushType::ContentAndNotify) { |
1015 | 0 | FlushTags(); |
1016 | 0 | } |
1017 | 0 | } |
1018 | 0 | if (aType >= FlushType::EnsurePresShellInitAndFrames) { |
1019 | 0 | // Make sure that layout has started so that the reflow flush |
1020 | 0 | // will actually happen. |
1021 | 0 | StartLayout(true); |
1022 | 0 | } |
1023 | 0 | } |
1024 | 0 | } |
1025 | | |
1026 | | nsresult |
1027 | | HTMLContentSink::FlushTags() |
1028 | 0 | { |
1029 | 0 | if (!mNotifiedRootInsertion) { |
1030 | 0 | NotifyRootInsertion(); |
1031 | 0 | return NS_OK; |
1032 | 0 | } |
1033 | 0 | |
1034 | 0 | return mCurrentContext ? mCurrentContext->FlushTags() : NS_OK; |
1035 | 0 | } |
1036 | | |
1037 | | void |
1038 | | HTMLContentSink::SetDocumentCharset(NotNull<const Encoding*> aEncoding) |
1039 | 0 | { |
1040 | 0 | MOZ_ASSERT_UNREACHABLE("<meta charset> case doesn't occur with about:blank"); |
1041 | 0 | } |
1042 | | |
1043 | | nsISupports * |
1044 | | HTMLContentSink::GetTarget() |
1045 | 0 | { |
1046 | 0 | return mDocument; |
1047 | 0 | } |
1048 | | |
1049 | | bool |
1050 | | HTMLContentSink::IsScriptExecuting() |
1051 | 0 | { |
1052 | 0 | return IsScriptExecutingImpl(); |
1053 | 0 | } |
1054 | | |
1055 | | void |
1056 | | HTMLContentSink::ContinueInterruptedParsingIfEnabled() |
1057 | 0 | { |
1058 | 0 | if (mParser && mParser->IsParserEnabled()) { |
1059 | 0 | static_cast<nsIParser*>(mParser.get())->ContinueInterruptedParsing(); |
1060 | 0 | } |
1061 | 0 | } |
1062 | | |
1063 | | void |
1064 | | HTMLContentSink::ContinueInterruptedParsingAsync() |
1065 | 0 | { |
1066 | 0 | nsCOMPtr<nsIRunnable> ev = |
1067 | 0 | NewRunnableMethod("HTMLContentSink::ContinueInterruptedParsingIfEnabled", |
1068 | 0 | this, |
1069 | 0 | &HTMLContentSink::ContinueInterruptedParsingIfEnabled); |
1070 | 0 |
|
1071 | 0 | nsCOMPtr<nsIDocument> doc = do_QueryInterface(mHTMLDocument); |
1072 | 0 | doc->Dispatch(mozilla::TaskCategory::Other, ev.forget()); |
1073 | 0 | } |