Changeset 118291 in webkit
- Timestamp:
- May 23, 2012 6:07:22 PM (12 years ago)
- Location:
- trunk
- Files:
-
- 3 added
- 20 edited
Legend:
- Unmodified
- Added
- Removed
-
trunk/LayoutTests/ChangeLog
r118279 r118291 1 2012-05-23 Eric Seidel <eric@webkit.org> 2 3 Add seamless layout code (and pass most of the remaining seamless tests) 4 https://bugs.webkit.org/show_bug.cgi?id=86608 5 6 Reviewed by Ojan Vafai. 7 8 Add a test for percent height in child documents, as well as 9 disabling the expand-to-fill-viewport quirk of html/body elements 10 in quirks mode. 11 Also update all the expectations now that we pass almost all the tests. 12 13 * fast/frames/seamless/resources/percent-square.html: Added. 14 * fast/frames/seamless/resources/two-inline-blocks.html: 15 * fast/frames/seamless/seamless-basic-expected.txt: 16 * fast/frames/seamless/seamless-float-expected.txt: 17 * fast/frames/seamless/seamless-inherited-origin-expected.txt: 18 * fast/frames/seamless/seamless-inherited-origin.html: 19 * fast/frames/seamless/seamless-inline-expected.txt: 20 * fast/frames/seamless/seamless-nested-expected.txt: 21 * fast/frames/seamless/seamless-percent-height-expected.txt: Added. 22 * fast/frames/seamless/seamless-percent-height.html: Added. 23 * fast/frames/seamless/seamless-quirks-expected.txt: 24 * fast/frames/seamless/seamless-sandbox-flag-expected.txt: 25 * fast/frames/seamless/seamless-sandbox-srcdoc-expected.txt: 26 1 27 2012-05-23 Tony Chang <tony@chromium.org> 2 28 -
trunk/LayoutTests/fast/frames/seamless/resources/two-inline-blocks.html
r115742 r118291 2 2 <html><head><style> 3 3 body { margin: 0px; line-height: 0px; } 4 /* FIXME: This vertical-align should not be needed, but without it the contentHeight() 5 of the document is reported as 4px too large, likely due to implied line decent? */ 6 div { vertical-align: top; } 4 7 </style></head> 5 8 <body><div style="background-color: orange; display: inline-block; width: 100px; height: 50px;"></div><div style="background-color: blue; display: inline-block; width: 50px; height: 50px;"></div></body></html> -
trunk/LayoutTests/fast/frames/seamless/seamless-basic-expected.txt
r115773 r118291 2 2 FAIL iframe.seamless should be true (of type boolean). Was undefined (of type undefined). 3 3 PASS window.getComputedStyle(iframe).display is "block" 4 FAIL window.getComputedStyle(iframe).width should be 200px. Was 300px. 5 FAIL window.getComputedStyle(iframe).height should be 100px. Was 150px. 6 FAIL window.getComputedStyle(iframe).height should be 200px. Was 150px. 7 FAIL window.getComputedStyle(iframe).width should be 100px. Was 300px. 4 PASS window.getComputedStyle(iframe).width is "200px" 5 PASS window.getComputedStyle(iframe).height is "100px" 6 PASS window.getComputedStyle(iframe).height is "200px" 7 PASS window.getComputedStyle(iframe).width is "100px" 8 8 -
trunk/LayoutTests/fast/frames/seamless/seamless-float-expected.txt
r115742 r118291 1 1 Test that floated seamless iframes 'shrink-wrap' their contents, as floated divs would. 2 FAIL window.getComputedStyle(iframe1).width should be 150px. Was 300px. 3 FAIL window.getComputedStyle(iframe1).height should be 50px. Was 1 50px.4 FAIL window.getComputedStyle(iframe2).width should be 100px. Was 300px. 5 FAIL window.getComputedStyle(iframe2).height should be 100px. Was 150px. 2 PASS window.getComputedStyle(iframe1).width is "150px" 3 FAIL window.getComputedStyle(iframe1).height should be 50px. Was 100px. 4 PASS window.getComputedStyle(iframe2).width is "100px" 5 PASS window.getComputedStyle(iframe2).height is "100px" 6 6 7 7 -
trunk/LayoutTests/fast/frames/seamless/seamless-inherited-origin-expected.txt
r115773 r118291 1 1 Test that frames with inherited security origins can still be seamless. 2 2 FAIL iframe.seamless should be true (of type boolean). Was undefined (of type undefined). 3 FAIL window.getComputedStyle(iframe).width should be 200px. Was 300px. 4 FAIL window.getComputedStyle(iframe).height should be 0px. Was 150px. 5 FAIL window.getComputedStyle(iframe).width should be 200px. Was 300px. 6 FAIL window.getComputedStyle(iframe).height should be 100px. Was 150px. 3 PASS window.getComputedStyle(iframe).width is "200px" 4 PASS window.getComputedStyle(iframe).height is "8px" 5 PASS window.getComputedStyle(iframe).width is "200px" 6 PASS window.getComputedStyle(iframe).height is "100px" 7 7 -
trunk/LayoutTests/fast/frames/seamless/seamless-inherited-origin.html
r115773 r118291 10 10 shouldBeTrue("iframe.seamless"); 11 11 12 // Initially about:blank should have 0 height.13 12 shouldBeEqualToString("window.getComputedStyle(iframe).width", "200px"); 14 shouldBeEqualToString("window.getComputedStyle(iframe).height", "0px"); 13 // Initially about:blank has no content, thus no height, except the body 14 // element defaults to margin: 8px (which gets collapsed to 8px when rendered). 15 shouldBeEqualToString("window.getComputedStyle(iframe).height", "8px"); 15 16 16 17 // Replace the empty document with a 100x100px square to test if it displays seamlessly. -
trunk/LayoutTests/fast/frames/seamless/seamless-inline-expected.txt
r115773 r118291 1 1 Test that inline seamless iframes 'shrink-wrap' their contents like inline-blocks do. 2 FAIL window.getComputedStyle(iframe1).display should be inline-block. Was inline. 3 FAIL window.getComputedStyle(iframe2).display should be inline-block. Was inline. 4 FAIL window.getComputedStyle(iframe1).width should be 150px. Was 300px. 5 FAIL window.getComputedStyle(iframe1).height should be 50px. Was 1 50px.6 FAIL window.getComputedStyle(parent1).height should be 50px. Was 200px.7 FAIL window.getComputedStyle(iframe2).width should be 100px. Was 300px. 8 FAIL window.getComputedStyle(iframe2).height should be 100px. Was 150px. 9 FAIL window.getComputedStyle(parent2).height should be 150px. Was 200px. 2 PASS window.getComputedStyle(iframe1).display is "inline-block" 3 PASS window.getComputedStyle(iframe2).display is "inline-block" 4 PASS window.getComputedStyle(iframe1).width is "150px" 5 FAIL window.getComputedStyle(iframe1).height should be 50px. Was 100px. 6 FAIL window.getComputedStyle(parent1).height should be 50px. Was 100px. 7 PASS window.getComputedStyle(iframe2).width is "100px" 8 PASS window.getComputedStyle(iframe2).height is "100px" 9 PASS window.getComputedStyle(parent2).height is "150px" 10 10 11 11 -
trunk/LayoutTests/fast/frames/seamless/seamless-nested-expected.txt
r115742 r118291 2 2 FAIL iframe.seamless should be true (of type boolean). Was undefined (of type undefined). 3 3 FAIL nestedFrame.seamless should be true (of type boolean). Was undefined (of type undefined). 4 FAIL window.getComputedStyle(iframe).width should be 200px. Was 300px. 5 FAIL window.getComputedStyle(iframe).height should be 100px. Was 150px. 4 PASS window.getComputedStyle(iframe).width is "200px" 5 PASS window.getComputedStyle(iframe).height is "100px" 6 6 -
trunk/LayoutTests/fast/frames/seamless/seamless-quirks-expected.txt
r115742 r118291 2 2 FAIL iframe.seamless should be true (of type boolean). Was undefined (of type undefined). 3 3 PASS iframe.contentDocument.compatMode is "BackCompat" 4 FAIL window.getComputedStyle(iframe).width should be 200px. Was 300px. 5 FAIL window.getComputedStyle(iframe).height should be 100px. Was 150px. 4 PASS window.getComputedStyle(iframe).width is "200px" 5 PASS window.getComputedStyle(iframe).height is "100px" 6 6 -
trunk/LayoutTests/fast/frames/seamless/seamless-sandbox-flag-expected.txt
r115773 r118291 5 5 PASS window.getComputedStyle(iframe).borderWidth is "0px" 6 6 PASS window.getComputedStyle(iframe).borderStyle is "none" 7 FAIL window.getComputedStyle(iframe).width should be 200px. Was 300px. 7 PASS window.getComputedStyle(iframe).width is "200px" 8 8 PASS window.getComputedStyle(iframe).height is "150px" 9 9 -
trunk/LayoutTests/fast/frames/seamless/seamless-sandbox-srcdoc-expected.txt
r115742 r118291 1 1 Test that iframes with srcdoc contents can still be seamless. 2 2 FAIL iframe.seamless should be true (of type boolean). Was undefined (of type undefined). 3 FAIL window.getComputedStyle(iframe).width should be 200px. Was 300px. 4 FAIL window.getComputedStyle(iframe).height should be 100px. Was 150px. 3 PASS window.getComputedStyle(iframe).width is "200px" 4 PASS window.getComputedStyle(iframe).height is "100px" 5 5 -
trunk/LayoutTests/http/tests/security/seamless/seamless-cross-origin-expected.txt
r115742 r118291 1 1 FAIL iframe1.seamless should be true (of type boolean). Was undefined (of type undefined). 2 2 FAIL iframe2.seamless should be true (of type boolean). Was undefined (of type undefined). 3 FAIL window.getComputedStyle(iframe1).width should be 200px. Was 300px. 4 FAIL window.getComputedStyle(iframe1).height should be 100px. Was 150px. 3 PASS window.getComputedStyle(iframe1).width is "200px" 4 PASS window.getComputedStyle(iframe1).height is "100px" 5 5 PASS window.getComputedStyle(iframe2).width is "300px" 6 6 PASS window.getComputedStyle(iframe2).height is "150px" -
trunk/LayoutTests/http/tests/security/seamless/seamless-sandbox-srcdoc-expected.txt
r115742 r118291 1 1 FAIL iframe.seamless should be true (of type boolean). Was undefined (of type undefined). 2 FAIL window.getComputedStyle(iframe).width should be 200px. Was 300px. 3 FAIL window.getComputedStyle(iframe).height should be 100px. Was 150px. 2 PASS window.getComputedStyle(iframe).width is "200px" 3 PASS window.getComputedStyle(iframe).height is "100px" 4 4 -
trunk/Source/WebCore/ChangeLog
r118289 r118291 1 2012-05-23 Eric Seidel <eric@webkit.org> 2 3 Add seamless layout code (and pass most of the remaining seamless tests) 4 https://bugs.webkit.org/show_bug.cgi?id=86608 5 6 Reviewed by Ojan Vafai. 7 8 This patch contains almost all the layout changes needed for seamless iframes. 9 I removed the scroll-bar avoiding code at the last moment, as it didn't 10 work for platforms other than mac-lion. I'll add that, as well as the 11 HTMLIFrameElement.seamless idl attribute in a follow-up patch. 12 13 Seamless iframes piggy-back a bit on the existing frame-flattening 14 logic, however seamless is different from frame-flattening in a few ways: 15 - Frame flattening can only ever make an iframe larger (seamless just behaves like a normal div). 16 - Frame flattening disables scrollbars (seamless frames behave like normal overflow: auto divs). 17 - Seamless only has to work with iframes (flattening works with frame/frameset as well). 18 - Seamless support shrink-wrap size negotiation when the iframe is inline. 19 20 Test: fast/frames/seamless/seamless-percent-height.html 21 22 * css/StyleResolver.cpp: 23 (WebCore::StyleResolver::adjustRenderStyle): map inline -> inline-block for seamless iframes. 24 * dom/Document.cpp: 25 (WebCore::Document::scheduleStyleRecalc): 26 - Seamless iframes don't manage their own style recalc. 27 (WebCore::Document::recalcStyle): 28 - We should make sure our parent is resolved before we are, but currently that causes some tests to crash 29 I don't have a test to demonstrate this need yet, as presumably it's fulfilled through other codepaths atm. 30 * page/FrameView.cpp: 31 (WebCore::FrameView::scheduleRelayout): Do the cheaper check first. 32 (WebCore::FrameView::isInChildFrameWithFrameFlattening): Make frameview layout abort child layouts like how frame flattening does. 33 * rendering/RenderBox.h: 34 (WebCore::RenderBox::stretchesToViewport): Disable the stretch-to-viewport quirk for seamless iframes (it makes no sense and breaks the layout code). 35 * rendering/RenderIFrame.cpp: 36 (WebCore::RenderIFrame::computeLogicalHeight): 37 - This function is needed for the child document to participate in the normal block shrink-wrap algorithm. 38 Thankfully all the shrink-wrap logic is in RenderBox instead of RenderBlock. In the future we may make 39 RenderIframe a RenderBlock for the seamless case. We may just split RenderIframe into two renderers. 40 (WebCore::RenderIFrame::computeLogicalWidth): 41 (WebCore::RenderIFrame::shouldComputeSizeAsReplaced): 42 - seamless iframes behave like blocks, not inline replaced elements. 43 (WebCore): 44 (WebCore::RenderIFrame::isInlineBlockOrInlineTable): 45 - Behave like an inline-block when marked inline. 46 (WebCore::RenderIFrame::minPreferredLogicalWidth): 47 - When asked for our pref widths, return those of our child document. 48 (WebCore::RenderIFrame::maxPreferredLogicalWidth): 49 (WebCore::RenderIFrame::isSeamless): helper function 50 (WebCore::RenderIFrame::contentRootRenderer): helper function 51 (WebCore::RenderIFrame::flattenFrame): seamless iframes never use the frame-flattening feature. 52 (WebCore::RenderIFrame::layoutSeamlessly): The guts of seamless layout. 53 (WebCore::RenderIFrame::layout): 54 * rendering/RenderIFrame.h: 55 (WebCore): 56 (RenderIFrame): 57 1 58 2012-05-23 Rafael Brandao <rafael.lobo@openbossa.org> 2 59 -
trunk/Source/WebCore/css/StyleResolver.cpp
r118263 r118291 2128 2128 || style->hasFilter())) 2129 2129 style->setTransformStyle3D(TransformStyle3DFlat); 2130 2131 // Seamless iframes behave like blocks. Map their display to inline-block when marked inline. 2132 if (e && e->hasTagName(iframeTag) && style->display() == INLINE && static_cast<HTMLIFrameElement*>(e)->shouldDisplaySeamlessly()) 2133 style->setDisplay(INLINE_BLOCK); 2130 2134 2131 2135 #if ENABLE(SVG) -
trunk/Source/WebCore/dom/Document.cpp
r118194 r118291 1661 1661 void Document::scheduleStyleRecalc() 1662 1662 { 1663 // FIXME: In the seamless case, we should likely schedule a style recalc 1664 // on our parent and instead return early here. 1663 if (shouldDisplaySeamlesslyWithParent()) { 1664 // When we're seamless, our parent document manages our style recalcs. 1665 ownerElement()->setNeedsStyleRecalc(); 1666 ownerElement()->document()->scheduleStyleRecalc(); 1667 return; 1668 } 1665 1669 1666 1670 if (m_styleRecalcTimer.isActive() || inPageCache()) … … 1718 1722 return; // Guard against re-entrancy. -dwh 1719 1723 1720 // FIXME: In the seamless case, we may wish to exit early in the child after recalcing our parent chain. 1721 // I have not yet found a test which requires such. 1724 // FIXME: We should update style on our ancestor chain before proceeding (especially for seamless), 1725 // however doing so currently causes several tests to crash, as Frame::setDocument calls Document::attach 1726 // before setting the DOMWindow on the Frame, or the SecurityOrigin on the document. The attach, in turn 1727 // resolves style (here) and then when we resolve style on the parent chain, we may end up 1728 // re-attaching our containing iframe, which when asked HTMLFrameElementBase::isURLAllowed 1729 // hits a null-dereference due to security code always assuming the document has a SecurityOrigin. 1722 1730 1723 1731 if (m_hasDirtyStyleResolver) -
trunk/Source/WebCore/page/FrameView.cpp
r117697 r118291 2042 2042 // When frame flattening is enabled, the contents of the frame could affect the layout of the parent frames. 2043 2043 // Also invalidate parent frame starting from the owner element of this frame. 2044 if ( isInChildFrameWithFrameFlattening() && m_frame->ownerRenderer())2044 if (m_frame->ownerRenderer() && isInChildFrameWithFrameFlattening()) 2045 2045 m_frame->ownerRenderer()->setNeedsLayout(true, MarkContainingBlockChain); 2046 2046 … … 2919 2919 bool FrameView::isInChildFrameWithFrameFlattening() 2920 2920 { 2921 if (!parent() || !m_frame->ownerElement() || !m_frame->settings() || !m_frame->settings()->frameFlatteningEnabled())2921 if (!parent() || !m_frame->ownerElement()) 2922 2922 return false; 2923 2923 … … 2926 2926 if (m_frame->ownerElement()->hasTagName(iframeTag)) { 2927 2927 RenderIFrame* iframeRenderer = toRenderIFrame(m_frame->ownerElement()->renderPart()); 2928 2929 if (iframeRenderer->flattenFrame()) 2928 if (iframeRenderer->flattenFrame() || iframeRenderer->isSeamless()) 2930 2929 return true; 2931 2932 } else if (m_frame->ownerElement()->hasTagName(frameTag)) 2930 } 2931 2932 if (!m_frame->settings() || !m_frame->settings()->frameFlatteningEnabled()) 2933 return false; 2934 2935 if (m_frame->ownerElement()->hasTagName(frameTag)) 2933 2936 return true; 2934 2937 -
trunk/Source/WebCore/rendering/RenderBox.h
r118076 r118291 327 327 bool stretchesToViewport() const 328 328 { 329 return document()->inQuirksMode() && style()->logicalHeight().isAuto() && !isFloatingOrPositioned() && (isRoot() || isBody()) ;329 return document()->inQuirksMode() && style()->logicalHeight().isAuto() && !isFloatingOrPositioned() && (isRoot() || isBody()) && !document()->shouldDisplaySeamlesslyWithParent(); 330 330 } 331 331 -
trunk/Source/WebCore/rendering/RenderIFrame.cpp
r111515 r118291 64 64 void RenderIFrame::computeLogicalWidth() 65 65 { 66 // When we're seamless, we behave like a block. Thankfully RenderBox has all the right logic for this. 67 if (isSeamless()) 68 return RenderBox::computeLogicalWidth(); 69 66 70 RenderPart::computeLogicalWidth(); 67 71 if (!flattenFrame()) … … 80 84 } 81 85 82 bool RenderIFrame::flattenFrame() 86 bool RenderIFrame::shouldComputeSizeAsReplaced() const 87 { 88 // When we're seamless, we use normal block/box sizing code except when inline. 89 return !isSeamless(); 90 } 91 92 bool RenderIFrame::isInlineBlockOrInlineTable() const 93 { 94 return isSeamless() && isInline(); 95 } 96 97 LayoutUnit RenderIFrame::minPreferredLogicalWidth() const 98 { 99 if (!isSeamless()) 100 return RenderFrameBase::minPreferredLogicalWidth(); 101 102 RenderView* childRoot = contentRootRenderer(); 103 if (!childRoot) 104 return 0; 105 106 return childRoot->minPreferredLogicalWidth(); 107 } 108 109 LayoutUnit RenderIFrame::maxPreferredLogicalWidth() const 110 { 111 if (!isSeamless()) 112 return RenderFrameBase::maxPreferredLogicalWidth(); 113 114 RenderView* childRoot = contentRootRenderer(); 115 if (!childRoot) 116 return 0; 117 118 return childRoot->maxPreferredLogicalWidth(); 119 } 120 121 bool RenderIFrame::isSeamless() const 122 { 123 return node() && node()->hasTagName(iframeTag) && static_cast<HTMLIFrameElement*>(node())->shouldDisplaySeamlessly(); 124 } 125 126 RenderView* RenderIFrame::contentRootRenderer() const 127 { 128 // FIXME: Is this always a valid cast? What about plugins? 129 ASSERT(!widget() || widget()->isFrameView()); 130 FrameView* childFrameView = static_cast<FrameView*>(widget()); 131 return childFrameView ? static_cast<RenderView*>(childFrameView->frame()->contentRenderer()) : 0; 132 } 133 134 bool RenderIFrame::flattenFrame() const 83 135 { 84 136 if (!node() || !node()->hasTagName(iframeTag)) … … 87 139 HTMLIFrameElement* element = static_cast<HTMLIFrameElement*>(node()); 88 140 Frame* frame = element->document()->frame(); 141 142 if (isSeamless()) 143 return false; // Seamless iframes are already "flat", don't try to flatten them. 89 144 90 145 bool enabled = frame && frame->settings() && frame->settings()->frameFlatteningEnabled(); … … 106 161 } 107 162 163 void RenderIFrame::layoutSeamlessly() 164 { 165 computeLogicalWidth(); 166 // FIXME: Containers set their height to 0 before laying out their kids (as we're doing here) 167 // however, this causes FrameView::layout() to add vertical scrollbars, incorrectly inflating 168 // the resulting contentHeight(). We'll need to make FrameView::layout() smarter. 169 setLogicalHeight(0); 170 updateWidgetPosition(); // Tell the Widget about our new width/height (it will also layout the child document). 171 172 // Laying out our kids is normally responsible for adjusting our height, so we set it here. 173 // Replaced elements do not respect padding, so we just add border to the child's height. 174 // FIXME: It's possible that seamless iframes (since they act like divs) *should* respect padding. 175 FrameView* childFrameView = static_cast<FrameView*>(widget()); 176 if (childFrameView) // Widget should never be null during layout(), but just in case. 177 setLogicalHeight(childFrameView->contentsHeight() + borderTop() + borderBottom()); 178 computeLogicalHeight(); 179 180 updateWidgetPosition(); // Notify the Widget of our final height. 181 182 // Assert that the child document did a complete layout. 183 RenderView* childRoot = childFrameView ? static_cast<RenderView*>(childFrameView->frame()->contentRenderer()) : 0; 184 ASSERT(!childFrameView || !childFrameView->layoutPending()); 185 ASSERT_UNUSED(childRoot, !childRoot || !childRoot->needsLayout()); 186 } 187 108 188 void RenderIFrame::layout() 109 189 { 110 190 ASSERT(needsLayout()); 111 191 112 RenderPart::computeLogicalWidth();113 RenderPart::computeLogicalHeight();114 115 192 if (flattenFrame()) { 193 RenderPart::computeLogicalWidth(); 194 RenderPart::computeLogicalHeight(); 116 195 layoutWithFlattening(style()->width().isFixed(), style()->height().isFixed()); 196 // FIXME: Is early return really OK here? What about transform/overflow code below? 117 197 return; 118 } 119 120 RenderPart::layout(); 198 } else if (isSeamless()) { 199 layoutSeamlessly(); 200 // Do not return so as to share the layer and overflow updates below. 201 } else { 202 computeLogicalWidth(); 203 // No kids to layout as a replaced element. 204 computeLogicalHeight(); 205 } 121 206 122 207 m_overflow.clear(); -
trunk/Source/WebCore/rendering/RenderIFrame.h
r110738 r118291 31 31 namespace WebCore { 32 32 33 class RenderView; 34 33 35 class RenderIFrame : public RenderFrameBase { 34 36 public: 35 37 explicit RenderIFrame(Element*); 36 38 37 bool flattenFrame(); 39 bool flattenFrame() const; 40 bool isSeamless() const; 38 41 39 42 private: 40 virtual void computeLogicalHeight() ;41 virtual void computeLogicalWidth() ;43 virtual void computeLogicalHeight() OVERRIDE; 44 virtual void computeLogicalWidth() OVERRIDE; 42 45 43 virtual void layout(); 46 virtual LayoutUnit minPreferredLogicalWidth() const OVERRIDE; 47 virtual LayoutUnit maxPreferredLogicalWidth() const OVERRIDE; 44 48 45 virtual bool isRenderIFrame() const { return true; } 49 virtual bool shouldComputeSizeAsReplaced() const OVERRIDE; 50 virtual bool isInlineBlockOrInlineTable() const OVERRIDE; 46 51 47 virtual const char* renderName() const { return "RenderPartObject"; } // Lying for now to avoid breaking tests52 virtual void layout() OVERRIDE; 48 53 54 virtual bool isRenderIFrame() const OVERRIDE { return true; } 55 56 virtual const char* renderName() const OVERRIDE { return "RenderPartObject"; } // Lying for now to avoid breaking tests 57 58 void layoutSeamlessly(); 59 60 RenderView* contentRootRenderer() const; 49 61 }; 50 62
Note: See TracChangeset
for help on using the changeset viewer.