Skip to content

Commit

Permalink
Only do partial layouts when possible.
Browse files Browse the repository at this point in the history
This patch changes offsetWidth, offsetHeight, offsetLeft, and offsetTop to only
layout a portion of the render tree, up to the target renderer. Partial layout
leaves the root needing layout so a full layout must happen before painting.

An example of the benefit of this patch is wikipedia where the following
pattern is common:
1) page load
2) javascript calls offsetWidth, forcing a full layout
3) javascript injects style into the page, invalidating layout
4) a full layout is performed for painting

With this patch, the offsetWidth call in #2 is significantly improved.

Text autosizing currently removes most of the benefit of this patch, this will
be addressed in a followup patch. Similarly, non-overlay scrollbars reduce
the benefit from this patch.

This is currently implemented behind the PartialLayout
runtime enabled feature flag.

BUG=283623

Review URL: https://chromiumcodereview.appspot.com/18601002

git-svn-id: svn://svn.chromium.org/blink/trunk@157252 bbb929c8-8fbe-4397-9dbb-9b2b20218538
  • Loading branch information
[email protected] committed Sep 5, 2013
1 parent 3e3ee45 commit 50887b8
Show file tree
Hide file tree
Showing 52 changed files with 482 additions and 42 deletions.
3 changes: 3 additions & 0 deletions LayoutTests/NeverFixTests
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@ fast/harness/sample-fail-mismatch-reftest.html [ WontFix ]
# this test will always fail.
[ Linux Win ] fast/events/option-tab.html [ WontFix ]

# This test only applies to overlay scrollbar platforms.
[ SnowLeopard Win Linux ] fast/dom/partial-layout-overlay-scrollbars.html [ WontFix ]

# <progress> on Mac is always animated. So it's hard to get a reliable pixel result.
[ Mac ] fast/dom/HTMLProgressElement/progress-element.html [ WontFix ]

Expand Down
3 changes: 3 additions & 0 deletions LayoutTests/fast/dom/partial-layout-block-expected.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@

PASS Test that partial layout works for offset{width, height, left, top} methods.

58 changes: 58 additions & 0 deletions LayoutTests/fast/dom/partial-layout-block.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
<!DOCTYPE HTML>
<html>
<script src="../../resources/testharness.js"></script>
<script src="../../resources/testharnessreport.js"></script>
<link rel="stylesheet" href="../../resources/testharness.css">
<head>
<style>
html, body {
margin: 0;
padding: 0;
}

#measure {
margin: 10px;
padding: 10px;
}

#fixedsize {
width: 100px;
height: 100px;
}
</style>
</head>
<body>
<div id="measure"><div id="fixedsize"></div></div>
<script>
if (window.testRunner)
testRunner.dumpAsText();

if (window.internals)
window.internals.setUsesOverlayScrollbars(true);

test(function() {
var measure = document.getElementById('measure');

// Record partial layout values for offset*.
var measureWidth = measure.offsetWidth;
var measureHeight = measure.offsetHeight;
var measureTop = measure.offsetTop;
var measureLeft = measure.offsetLeft;

// Invalidate measure and force a full layout.
var child = measure.firstChild;
measure.removeChild(child);
document.body.clientHeight;
measure.appendChild(child);
var forceLayout = document.body.clientHeight;

var childOffsetTop = child.offsetTop;

assert_equals(measureWidth, document.body.offsetWidth - 20);
assert_equals(measureHeight, measure.offsetHeight);
assert_equals(measureTop, childOffsetTop - 10);
assert_equals(measureLeft, measure.offsetLeft);
}, 'Test that partial layout works for offset{width, height, left, top} methods.');
</script>
</body>
</html>
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@

PASS Test that partial layout works with non-overlay scrollbars.

47 changes: 47 additions & 0 deletions LayoutTests/fast/dom/partial-layout-non-overlay-scrollbars.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
<!DOCTYPE HTML>
<html>
<script src="../../resources/testharness.js"></script>
<script src="../../resources/testharnessreport.js"></script>
<link rel="stylesheet" href="../../resources/testharness.css">
<head>
<style>
::-webkit-scrollbar {
-webkit-appearance: none;
width: 8px;
}

* {
margin: 0;
padding: 0;
}

#makespace {
height: 5000px;
}
</style>
</head>
<body>
<div id="makespace"></div>
<div id="measure"></div>
<script>
if (window.testRunner)
testRunner.dumpAsText();

if (window.internals)
window.internals.setUsesOverlayScrollbars(true); // Note: We force non-overlay scrollbars with CSS.

var test = async_test("Test that partial layout works with non-overlay scrollbars.");
setTimeout(function() {
test.step(function() {
var measure = document.getElementById('measure');
var measureWidth = measure.offsetWidth; // Partial layout occurs here!

assert_equals(measureWidth, document.body.offsetWidth);
assert_equals(measureWidth, window.innerWidth - 8);
});

test.done();
}, 0);
</script>
</body>
</html>
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@

