1/*
2 * Copyright (C) 2016-2017 Apple Inc. All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
6 * are met:
7 * 1. Redistributions of source code must retain the above copyright
8 * notice, this list of conditions and the following disclaimer.
9 * 2. Redistributions in binary form must reproduce the above copyright
10 * notice, this list of conditions and the following disclaimer in the
11 * documentation and/or other materials provided with the distribution.
12 *
13 * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
14 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
15 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
17 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
23 * THE POSSIBILITY OF SUCH DAMAGE.
24 */
25
26#include "config.h"
27#include "RenderTreeUpdater.h"
28
29#include "AXObjectCache.h"
30#include "CSSAnimationController.h"
31#include "ComposedTreeAncestorIterator.h"
32#include "ComposedTreeIterator.h"
33#include "Document.h"
34#include "DocumentTimeline.h"
35#include "Element.h"
36#include "FullscreenManager.h"
37#include "HTMLParserIdioms.h"
38#include "HTMLSlotElement.h"
39#include "InspectorInstrumentation.h"
40#include "NodeRenderStyle.h"
41#include "PseudoElement.h"
42#include "RenderDescendantIterator.h"
43#include "RenderFullScreen.h"
44#include "RenderInline.h"
45#include "RenderMultiColumnFlow.h"
46#include "RenderMultiColumnSet.h"
47#include "RenderTreeUpdaterGeneratedContent.h"
48#include "RuntimeEnabledFeatures.h"
49#include "StyleResolver.h"
50#include "StyleTreeResolver.h"
51#include <wtf/SystemTracing.h>
52
53#if PLATFORM(IOS_FAMILY)
54#include "ContentChangeObserver.h"
55#endif
56
57namespace WebCore {
58
59RenderTreeUpdater::Parent::Parent(ContainerNode& root)
60 : element(is<Document>(root) ? nullptr : downcast<Element>(&root))
61 , renderTreePosition(RenderTreePosition(*root.renderer()))
62{
63}
64
65RenderTreeUpdater::Parent::Parent(Element& element, const Style::ElementUpdates* updates)
66 : element(&element)
67 , updates(updates)
68 , renderTreePosition(element.renderer() ? makeOptional(RenderTreePosition(*element.renderer())) : WTF::nullopt)
69{
70}
71
72RenderTreeUpdater::RenderTreeUpdater(Document& document)
73 : m_document(document)
74 , m_generatedContent(std::make_unique<GeneratedContent>(*this))
75 , m_builder(renderView())
76{
77}
78
79RenderTreeUpdater::~RenderTreeUpdater() = default;
80
81static ContainerNode* findRenderingRoot(ContainerNode& node)
82{
83 if (node.renderer())
84 return &node;
85 for (auto& ancestor : composedTreeAncestors(node)) {
86 if (ancestor.renderer())
87 return &ancestor;
88 if (!ancestor.hasDisplayContents())
89 return nullptr;
90 }
91 return &node.document();
92}
93
94static ListHashSet<ContainerNode*> findRenderingRoots(const Style::Update& update)
95{
96 ListHashSet<ContainerNode*> renderingRoots;
97 for (auto* root : update.roots()) {
98 auto* renderingRoot = findRenderingRoot(*root);
99 if (!renderingRoot)
100 continue;
101 renderingRoots.add(renderingRoot);
102 }
103 return renderingRoots;
104}
105
106void RenderTreeUpdater::commit(std::unique_ptr<const Style::Update> styleUpdate)
107{
108 ASSERT(&m_document == &styleUpdate->document());
109
110 if (!m_document.shouldCreateRenderers() || !m_document.renderView())
111 return;
112
113 TraceScope scope(RenderTreeBuildStart, RenderTreeBuildEnd);
114
115 Style::PostResolutionCallbackDisabler callbackDisabler(m_document);
116
117 m_styleUpdate = WTFMove(styleUpdate);
118
119 for (auto* root : findRenderingRoots(*m_styleUpdate))
120 updateRenderTree(*root);
121
122 generatedContent().updateRemainingQuotes();
123
124 m_builder.updateAfterDescendants(renderView());
125
126 m_styleUpdate = nullptr;
127}
128
129static bool shouldCreateRenderer(const Element& element, const RenderElement& parentRenderer)
130{
131 if (!parentRenderer.canHaveChildren() && !(element.isPseudoElement() && parentRenderer.canHaveGeneratedChildren()))
132 return false;
133 if (parentRenderer.element() && !parentRenderer.element()->childShouldCreateRenderer(element))
134 return false;
135 return true;
136}
137
138void RenderTreeUpdater::updateRenderTree(ContainerNode& root)
139{
140#if PLATFORM(IOS_FAMILY)
141 ContentChangeObserver::RenderTreeUpdateScope observingScope(m_document);
142#endif
143
144 ASSERT(root.renderer());
145 ASSERT(m_parentStack.isEmpty());
146
147 m_parentStack.append(Parent(root));
148
149 auto descendants = composedTreeDescendants(root);
150 auto it = descendants.begin();
151 auto end = descendants.end();
152
153 // FIXME: https://bugs.webkit.org/show_bug.cgi?id=156172
154 it.dropAssertions();
155
156 while (it != end) {
157 popParentsToDepth(it.depth());
158
159 auto& node = *it;
160
161 if (auto* renderer = node.renderer())
162 renderTreePosition().invalidateNextSibling(*renderer);
163 else if (is<Element>(node) && downcast<Element>(node).hasDisplayContents())
164 renderTreePosition().invalidateNextSibling();
165
166 if (is<Text>(node)) {
167 auto& text = downcast<Text>(node);
168 auto* textUpdate = m_styleUpdate->textUpdate(text);
169 bool didCreateParent = parent().updates && parent().updates->update.change == Style::Detach;
170 bool mayNeedUpdateWhitespaceOnlyRenderer = renderingParent().didCreateOrDestroyChildRenderer && text.data().isAllSpecialCharacters<isHTMLSpace>();
171 if (didCreateParent || textUpdate || mayNeedUpdateWhitespaceOnlyRenderer)
172 updateTextRenderer(text, textUpdate);
173
174 storePreviousRenderer(text);
175 it.traverseNextSkippingChildren();
176 continue;
177 }
178
179 auto& element = downcast<Element>(node);
180
181 auto* elementUpdates = m_styleUpdate->elementUpdates(element);
182
183 // We hop through display: contents elements in findRenderingRoot, so
184 // there may be other updates down the tree.
185 if (!elementUpdates && !element.hasDisplayContents()) {
186 storePreviousRenderer(element);
187 it.traverseNextSkippingChildren();
188 continue;
189 }
190
191 if (elementUpdates)
192 updateElementRenderer(element, elementUpdates->update);
193
194 storePreviousRenderer(element);
195
196 bool mayHaveRenderedDescendants = element.renderer() || (element.hasDisplayContents() && shouldCreateRenderer(element, renderTreePosition().parent()));
197 if (!mayHaveRenderedDescendants) {
198 it.traverseNextSkippingChildren();
199 continue;
200 }
201
202 pushParent(element, elementUpdates);
203
204 it.traverseNext();
205 }
206
207 popParentsToDepth(0);
208}
209
210auto RenderTreeUpdater::renderingParent() -> Parent&
211{
212 for (unsigned i = m_parentStack.size(); i--;) {
213 if (m_parentStack[i].renderTreePosition)
214 return m_parentStack[i];
215 }
216 ASSERT_NOT_REACHED();
217 return m_parentStack.last();
218}
219
220RenderTreePosition& RenderTreeUpdater::renderTreePosition()
221{
222 return *renderingParent().renderTreePosition;
223}
224
225void RenderTreeUpdater::pushParent(Element& element, const Style::ElementUpdates* updates)
226{
227 m_parentStack.append(Parent(element, updates));
228
229 updateBeforeDescendants(element, updates);
230}
231
232void RenderTreeUpdater::popParent()
233{
234 auto& parent = m_parentStack.last();
235 if (parent.element)
236 updateAfterDescendants(*parent.element, parent.updates);
237
238 m_parentStack.removeLast();
239}
240
241void RenderTreeUpdater::popParentsToDepth(unsigned depth)
242{
243 ASSERT(m_parentStack.size() >= depth);
244
245 while (m_parentStack.size() > depth)
246 popParent();
247}
248
249void RenderTreeUpdater::updateBeforeDescendants(Element& element, const Style::ElementUpdates* updates)
250{
251 if (updates)
252 generatedContent().updatePseudoElement(element, updates->beforePseudoElementUpdate, PseudoId::Before);
253}
254
255void RenderTreeUpdater::updateAfterDescendants(Element& element, const Style::ElementUpdates* updates)
256{
257 if (updates)
258 generatedContent().updatePseudoElement(element, updates->afterPseudoElementUpdate, PseudoId::After);
259
260 auto* renderer = element.renderer();
261 if (!renderer)
262 return;
263
264 m_builder.updateAfterDescendants(*renderer);
265
266 if (element.hasCustomStyleResolveCallbacks() && updates && updates->update.change == Style::Detach)
267 element.didAttachRenderers();
268}
269
270static bool pseudoStyleCacheIsInvalid(RenderElement* renderer, RenderStyle* newStyle)
271{
272 const RenderStyle& currentStyle = renderer->style();
273
274 const PseudoStyleCache* pseudoStyleCache = currentStyle.cachedPseudoStyles();
275 if (!pseudoStyleCache)
276 return false;
277
278 for (auto& cache : *pseudoStyleCache) {
279 PseudoId pseudoId = cache->styleType();
280 std::unique_ptr<RenderStyle> newPseudoStyle = renderer->getUncachedPseudoStyle(PseudoStyleRequest(pseudoId), newStyle, newStyle);
281 if (!newPseudoStyle)
282 return true;
283 if (*newPseudoStyle != *cache) {
284 newStyle->addCachedPseudoStyle(WTFMove(newPseudoStyle));
285 return true;
286 }
287 }
288 return false;
289}
290
291void RenderTreeUpdater::updateRendererStyle(RenderElement& renderer, RenderStyle&& newStyle, StyleDifference minimalStyleDifference)
292{
293 auto oldStyle = RenderStyle::clone(renderer.style());
294 renderer.setStyle(WTFMove(newStyle), minimalStyleDifference);
295 m_builder.normalizeTreeAfterStyleChange(renderer, oldStyle);
296}
297
298void RenderTreeUpdater::updateElementRenderer(Element& element, const Style::ElementUpdate& update)
299{
300#if PLATFORM(IOS_FAMILY)
301 ContentChangeObserver::StyleChangeScope observingScope(m_document, element);
302#endif
303
304 bool shouldTearDownRenderers = update.change == Style::Detach && (element.renderer() || element.hasDisplayContents());
305 if (shouldTearDownRenderers) {
306 if (!element.renderer()) {
307 // We may be tearing down a descendant renderer cached in renderTreePosition.
308 renderTreePosition().invalidateNextSibling();
309 }
310
311 // display:none cancels animations.
312 auto teardownType = update.style->display() == DisplayType::None ? TeardownType::RendererUpdateCancelingAnimations : TeardownType::RendererUpdate;
313 tearDownRenderers(element, teardownType, m_builder);
314
315 renderingParent().didCreateOrDestroyChildRenderer = true;
316 }
317
318 bool hasDisplayContents = update.style->display() == DisplayType::Contents;
319 if (hasDisplayContents)
320 element.storeDisplayContentsStyle(RenderStyle::clonePtr(*update.style));
321 else
322 element.resetComputedStyle();
323
324 bool shouldCreateNewRenderer = !element.renderer() && !hasDisplayContents;
325 if (shouldCreateNewRenderer) {
326 if (element.hasCustomStyleResolveCallbacks())
327 element.willAttachRenderers();
328 createRenderer(element, RenderStyle::clone(*update.style));
329
330 renderingParent().didCreateOrDestroyChildRenderer = true;
331 return;
332 }
333
334 if (!element.renderer())
335 return;
336 auto& renderer = *element.renderer();
337
338 if (update.recompositeLayer) {
339 updateRendererStyle(renderer, RenderStyle::clone(*update.style), StyleDifference::RecompositeLayer);
340 return;
341 }
342
343 if (update.change == Style::NoChange) {
344 if (pseudoStyleCacheIsInvalid(&renderer, update.style.get())) {
345 updateRendererStyle(renderer, RenderStyle::clone(*update.style), StyleDifference::Equal);
346 return;
347 }
348 return;
349 }
350
351 updateRendererStyle(renderer, RenderStyle::clone(*update.style), StyleDifference::Equal);
352}
353
354void RenderTreeUpdater::createRenderer(Element& element, RenderStyle&& style)
355{
356 auto computeInsertionPosition = [this, &element] () {
357 renderTreePosition().computeNextSibling(element);
358 return renderTreePosition();
359 };
360
361 if (!shouldCreateRenderer(element, renderTreePosition().parent()))
362 return;
363
364 if (!element.rendererIsNeeded(style))
365 return;
366
367 RenderTreePosition insertionPosition = computeInsertionPosition();
368 auto newRenderer = element.createElementRenderer(WTFMove(style), insertionPosition);
369 if (!newRenderer)
370 return;
371
372 if (!insertionPosition.parent().isChildAllowed(*newRenderer, newRenderer->style()))
373 return;
374
375 element.setRenderer(newRenderer.get());
376
377 newRenderer->initializeStyle();
378
379#if ENABLE(FULLSCREEN_API)
380 if (m_document.fullscreenManager().isFullscreen() && m_document.fullscreenManager().currentFullscreenElement() == &element) {
381 newRenderer = RenderFullScreen::wrapNewRenderer(m_builder, WTFMove(newRenderer), insertionPosition.parent(), m_document);
382 if (!newRenderer)
383 return;
384 }
385#endif
386
387 m_builder.attach(insertionPosition, WTFMove(newRenderer));
388
389 if (AXObjectCache* cache = m_document.axObjectCache())
390 cache->updateCacheAfterNodeIsAttached(&element);
391}
392
393bool RenderTreeUpdater::textRendererIsNeeded(const Text& textNode)
394{
395 auto& renderingParent = this->renderingParent();
396 auto& parentRenderer = renderingParent.renderTreePosition->parent();
397 if (!parentRenderer.canHaveChildren())
398 return false;
399 if (parentRenderer.element() && !parentRenderer.element()->childShouldCreateRenderer(textNode))
400 return false;
401 if (textNode.isEditingText())
402 return true;
403 if (!textNode.length())
404 return false;
405 if (!textNode.data().isAllSpecialCharacters<isHTMLSpace>())
406 return true;
407 if (is<RenderText>(renderingParent.previousChildRenderer))
408 return true;
409 // This text node has nothing but white space. We may still need a renderer in some cases.
410 if (parentRenderer.isTable() || parentRenderer.isTableRow() || parentRenderer.isTableSection() || parentRenderer.isRenderTableCol() || parentRenderer.isFrameSet())
411 return false;
412 if (parentRenderer.isFlexibleBox() && !parentRenderer.isRenderButton())
413 return false;
414 if (parentRenderer.style().preserveNewline()) // pre/pre-wrap/pre-line always make renderers.
415 return true;
416
417 auto* previousRenderer = renderingParent.previousChildRenderer;
418 if (previousRenderer && previousRenderer->isBR()) // <span><br/> <br/></span>
419 return false;
420
421 if (parentRenderer.isRenderInline()) {
422 // <span><div/> <div/></span>
423 if (previousRenderer && !previousRenderer->isInline())
424 return false;
425 } else {
426 if (parentRenderer.isRenderBlock() && !parentRenderer.childrenInline() && (!previousRenderer || !previousRenderer->isInline()))
427 return false;
428
429 RenderObject* first = parentRenderer.firstChild();
430 while (first && first->isFloatingOrOutOfFlowPositioned())
431 first = first->nextSibling();
432 RenderObject* nextRenderer = textNode.renderer() ? textNode.renderer() : renderTreePosition().nextSiblingRenderer(textNode);
433 if (!first || nextRenderer == first) {
434 // Whitespace at the start of a block just goes away. Don't even make a render object for this text.
435 return false;
436 }
437 }
438 return true;
439}
440
441void RenderTreeUpdater::createTextRenderer(Text& textNode, const Style::TextUpdate* textUpdate)
442{
443 ASSERT(!textNode.renderer());
444
445 auto& renderTreePosition = this->renderTreePosition();
446 auto textRenderer = textNode.createTextRenderer(renderTreePosition.parent().style());
447
448 renderTreePosition.computeNextSibling(textNode);
449
450 if (!renderTreePosition.parent().isChildAllowed(*textRenderer, renderTreePosition.parent().style()))
451 return;
452
453 textNode.setRenderer(textRenderer.get());
454
455 if (textUpdate && textUpdate->inheritedDisplayContentsStyle && *textUpdate->inheritedDisplayContentsStyle) {
456 // Wrap text renderer into anonymous inline so we can give it a style.
457 // This is to support "<div style='display:contents;color:green'>text</div>" type cases
458 auto newDisplayContentsAnonymousWrapper = WebCore::createRenderer<RenderInline>(textNode.document(), RenderStyle::clone(**textUpdate->inheritedDisplayContentsStyle));
459 newDisplayContentsAnonymousWrapper->initializeStyle();
460 auto& displayContentsAnonymousWrapper = *newDisplayContentsAnonymousWrapper;
461 m_builder.attach(renderTreePosition, WTFMove(newDisplayContentsAnonymousWrapper));
462
463 textRenderer->setInlineWrapperForDisplayContents(&displayContentsAnonymousWrapper);
464 m_builder.attach(displayContentsAnonymousWrapper, WTFMove(textRenderer));
465 return;
466 }
467
468 m_builder.attach(renderTreePosition, WTFMove(textRenderer));
469}
470
471void RenderTreeUpdater::updateTextRenderer(Text& text, const Style::TextUpdate* textUpdate)
472{
473 auto* existingRenderer = text.renderer();
474 bool needsRenderer = textRendererIsNeeded(text);
475
476 if (existingRenderer && textUpdate && textUpdate->inheritedDisplayContentsStyle) {
477 if (existingRenderer->inlineWrapperForDisplayContents() || *textUpdate->inheritedDisplayContentsStyle) {
478 // FIXME: We could update without teardown.
479 tearDownTextRenderer(text, m_builder);
480 existingRenderer = nullptr;
481 }
482 }
483
484 if (existingRenderer) {
485 if (needsRenderer) {
486 if (textUpdate)
487 existingRenderer->setTextWithOffset(text.data(), textUpdate->offset, textUpdate->length);
488 return;
489 }
490 tearDownTextRenderer(text, m_builder);
491 renderingParent().didCreateOrDestroyChildRenderer = true;
492 return;
493 }
494 if (!needsRenderer)
495 return;
496 createTextRenderer(text, textUpdate);
497 renderingParent().didCreateOrDestroyChildRenderer = true;
498}
499
500void RenderTreeUpdater::storePreviousRenderer(Node& node)
501{
502 auto* renderer = node.renderer();
503 if (!renderer)
504 return;
505 ASSERT(renderingParent().previousChildRenderer != renderer);
506 renderingParent().previousChildRenderer = renderer;
507}
508
509void RenderTreeUpdater::tearDownRenderers(Element& root)
510{
511 auto* view = root.document().renderView();
512 if (!view)
513 return;
514 RenderTreeBuilder builder(*view);
515 tearDownRenderers(root, TeardownType::Full, builder);
516}
517
518void RenderTreeUpdater::tearDownRenderer(Text& text)
519{
520 auto* view = text.document().renderView();
521 if (!view)
522 return;
523 RenderTreeBuilder builder(*view);
524 tearDownTextRenderer(text, builder);
525}
526
527void RenderTreeUpdater::tearDownRenderers(Element& root, TeardownType teardownType, RenderTreeBuilder& builder)
528{
529 WidgetHierarchyUpdatesSuspensionScope suspendWidgetHierarchyUpdates;
530
531 Vector<Element*, 30> teardownStack;
532
533 auto push = [&] (Element& element) {
534 if (element.hasCustomStyleResolveCallbacks())
535 element.willDetachRenderers();
536 teardownStack.append(&element);
537 };
538
539 auto& document = root.document();
540 auto* timeline = document.existingTimeline();
541 auto& animationController = document.frame()->animation();
542
543 auto pop = [&] (unsigned depth) {
544 while (teardownStack.size() > depth) {
545 auto& element = *teardownStack.takeLast();
546
547 if (teardownType == TeardownType::Full || teardownType == TeardownType::RendererUpdateCancelingAnimations) {
548 if (timeline) {
549 if (document.renderTreeBeingDestroyed())
550 timeline->elementWasRemoved(element);
551 else if (teardownType == TeardownType::RendererUpdateCancelingAnimations)
552 timeline->cancelDeclarativeAnimationsForElement(element);
553 }
554 animationController.cancelAnimations(element);
555 }
556
557 if (teardownType == TeardownType::Full)
558 element.clearHoverAndActiveStatusBeforeDetachingRenderer();
559
560 GeneratedContent::removeBeforePseudoElement(element, builder);
561 GeneratedContent::removeAfterPseudoElement(element, builder);
562
563 if (auto* renderer = element.renderer()) {
564#if PLATFORM(IOS_FAMILY)
565 document.contentChangeObserver().willDestroyRenderer(element);
566#endif
567 builder.destroyAndCleanUpAnonymousWrappers(*renderer);
568 element.setRenderer(nullptr);
569 }
570
571 // Make sure we don't leave any renderers behind in nodes outside the composed tree.
572 if (element.shadowRoot())
573 tearDownLeftoverShadowHostChildren(element, builder);
574
575 if (element.hasCustomStyleResolveCallbacks())
576 element.didDetachRenderers();
577 }
578 };
579
580 push(root);
581
582 auto descendants = composedTreeDescendants(root);
583 for (auto it = descendants.begin(), end = descendants.end(); it != end; ++it) {
584 pop(it.depth());
585
586 if (is<Text>(*it)) {
587 tearDownTextRenderer(downcast<Text>(*it), builder);
588 continue;
589 }
590
591 push(downcast<Element>(*it));
592 }
593
594 pop(0);
595
596 tearDownLeftoverPaginationRenderersIfNeeded(root, builder);
597}
598
599void RenderTreeUpdater::tearDownTextRenderer(Text& text, RenderTreeBuilder& builder)
600{
601 auto* renderer = text.renderer();
602 if (!renderer)
603 return;
604 builder.destroyAndCleanUpAnonymousWrappers(*renderer);
605 text.setRenderer(nullptr);
606}
607
608void RenderTreeUpdater::tearDownLeftoverPaginationRenderersIfNeeded(Element& root, RenderTreeBuilder& builder)
609{
610 if (&root != root.document().documentElement())
611 return;
612 for (auto* child = root.document().renderView()->firstChild(); child;) {
613 auto* nextSibling = child->nextSibling();
614 if (is<RenderMultiColumnFlow>(*child) || is<RenderMultiColumnSet>(*child))
615 builder.destroyAndCleanUpAnonymousWrappers(*child);
616 child = nextSibling;
617 }
618}
619
620void RenderTreeUpdater::tearDownLeftoverShadowHostChildren(Element& host, RenderTreeBuilder& builder)
621{
622 for (auto* hostChild = host.firstChild(); hostChild; hostChild = hostChild->nextSibling()) {
623 if (!hostChild->renderer())
624 continue;
625 if (is<Text>(*hostChild)) {
626 tearDownTextRenderer(downcast<Text>(*hostChild), builder);
627 continue;
628 }
629 if (is<Element>(*hostChild))
630 tearDownRenderers(downcast<Element>(*hostChild), TeardownType::Full, builder);
631 }
632}
633
634RenderView& RenderTreeUpdater::renderView()
635{
636 return *m_document.renderView();
637}
638
639}
640