PASS Test that partial layout works with overlay scrollbars.

42 changes: 42 additions & 0 deletions LayoutTests/fast/dom/partial-layout-overlay-scrollbars.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
<!DOCTYPE HTML>
<html>
<script src="../../resources/testharness.js"></script>
<script src="../../resources/testharnessreport.js"></script>
<link rel="stylesheet" href="../../resources/testharness.css">
<head>
<style>
* {
margin: 0;
padding: 0;
}

#makespace {
height: 5000px;
}
</style>
</head>
<body>
<div id="makespace"></div>
<div id="measure"></div>
<script>
if (window.testRunner)
testRunner.dumpAsText();

if (window.internals)
window.internals.setUsesOverlayScrollbars(true);

var test = async_test("Test that partial layout works with overlay scrollbars.");
setTimeout(function() {
test.step(function() {
var measure = document.getElementById('measure');
var measureWidth = measure.offsetWidth; // Partial layout occurs here!

assert_equals(measureWidth, document.body.offsetWidth);
assert_equals(measureWidth, window.innerWidth);
});

test.done();
}, 0);
</script>
</body>
</html>
1 change: 1 addition & 0 deletions Source/core/core.gypi
Original file line number Diff line number Diff line change
Expand Up @@ -1351,6 +1351,7 @@
'rendering/OrderIterator.cpp',
'rendering/OrderIterator.h',
'rendering/LayoutRepainter.cpp',
'rendering/PartialLayoutState.h',
'rendering/Pagination.cpp',
'rendering/Pagination.h',
'rendering/PointerEventsHitRules.cpp',
Expand Down
77 changes: 58 additions & 19 deletions Source/core/dom/Document.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,7 @@
#include "core/platform/DateComponents.h"
#include "core/platform/HistogramSupport.h"
#include "core/platform/Language.h"
#include "core/platform/ScrollbarTheme.h"
#include "core/platform/Timer.h"
#include "core/platform/chromium/TraceEvent.h"
#include "core/platform/network/HTTPParsers.h"
Expand Down Expand Up @@ -1822,6 +1823,9 @@ void Document::updateLayout()
if (frameView && renderer() && (frameView->layoutPending() || renderer()->needsLayout()))
frameView->layout();

if (frameView)
frameView->partialLayout().reset();

setNeedsFocusedElementCheck();
}

Expand All @@ -1834,6 +1838,31 @@ void Document::setNeedsFocusedElementCheck()
m_didPostCheckFocusedElementTask = true;
}

void Document::recalcStyleForLayoutIgnoringPendingStylesheets()
{
TemporaryChange<bool> ignorePendingStylesheets(m_ignorePendingStylesheets, m_ignorePendingStylesheets);
if (!haveStylesheetsLoaded()) {
m_ignorePendingStylesheets = true;
// FIXME: We are willing to attempt to suppress painting with outdated style info only once.
// Our assumption is that it would be dangerous to try to stop it a second time, after page
// content has already been loaded and displayed with accurate style information. (Our
// suppression involves blanking the whole page at the moment. If it were more refined, we
// might be able to do something better.) It's worth noting though that this entire method
// is a hack, since what we really want to do is suspend JS instead of doing a layout with
// inaccurate information.
HTMLElement* bodyElement = body();
if (bodyElement && !bodyElement->renderer() && m_pendingSheetLayout == NoLayoutWithPendingSheets) {
m_pendingSheetLayout = DidLayoutWithPendingSheets;
styleResolverChanged(RecalcStyleImmediately);
} else if (m_hasNodesWithPlaceholderStyle) {
// If new nodes have been added or style recalc has been done with style sheets still
// pending, some nodes may not have had their real style calculated yet. Normally this
// gets cleaned when style sheets arrive but here we need up-to-date style immediately.
recalcStyle(Force);
}
}
}

// FIXME: This is a bad idea and needs to be removed eventually.
// Other browsers load stylesheets before they continue parsing the web page.
// Since we don't, we can run JavaScript code that needs answers before the
Expand All @@ -1842,30 +1871,40 @@ void Document::setNeedsFocusedElementCheck()
// to instead suspend JavaScript execution.
void Document::updateLayoutIgnorePendingStylesheets()
{
bool oldIgnore = m_ignorePendingStylesheets;
recalcStyleForLayoutIgnoringPendingStylesheets();
updateLayout();
}

void Document::partialUpdateLayoutIgnorePendingStylesheets(Node* stopLayoutAtNode)
{
// Non-overlay scrollbars can cause a second layout that is dependent
// on a first layout. This is disabled for partial layout for now.
if (!RuntimeEnabledFeatures::partialLayoutEnabled() || !ScrollbarTheme::theme()->usesOverlayScrollbars()) {
updateLayoutIgnorePendingStylesheets();
return;
}

if (!haveStylesheetsLoaded()) {
m_ignorePendingStylesheets = true;
// FIXME: We are willing to attempt to suppress painting with outdated style info only once. Our assumption is that it would be
// dangerous to try to stop it a second time, after page content has already been loaded and displayed
// with accurate style information. (Our suppression involves blanking the whole page at the
// moment. If it were more refined, we might be able to do something better.)
// It's worth noting though that this entire method is a hack, since what we really want to do is
// suspend JS instead of doing a layout with inaccurate information.
HTMLElement* bodyElement = body();
if (bodyElement && !bodyElement->renderer() && m_pendingSheetLayout == NoLayoutWithPendingSheets) {
m_pendingSheetLayout = DidLayoutWithPendingSheets;
styleResolverChanged(RecalcStyleImmediately);
} else if (m_hasNodesWithPlaceholderStyle)
// If new nodes have been added or style recalc has been done with style sheets still pending, some nodes
// may not have had their real style calculated yet. Normally this gets cleaned when style sheets arrive
// but here we need up-to-date style immediately.
recalcStyle(Force);
TemporaryChange<bool> ignorePendingStylesheets(m_ignorePendingStylesheets, m_ignorePendingStylesheets);
recalcStyleForLayoutIgnoringPendingStylesheets();

if (stopLayoutAtNode) {
RenderObject* renderer = stopLayoutAtNode->renderer();
bool canPartialLayout = renderer;
while (renderer) {
if (!renderer->supportsPartialLayout()) {
canPartialLayout = false;
break;
}
renderer = renderer->parent();
}
if (canPartialLayout && view())
view()->partialLayout().setStopAtRenderer(stopLayoutAtNode->renderer());
}

updateLayout();

m_ignorePendingStylesheets = oldIgnore;
if (view())
view()->partialLayout().reset();
}

PassRefPtr<RenderStyle> Document::styleForElementIgnoringPendingStylesheets(Element* element)
Expand Down
3 changes: 3 additions & 0 deletions Source/core/dom/Document.h
Original file line number Diff line number Diff line change
Expand Up @@ -491,6 +491,7 @@ class Document : public ContainerNode, public TreeScope, public ScriptExecutionC
void updateStyleForNodeIfNeeded(Node*);
void updateLayout();
void updateLayoutIgnorePendingStylesheets();
void partialUpdateLayoutIgnorePendingStylesheets(Node*);
PassRefPtr<RenderStyle> styleForElementIgnoringPendingStylesheets(Element*);
PassRefPtr<RenderStyle> styleForPage(int pageIndex);

Expand Down Expand Up @@ -1121,6 +1122,8 @@ class Document : public ContainerNode, public TreeScope, public ScriptExecutionC

void seamlessParentUpdatedStylesheets();

void recalcStyleForLayoutIgnoringPendingStylesheets();

PassRefPtr<NodeList> handleZeroPadding(const HitTestRequest&, HitTestResult&) const;

void loadEventDelayTimerFired(Timer<Document>*);
Expand Down
8 changes: 4 additions & 4 deletions Source/core/dom/Element.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -569,15 +569,15 @@ static int adjustForLocalZoom(LayoutUnit value, RenderObject* renderer)

int Element::offsetLeft()
{
document().updateLayoutIgnorePendingStylesheets();
document().partialUpdateLayoutIgnorePendingStylesheets(this);
if (RenderBoxModelObject* renderer = renderBoxModelObject())
return adjustForLocalZoom(renderer->pixelSnappedOffsetLeft(), renderer);
return 0;
}

int Element::offsetTop()
{
document().updateLayoutIgnorePendingStylesheets();
document().partialUpdateLayoutIgnorePendingStylesheets(this);
if (RenderBoxModelObject* renderer = renderBoxModelObject())
return adjustForLocalZoom(renderer->pixelSnappedOffsetTop(), renderer);
return 0;
Expand All @@ -592,15 +592,15 @@ int Element::offsetWidth()
return adjustLayoutUnitForAbsoluteZoom(renderer->fixedOffsetWidth(), renderer).round();
}

document().updateLayoutIgnorePendingStylesheets();
document().partialUpdateLayoutIgnorePendingStylesheets(this);
if (RenderBoxModelObject* renderer = renderBoxModelObject())
return adjustLayoutUnitForAbsoluteZoom(renderer->pixelSnappedOffsetWidth(), renderer).round();
return 0;
}

int Element::offsetHeight()
{
document().updateLayoutIgnorePendingStylesheets();
document().partialUpdateLayoutIgnorePendingStylesheets(this);
if (RenderBoxModelObject* renderer = renderBoxModelObject())
return adjustLayoutUnitForAbsoluteZoom(renderer->pixelSnappedOffsetHeight(), renderer).round();
return 0;
Expand Down
1 change: 1 addition & 0 deletions Source/core/html/shadow/SliderThumbElement.h
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ class RenderSliderThumb FINAL : public RenderBlock {

private:
virtual bool isSliderThumb() const;
virtual bool supportsPartialLayout() const OVERRIDE { return false; }
};

// --------------------------------
Expand Down
Loading

0 comments on commit 50887b8

Please sign in to comment.