diff --git a/DEPS b/DEPS
index 335af88e..d001dae 100644
--- a/DEPS
+++ b/DEPS
@@ -36,7 +36,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling Skia
   # and whatever else without interference from each other.
-  'skia_revision': '0e72e9ee3b0201f05e5d74917b17c8a427b14d9f',
+  'skia_revision': '057ae8a15ddd2af639a829d63aca29cbc6b1bb57',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling V8
   # and whatever else without interference from each other.
diff --git a/chrome/browser/ui/autofill/autofill_popup_controller.h b/chrome/browser/ui/autofill/autofill_popup_controller.h
index b2b98b4..53a72924 100644
--- a/chrome/browser/ui/autofill/autofill_popup_controller.h
+++ b/chrome/browser/ui/autofill/autofill_popup_controller.h
@@ -16,7 +16,6 @@
 #include "third_party/skia/include/core/SkColor.h"
 
 namespace gfx {
-class FontList;
 class Point;
 class Rect;
 }
@@ -56,13 +55,6 @@
   // Removes the suggestion at the given index.
   virtual bool RemoveSuggestion(int index) = 0;
 
-#if !defined(OS_ANDROID)
-  // The same font can vary based on the type of data it is showing,
-  // so we need to know the row.
-  virtual const gfx::FontList& GetValueFontListForRow(size_t index) const = 0;
-  virtual const gfx::FontList& GetLabelFontList() const = 0;
-#endif
-
   // Returns the background color of the row item according to its |index|, or
   // transparent if the default popup background should be used.
   virtual SkColor GetBackgroundColorForRow(int index) const = 0;
diff --git a/chrome/browser/ui/autofill/autofill_popup_controller_impl.cc b/chrome/browser/ui/autofill/autofill_popup_controller_impl.cc
index e1bbe01..2e21fe04 100644
--- a/chrome/browser/ui/autofill/autofill_popup_controller_impl.cc
+++ b/chrome/browser/ui/autofill/autofill_popup_controller_impl.cc
@@ -19,6 +19,7 @@
 #include "components/autofill/core/browser/suggestion.h"
 #include "content/public/browser/native_web_keyboard_event.h"
 #include "ui/events/event.h"
+#include "ui/gfx/canvas.h"
 #include "ui/gfx/text_elider.h"
 #include "ui/gfx/text_utils.h"
 
@@ -30,11 +31,6 @@
 // Used to indicate that no line is currently selected by the user.
 const int kNoSelection = -1;
 
-#if !defined(OS_ANDROID)
-// Size difference between the normal font and the smaller font, in pixels.
-const int kSmallerFontSizeDelta = -1;
-#endif
-
 }  // namespace
 
 // static
@@ -81,17 +77,6 @@
   controller_common_->SetKeyPressCallback(
       base::Bind(&AutofillPopupControllerImpl::HandleKeyPressEvent,
                  base::Unretained(this)));
-#if !defined(OS_ANDROID)
-  smaller_font_list_ =
-      normal_font_list_.DeriveWithSizeDelta(kSmallerFontSizeDelta);
-  bold_font_list_ = normal_font_list_.DeriveWithWeight(gfx::Font::Weight::BOLD);
-#if defined(OS_MACOSX)
-  // There is no italic version of the system font.
-  warning_font_list_ = normal_font_list_;
-#else
-  warning_font_list_ = normal_font_list_.DeriveWithStyle(gfx::Font::ITALIC);
-#endif
-#endif
 }
 
 AutofillPopupControllerImpl::~AutofillPopupControllerImpl() {}
@@ -334,11 +319,12 @@
 #if !defined(OS_ANDROID)
 int AutofillPopupControllerImpl::GetElidedValueWidthForRow(size_t row) {
   return gfx::GetStringWidth(GetElidedValueAt(row),
-                             GetValueFontListForRow(row));
+                             layout_model_.GetValueFontListForRow(row));
 }
 
 int AutofillPopupControllerImpl::GetElidedLabelWidthForRow(size_t row) {
-  return gfx::GetStringWidth(GetElidedLabelAt(row), GetLabelFontList());
+  return gfx::GetStringWidth(GetElidedLabelAt(row),
+                             layout_model_.GetLabelFontList());
 }
 #endif
 
@@ -393,40 +379,6 @@
   return true;
 }
 
-#if !defined(OS_ANDROID)
-const gfx::FontList& AutofillPopupControllerImpl::GetValueFontListForRow(
-    size_t index) const {
-  // Autofill values have positive |frontend_id|.
-  if (suggestions_[index].frontend_id > 0)
-    return bold_font_list_;
-
-  // All other message types are defined here.
-  PopupItemId id = static_cast<PopupItemId>(suggestions_[index].frontend_id);
-  switch (id) {
-    case POPUP_ITEM_ID_WARNING_MESSAGE:
-      return warning_font_list_;
-    case POPUP_ITEM_ID_CLEAR_FORM:
-    case POPUP_ITEM_ID_AUTOFILL_OPTIONS:
-    case POPUP_ITEM_ID_SCAN_CREDIT_CARD:
-    case POPUP_ITEM_ID_SEPARATOR:
-      return normal_font_list_;
-    case POPUP_ITEM_ID_CREDIT_CARD_SIGNIN_PROMO:
-      return smaller_font_list_;
-    case POPUP_ITEM_ID_TITLE:
-    case POPUP_ITEM_ID_AUTOCOMPLETE_ENTRY:
-    case POPUP_ITEM_ID_DATALIST_ENTRY:
-    case POPUP_ITEM_ID_PASSWORD_ENTRY:
-      return bold_font_list_;
-  }
-  NOTREACHED();
-  return normal_font_list_;
-}
-
-const gfx::FontList& AutofillPopupControllerImpl::GetLabelFontList() const {
-  return smaller_font_list_;
-}
-#endif
-
 SkColor AutofillPopupControllerImpl::GetBackgroundColorForRow(int index) const {
   if (index == selected_line_)
     return kHoveredBackgroundColor;
@@ -556,10 +508,10 @@
 void AutofillPopupControllerImpl::ElideValueAndLabelForRow(
     size_t row,
     int available_width) {
-  int value_width =
-      gfx::GetStringWidth(suggestions_[row].value, GetValueFontListForRow(row));
-  int label_width =
-      gfx::GetStringWidth(suggestions_[row].label, GetLabelFontList());
+  int value_width = gfx::GetStringWidth(
+      suggestions_[row].value, layout_model_.GetValueFontListForRow(row));
+  int label_width = gfx::GetStringWidth(suggestions_[row].label,
+                                        layout_model_.GetLabelFontList());
   int total_text_length = value_width + label_width;
 
   // The line can have no strings if it represents a UI element, such as
@@ -569,13 +521,14 @@
 
   // Each field receives space in proportion to its length.
   int value_size = available_width * value_width / total_text_length;
-  elided_values_[row] =
-      gfx::ElideText(suggestions_[row].value, GetValueFontListForRow(row),
-                     value_size, gfx::ELIDE_TAIL);
+  elided_values_[row] = gfx::ElideText(
+      suggestions_[row].value, layout_model_.GetValueFontListForRow(row),
+      value_size, gfx::ELIDE_TAIL);
 
   int label_size = available_width * label_width / total_text_length;
-  elided_labels_[row] = gfx::ElideText(
-      suggestions_[row].label, GetLabelFontList(), label_size, gfx::ELIDE_TAIL);
+  elided_labels_[row] =
+      gfx::ElideText(suggestions_[row].label, layout_model_.GetLabelFontList(),
+                     label_size, gfx::ELIDE_TAIL);
 }
 #endif
 
diff --git a/chrome/browser/ui/autofill/autofill_popup_controller_impl.h b/chrome/browser/ui/autofill/autofill_popup_controller_impl.h
index 4dc894b8..c39dc6d6 100644
--- a/chrome/browser/ui/autofill/autofill_popup_controller_impl.h
+++ b/chrome/browser/ui/autofill/autofill_popup_controller_impl.h
@@ -15,7 +15,6 @@
 #include "chrome/browser/ui/autofill/autofill_popup_controller.h"
 #include "chrome/browser/ui/autofill/autofill_popup_layout_model.h"
 #include "chrome/browser/ui/autofill/popup_controller_common.h"
-#include "ui/gfx/font_list.h"
 #include "ui/gfx/geometry/rect.h"
 #include "ui/gfx/geometry/rect_f.h"
 
@@ -97,10 +96,6 @@
                                   base::string16* title,
                                   base::string16* body) override;
   bool RemoveSuggestion(int list_index) override;
-#if !defined(OS_ANDROID)
-  const gfx::FontList& GetValueFontListForRow(size_t index) const override;
-  const gfx::FontList& GetLabelFontList() const override;
-#endif
   SkColor GetBackgroundColorForRow(int index) const override;
   int selected_line() const override;
   const AutofillPopupLayoutModel& layout_model() const override;
@@ -170,19 +165,6 @@
   std::vector<base::string16> elided_values_;
   std::vector<base::string16> elided_labels_;
 
-#if !defined(OS_ANDROID)
-  // The fonts for the popup text.
-  // Normal font (readable size, non bold).
-  gfx::FontList normal_font_list_;
-  // Slightly smaller than the normal font.
-  gfx::FontList smaller_font_list_;
-  // Bold version of the normal font.
-  gfx::FontList bold_font_list_;
-  // Font used for the warning dialog, which may be italic or not depending on
-  // the platform.
-  gfx::FontList warning_font_list_;
-#endif
-
   // The line that is currently selected by the user.
   // |kNoSelection| indicates that no line is currently selected.
   int selected_line_;
diff --git a/chrome/browser/ui/autofill/autofill_popup_controller_unittest.cc b/chrome/browser/ui/autofill/autofill_popup_controller_unittest.cc
index f606678..ce2f055 100644
--- a/chrome/browser/ui/autofill/autofill_popup_controller_unittest.cc
+++ b/chrome/browser/ui/autofill/autofill_popup_controller_unittest.cc
@@ -93,10 +93,6 @@
   using AutofillPopupControllerImpl::RemoveSelectedLine;
   using AutofillPopupControllerImpl::popup_bounds;
   using AutofillPopupControllerImpl::element_bounds;
-#if !defined(OS_ANDROID)
-  using AutofillPopupControllerImpl::GetValueFontListForRow;
-  using AutofillPopupControllerImpl::GetLabelFontList;
-#endif
   using AutofillPopupControllerImpl::SetValues;
   using AutofillPopupControllerImpl::GetWeakPtr;
   MOCK_METHOD1(InvalidateRow, void(size_t));
@@ -445,10 +441,12 @@
   int popup_max_width =
       gfx::GetStringWidth(
           suggestions[0].value,
-          autofill_popup_controller_->GetValueFontListForRow(0)) +
+          autofill_popup_controller_->layout_model().GetValueFontListForRow(
+              0)) +
       gfx::GetStringWidth(
           suggestions[0].label,
-          autofill_popup_controller_->GetLabelFontList()) - 25;
+          autofill_popup_controller_->layout_model().GetLabelFontList()) -
+      25;
 
   autofill_popup_controller_->ElideValueAndLabelForRow(0, popup_max_width);
 
diff --git a/chrome/browser/ui/autofill/autofill_popup_layout_model.cc b/chrome/browser/ui/autofill/autofill_popup_layout_model.cc
index 986a516..5f592fd 100644
--- a/chrome/browser/ui/autofill/autofill_popup_layout_model.cc
+++ b/chrome/browser/ui/autofill/autofill_popup_layout_model.cc
@@ -15,6 +15,7 @@
 #include "components/autofill/core/common/autofill_util.h"
 #include "grit/components_scaled_resources.h"
 #include "ui/base/resource/resource_bundle.h"
+#include "ui/gfx/font_list.h"
 #include "ui/gfx/geometry/rect_conversions.h"
 
 namespace autofill {
@@ -27,6 +28,11 @@
 // The vertical height of a separator in pixels.
 const size_t kSeparatorHeight = 1;
 
+#if !defined(OS_ANDROID)
+// Size difference between the normal font and the smaller font, in pixels.
+const int kSmallerFontSizeDelta = -1;
+#endif
+
 const struct {
   const char* name;
   int id;
@@ -55,7 +61,21 @@
 
 AutofillPopupLayoutModel::AutofillPopupLayoutModel(
     AutofillPopupViewDelegate* delegate)
-    : delegate_(delegate) {}
+    : delegate_(delegate) {
+#if !defined(OS_ANDROID)
+  smaller_font_list_ =
+      normal_font_list_.DeriveWithSizeDelta(kSmallerFontSizeDelta);
+  bold_font_list_ = normal_font_list_.DeriveWithWeight(gfx::Font::Weight::BOLD);
+#if defined(OS_MACOSX)
+  // There is no italic version of the system font.
+  warning_font_list_ = normal_font_list_;
+#else
+  warning_font_list_ = normal_font_list_.DeriveWithStyle(gfx::Font::ITALIC);
+#endif
+#endif
+}
+
+AutofillPopupLayoutModel::~AutofillPopupLayoutModel() {}
 
 #if !defined(OS_ANDROID)
 int AutofillPopupLayoutModel::GetDesiredPopupHeight() const {
@@ -125,6 +145,40 @@
       popup_width, popup_height, RoundedElementBounds(),
       delegate_->container_view(), delegate_->IsRTL());
 }
+
+const gfx::FontList& AutofillPopupLayoutModel::GetValueFontListForRow(
+    size_t index) const {
+  std::vector<autofill::Suggestion> suggestions = delegate_->GetSuggestions();
+
+  // Autofill values have positive |frontend_id|.
+  if (suggestions[index].frontend_id > 0)
+    return bold_font_list_;
+
+  // All other message types are defined here.
+  PopupItemId id = static_cast<PopupItemId>(suggestions[index].frontend_id);
+  switch (id) {
+    case POPUP_ITEM_ID_WARNING_MESSAGE:
+      return warning_font_list_;
+    case POPUP_ITEM_ID_CLEAR_FORM:
+    case POPUP_ITEM_ID_AUTOFILL_OPTIONS:
+    case POPUP_ITEM_ID_SCAN_CREDIT_CARD:
+    case POPUP_ITEM_ID_SEPARATOR:
+      return normal_font_list_;
+    case POPUP_ITEM_ID_CREDIT_CARD_SIGNIN_PROMO:
+      return smaller_font_list_;
+    case POPUP_ITEM_ID_TITLE:
+    case POPUP_ITEM_ID_AUTOCOMPLETE_ENTRY:
+    case POPUP_ITEM_ID_DATALIST_ENTRY:
+    case POPUP_ITEM_ID_PASSWORD_ENTRY:
+      return bold_font_list_;
+  }
+  NOTREACHED();
+  return normal_font_list_;
+}
+
+const gfx::FontList& AutofillPopupLayoutModel::GetLabelFontList() const {
+  return smaller_font_list_;
+}
 #endif
 
 int AutofillPopupLayoutModel::LineFromY(int y) const {
diff --git a/chrome/browser/ui/autofill/autofill_popup_layout_model.h b/chrome/browser/ui/autofill/autofill_popup_layout_model.h
index aad29d1..0e90842 100644
--- a/chrome/browser/ui/autofill/autofill_popup_layout_model.h
+++ b/chrome/browser/ui/autofill/autofill_popup_layout_model.h
@@ -10,6 +10,7 @@
 #include "base/strings/string16.h"
 #include "chrome/browser/ui/autofill/autofill_popup_view_delegate.h"
 #include "chrome/browser/ui/autofill/popup_view_common.h"
+#include "ui/gfx/font_list.h"
 #include "ui/gfx/geometry/rect.h"
 #include "ui/gfx/native_widget_types.h"
 
@@ -29,6 +30,7 @@
 class AutofillPopupLayoutModel {
  public:
   explicit AutofillPopupLayoutModel(AutofillPopupViewDelegate* delegate);
+  ~AutofillPopupLayoutModel();
 
   // The minimum amount of padding between the Autofill name and subtext,
   // in pixels.
@@ -60,6 +62,11 @@
   // Calculates and sets the bounds of the popup, including placing it properly
   // to prevent it from going off the screen.
   void UpdatePopupBounds();
+
+  // The same font can vary based on the type of data it is showing at the row
+  // |index|.
+  const gfx::FontList& GetValueFontListForRow(size_t index) const;
+  const gfx::FontList& GetLabelFontList() const;
 #endif
 
   // Convert a y-coordinate to the closest line.
@@ -79,6 +86,19 @@
   // Returns the enclosing rectangle for the element_bounds.
   const gfx::Rect RoundedElementBounds() const;
 
+#if !defined(OS_ANDROID)
+  // The fonts for the popup text.
+  // Normal font (readable size, non bold).
+  gfx::FontList normal_font_list_;
+  // Slightly smaller than the normal font.
+  gfx::FontList smaller_font_list_;
+  // Bold version of the normal font.
+  gfx::FontList bold_font_list_;
+  // Font used for the warning dialog, which may be italic or not depending on
+  // the platform.
+  gfx::FontList warning_font_list_;
+#endif
+
   // The bounds of the Autofill popup.
   gfx::Rect popup_bounds_;
 
diff --git a/chrome/browser/ui/cocoa/autofill/autofill_popup_view_cocoa.mm b/chrome/browser/ui/cocoa/autofill/autofill_popup_view_cocoa.mm
index c368b4c..35856a79 100644
--- a/chrome/browser/ui/cocoa/autofill/autofill_popup_view_cocoa.mm
+++ b/chrome/browser/ui/cocoa/autofill/autofill_popup_view_cocoa.mm
@@ -197,12 +197,13 @@
         textYOffset:(CGFloat)textYOffset {
   NSColor* nameColor =
       controller_->IsWarning(index) ? [self warningColor] : [self nameColor];
-  NSDictionary* nameAttributes =
-      [NSDictionary dictionaryWithObjectsAndKeys:
-           controller_->GetValueFontListForRow(index).GetPrimaryFont().
-               GetNativeFont(),
-           NSFontAttributeName, nameColor, NSForegroundColorAttributeName,
-           nil];
+  NSDictionary* nameAttributes = [NSDictionary
+      dictionaryWithObjectsAndKeys:controller_->layout_model()
+                                       .GetValueFontListForRow(index)
+                                       .GetPrimaryFont()
+                                       .GetNativeFont(),
+                                   NSFontAttributeName, nameColor,
+                                   NSForegroundColorAttributeName, nil];
   NSSize nameSize = [name sizeWithAttributes:nameAttributes];
   x -= rightAlign ? nameSize.width : 0;
   CGFloat y = bounds.origin.y + (bounds.size.height - nameSize.height) / 2;
@@ -241,13 +242,13 @@
             rightAlign:(BOOL)rightAlign
                 bounds:(NSRect)bounds
            textYOffset:(CGFloat)textYOffset {
-  NSDictionary* subtextAttributes =
-      [NSDictionary dictionaryWithObjectsAndKeys:
-           controller_->GetLabelFontList().GetPrimaryFont().GetNativeFont(),
-           NSFontAttributeName,
-           [self subtextColor],
-           NSForegroundColorAttributeName,
-           nil];
+  NSDictionary* subtextAttributes = [NSDictionary
+      dictionaryWithObjectsAndKeys:controller_->layout_model()
+                                       .GetLabelFontList()
+                                       .GetPrimaryFont()
+                                       .GetNativeFont(),
+                                   NSFontAttributeName, [self subtextColor],
+                                   NSForegroundColorAttributeName, nil];
   NSSize subtextSize = [subtext sizeWithAttributes:subtextAttributes];
   x -= rightAlign ? subtextSize.width : 0;
   CGFloat y = bounds.origin.y + (bounds.size.height - subtextSize.height) / 2;
diff --git a/chrome/browser/ui/views/autofill/autofill_popup_view_views.cc b/chrome/browser/ui/views/autofill/autofill_popup_view_views.cc
index b40c8e3..c289a8c 100644
--- a/chrome/browser/ui/views/autofill/autofill_popup_view_views.cc
+++ b/chrome/browser/ui/views/autofill/autofill_popup_view_views.cc
@@ -80,7 +80,7 @@
   value_rect.Inset(AutofillPopupLayoutModel::kEndPadding, 0);
   canvas->DrawStringRectWithFlags(
       controller_->GetElidedValueAt(index),
-      controller_->GetValueFontListForRow(index),
+      controller_->layout_model().GetValueFontListForRow(index),
       controller_->IsWarning(index) ? kWarningTextColor : kValueTextColor,
       value_rect, text_align);
 
@@ -111,13 +111,13 @@
   // Draw the label text.
   const int label_width =
       gfx::GetStringWidth(controller_->GetElidedLabelAt(index),
-                          controller_->GetLabelFontList());
+                          controller_->layout_model().GetLabelFontList());
   if (!is_rtl)
     x_align_left -= label_width;
 
   canvas->DrawStringRectWithFlags(
-      controller_->GetElidedLabelAt(index), controller_->GetLabelFontList(),
-      kLabelTextColor,
+      controller_->GetElidedLabelAt(index),
+      controller_->layout_model().GetLabelFontList(), kLabelTextColor,
       gfx::Rect(x_align_left, entry_rect.y(), label_width, entry_rect.height()),
       text_align);
 }
diff --git a/content/public/renderer/render_view.h b/content/public/renderer/render_view.h
index 00a3074d..6191c19 100644
--- a/content/public/renderer/render_view.h
+++ b/content/public/renderer/render_view.h
@@ -38,7 +38,6 @@
 namespace content {
 
 class RenderFrame;
-class RenderWidget;
 class RenderViewVisitor;
 struct SSLStatus;
 struct WebPreferences;
@@ -67,9 +66,6 @@
   static void ApplyWebPreferences(const WebPreferences& preferences,
                                   blink::WebView* web_view);
 
-  // Returns the RenderWidget for this RenderView.
-  virtual RenderWidget* GetWidget() const = 0;
-
   // Returns the main RenderFrame.
   virtual RenderFrame* GetMainRenderFrame() = 0;
 
diff --git a/content/renderer/browser_plugin/browser_plugin.cc b/content/renderer/browser_plugin/browser_plugin.cc
index 79b007a8..93947fde 100644
--- a/content/renderer/browser_plugin/browser_plugin.cc
+++ b/content/renderer/browser_plugin/browser_plugin.cc
@@ -370,7 +370,7 @@
   // Convert the plugin_rect_in_viewport to window coordinates, which is css.
   WebRect rect_in_css(plugin_rect_in_viewport);
   blink::WebView* webview = container()->document().frame()->view();
-  RenderView::FromWebView(webview)->GetWidget()->convertViewportToWindow(
+  RenderViewImpl::FromWebView(webview)->GetWidget()->convertViewportToWindow(
       &rect_in_css);
   view_rect_ = rect_in_css;
 
diff --git a/content/renderer/render_frame_impl_browsertest.cc b/content/renderer/render_frame_impl_browsertest.cc
index dc7d26a..999a9b39 100644
--- a/content/renderer/render_frame_impl_browsertest.cc
+++ b/content/renderer/render_frame_impl_browsertest.cc
@@ -139,7 +139,7 @@
 // RenderWidget.
 TEST_F(RenderFrameImplTest, MAYBE_SubframeWidget) {
   EXPECT_TRUE(frame_widget());
-  EXPECT_NE(frame_widget(), view_->GetWidget());
+  EXPECT_NE(frame_widget(), static_cast<RenderViewImpl*>(view_)->GetWidget());
 }
 
 // Verify a subframe RenderWidget properly processes its viewport being
diff --git a/content/renderer/render_view_impl.h b/content/renderer/render_view_impl.h
index 24bd092..f861acb 100644
--- a/content/renderer/render_view_impl.h
+++ b/content/renderer/render_view_impl.h
@@ -179,6 +179,9 @@
   // May return NULL when the view is closing.
   blink::WebView* webview() const;
 
+  // Returns the RenderWidget for this RenderView.
+  RenderWidget* GetWidget() const;
+
   const WebPreferences& webkit_preferences() const {
     return webkit_preferences_;
   }
@@ -402,7 +405,6 @@
   // RenderView implementation -------------------------------------------------
 
   bool Send(IPC::Message* message) override;
-  RenderWidget* GetWidget() const override;
   RenderFrameImpl* GetMainRenderFrame() override;
   int GetRoutingID() const override;
   gfx::Size GetSize() const override;
diff --git a/content/renderer/render_widget_browsertest.cc b/content/renderer/render_widget_browsertest.cc
index 69dd2e4..bf81972 100644
--- a/content/renderer/render_widget_browsertest.cc
+++ b/content/renderer/render_widget_browsertest.cc
@@ -11,7 +11,9 @@
 
 class RenderWidgetTest : public RenderViewTest {
  protected:
-  RenderWidget* widget() { return view_->GetWidget(); }
+  RenderWidget* widget() {
+    return static_cast<RenderViewImpl*>(view_)->GetWidget();
+  }
 
   void OnResize(const ResizeParams& params) {
     widget()->OnResize(params);
diff --git a/content/test/gpu/gpu_tests/pixel_expectations.py b/content/test/gpu/gpu_tests/pixel_expectations.py
index 036f354d..2a054c0 100644
--- a/content/test/gpu/gpu_tests/pixel_expectations.py
+++ b/content/test/gpu/gpu_tests/pixel_expectations.py
@@ -30,7 +30,3 @@
     # TODO(erikchen) check / generate reference images.
     self.Fail('Pixel.CSSFilterEffects', ['mac'], bug=581526)
     self.Fail('Pixel.CSSFilterEffects.NoOverlays', ['mac'], bug=581526)
-
-    # TODO(kbr): remove once expectations for Android have been
-    # generated using the new naming convention.
-    self.Fail('*', ['android'], bug=624621)
diff --git a/content/test/gpu/gpu_tests/webgl2_conformance_expectations.py b/content/test/gpu/gpu_tests/webgl2_conformance_expectations.py
index bb90bf65..ddaebfa7 100644
--- a/content/test/gpu/gpu_tests/webgl2_conformance_expectations.py
+++ b/content/test/gpu/gpu_tests/webgl2_conformance_expectations.py
@@ -147,6 +147,9 @@
     self.Fail('conformance2/rendering/uniform-block-buffer-size.html',
         ['win', 'intel'], bug=628863)
 
+    self.Fail('deqp/functional/gles3/fbomultisample*',
+        ['mac', 'intel'], bug=483282)
+
     # It's unfortunate that these suppressions need to be so broad, but it
     # looks like the D3D11 device can be lost spontaneously on this
     # configuration while running basically any test.
diff --git a/content/test/layouttest_support.cc b/content/test/layouttest_support.cc
index 904e900..f7dbe2b 100644
--- a/content/test/layouttest_support.cc
+++ b/content/test/layouttest_support.cc
@@ -283,7 +283,9 @@
 
 void SetDeviceColorProfile(RenderView* render_view, const std::string& name) {
   if (name == "reset") {
-    render_view->GetWidget()->ResetDeviceColorProfileForTesting();
+    static_cast<RenderViewImpl*>(render_view)
+        ->GetWidget()
+        ->ResetDeviceColorProfileForTesting();
     return;
   }
 
@@ -415,7 +417,9 @@
     color_profile.assign(test.data(), test.data() + test.size());
   }
 
-  render_view->GetWidget()->SetDeviceColorProfileForTesting(color_profile);
+  static_cast<RenderViewImpl*>(render_view)
+      ->GetWidget()
+      ->SetDeviceColorProfileForTesting(color_profile);
 }
 
 void SetTestBluetoothScanDuration() {
diff --git a/third_party/WebKit/LayoutTests/http/tests/media/video-load-metadata-decode-error-expected.txt b/third_party/WebKit/LayoutTests/http/tests/media/video-load-metadata-decode-error-expected.txt
deleted file mode 100644
index 817588a8..0000000
--- a/third_party/WebKit/LayoutTests/http/tests/media/video-load-metadata-decode-error-expected.txt
+++ /dev/null
@@ -1,9 +0,0 @@
-This test case simulates a decode error after loading meta data of a video.
-
-EVENT(loadedmetadata)
-loaded metadata of media file
-EVENT(error)
-failed to load media file
-EXPECTED (video.networkState == '1') OK
-END OF TEST
-
diff --git a/third_party/WebKit/LayoutTests/http/tests/media/video-load-metadata-decode-error.html b/third_party/WebKit/LayoutTests/http/tests/media/video-load-metadata-decode-error.html
index ca566a3e..cd3f0200 100644
--- a/third_party/WebKit/LayoutTests/http/tests/media/video-load-metadata-decode-error.html
+++ b/third_party/WebKit/LayoutTests/http/tests/media/video-load-metadata-decode-error.html
@@ -1,41 +1,20 @@
-<html>
-<head>
-    <title>Loading corrupted video with proper metadata</title>
-    <script src=../../../media-resources/media-file.js></script>
-    <!-- TODO(foolip): Convert test to testharness.js. crbug.com/588956
-         (Please avoid writing new tests using video-test.js) -->
-    <script src=../../../media-resources/video-test.js></script>
-    <script>
-        function loadedmetadata(e)
-        {
-            consoleWrite("loaded metadata of media file");
-        }
+<!DOCTYPE html>
+<title>This test case simulates a decode error after loading meta data of a video.</title>
+<script src="../resources/testharness.js"></script>
+<script src="../resources/testharnessreport.js"></script>
+<script src="../../media-resources/media-file.js"></script>
+<video></video>
+<script>
+async_test(function(t) {
+    var video = document.querySelector("video");
 
-        function error(e)
-        {
-            consoleWrite("failed to load media file");
-            testExpected("video.networkState", HTMLMediaElement.NETWORK_IDLE);
-            endTest();
-        }
-
-        function start()
-        {
-            findMediaElement();
-
-            waitForEvent('loadedmetadata', loadedmetadata);
-            waitForEvent("error", error);
-
-            var movie = findMediaFile("video", "resources/test");
-            var type = mimeTypeForExtension(movie.split('.').pop());
-            video.src = "video-load-metadata-decode-error.cgi?name=" + movie + "&type=" + type;
-            video.play();
-        }
-    </script>
-</head>
-<body onload="start()">
-<video id="video"></video>
-<p>
-This test case simulates a decode error after loading meta data of a video.<br/><br/>
-</p>
-</body>
-</html>
+    var watcher = new EventWatcher(t, video, ["loadedmetadata", "error"]);
+    watcher.wait_for(["loadedmetadata", "error"]).then(t.step_func_done(function() {
+        assert_equals(video.networkState, HTMLMediaElement.NETWORK_IDLE);
+    }));
+    var movie = findMediaFile("video", "resources/test");
+    var type = mimeTypeForExtension(movie.split(".").pop());
+    video.src = "video-load-metadata-decode-error.cgi?name=" + movie + "&type=" + type;
+    video.play();
+});
+</script>
\ No newline at end of file
diff --git a/third_party/WebKit/LayoutTests/http/tests/media/video-load-suspend-expected.txt b/third_party/WebKit/LayoutTests/http/tests/media/video-load-suspend-expected.txt
deleted file mode 100644
index 92697a2a..0000000
--- a/third_party/WebKit/LayoutTests/http/tests/media/video-load-suspend-expected.txt
+++ /dev/null
@@ -1,8 +0,0 @@
-Test that the load eventually suspends and returns to NETWORK_IDLE.
-
-RUN(video.src = file)
-EVENT(loadstart)
-EVENT(suspend)
-EXPECTED (video.networkState == '1') OK
-END OF TEST
-
diff --git a/third_party/WebKit/LayoutTests/http/tests/media/video-load-suspend.html b/third_party/WebKit/LayoutTests/http/tests/media/video-load-suspend.html
index 1f531d89..71a7186 100644
--- a/third_party/WebKit/LayoutTests/http/tests/media/video-load-suspend.html
+++ b/third_party/WebKit/LayoutTests/http/tests/media/video-load-suspend.html
@@ -1,33 +1,16 @@
-<html>
-<head>
-<script src=../../media-resources/media-file.js></script>
-<!-- TODO(foolip): Convert test to testharness.js. crbug.com/588956
-     (Please avoid writing new tests using video-test.js) -->
-<script src=../../media-resources/video-test.js></script>
+<!DOCTYPE html>
+<title>Test that the load eventually suspends and returns to NETWORK_IDLE.</title>
+<script src="../resources/testharness.js"></script>
+<script src="../resources/testharnessreport.js"></script>
+<script src="../../media-resources/media-file.js"></script>
+<video></video>
 <script>
-    var file = findMediaFile("video", "http://127.0.0.1:8000/resources/test");
-
-    function init()
-    {
-        findMediaElement();
-        run("video.src = file");
-        waitForEvent('loadstart', onLoadStart);
-    }
-
-    function onLoadStart()
-    {
-        waitForEvent('suspend', onSuspend);
-    }
-
-    function onSuspend()
-    {
-        testExpected("video.networkState", HTMLMediaElement.NETWORK_IDLE, "==");
-        endTest();
-    }
-</script>
-</head>
-<body onload="init()">
-    <p>Test that the load eventually suspends and returns to NETWORK_IDLE.</p>
-    <video></video>
-</body>
-</html>
+async_test(function(t) {
+    var video = document.querySelector("video");
+    video.src = findMediaFile("video", "http://127.0.0.1:8000/resources/test");
+    var watcher = new EventWatcher(t, video, ["loadstart", "suspend"]);
+    watcher.wait_for(["loadstart", "suspend"]).then(t.step_func_done(function() {
+        assert_equals(video.networkState, HTMLMediaElement.NETWORK_IDLE);
+    }));
+});
+</script>
\ No newline at end of file
diff --git a/third_party/WebKit/LayoutTests/http/tests/media/video-load-twice-expected.txt b/third_party/WebKit/LayoutTests/http/tests/media/video-load-twice-expected.txt
deleted file mode 100644
index 9d08efb8..0000000
--- a/third_party/WebKit/LayoutTests/http/tests/media/video-load-twice-expected.txt
+++ /dev/null
@@ -1,12 +0,0 @@
-RUN(video.src = file)
-EVENT(loadedmetadata)
-EVENT(loadeddata)
-EVENT(canplay)
-EVENT(canplaythrough)
-RUN(video.src = file)
-EVENT(loadedmetadata)
-EVENT(loadeddata)
-EVENT(canplay)
-EVENT(canplaythrough)
-END OF TEST
-
diff --git a/third_party/WebKit/LayoutTests/http/tests/media/video-load-twice.html b/third_party/WebKit/LayoutTests/http/tests/media/video-load-twice.html
index 6a636ee..f89f1bd 100644
--- a/third_party/WebKit/LayoutTests/http/tests/media/video-load-twice.html
+++ b/third_party/WebKit/LayoutTests/http/tests/media/video-load-twice.html
@@ -1,35 +1,28 @@
-<html>
-<head>
-<script src=../../media-resources/media-file.js></script>
-<!-- TODO(foolip): Convert test to testharness.js. crbug.com/588956
-     (Please avoid writing new tests using video-test.js) -->
-<script src=../../media-resources/video-test.js></script>
+<!DOCTYPE html>
+<title>Test loading video twice.</title>
+<script src="../resources/testharness.js"></script>
+<script src="../resources/testharnessreport.js"></script>
+<script src="../../media-resources/media-file.js"></script>
+<body>
 <script>
+async_test(function(t) {
     var file = findMediaFile("video", "http://127.0.0.1:8000/resources/test");
-
-    function createVideo() {
+    createAndLoadVideo(false);
+    function createAndLoadVideo(endTest) {
         var video = document.createElement("video");
         document.body.appendChild(video);
-        findMediaElement();
-        waitForEvent('loadedmetadata');
-        waitForEvent('loadeddata');
-        waitForEvent('canplay');
-    }
+        var expectedEvents = ["loadedmetadata", "loadeddata", "canplay", "canplaythrough"];
+        var watcher = new EventWatcher(t, video, expectedEvents);
+        watcher.wait_for(expectedEvents).then(t.step_func(function() {
+            if (endTest) {
+                t.done();
+            }  else {
+                document.body.removeChild(video);
+                createAndLoadVideo(true);
+            }
+        }));
 
-    function firstCanPlayThrough() {
-        document.body.removeChild(video);
-        createVideo();
-        waitForEventOnce('canplaythrough', endTest);
-        run("video.src = file");
+        video.src = file;
     }
-
-    function init() {
-        createVideo();
-        waitForEventOnce('canplaythrough', firstCanPlayThrough);
-        run("video.src = file");
-    }
-</script>
-</head>
-<body onload="init()">
-</body>
-</html>
+});
+</script>
\ No newline at end of file
diff --git a/third_party/WebKit/LayoutTests/http/tests/media/video-load-with-userpass-expected.txt b/third_party/WebKit/LayoutTests/http/tests/media/video-load-with-userpass-expected.txt
deleted file mode 100644
index 92f4aed..0000000
--- a/third_party/WebKit/LayoutTests/http/tests/media/video-load-with-userpass-expected.txt
+++ /dev/null
@@ -1,5 +0,0 @@
- 
-Tests that the media player does not include authorization information.  
-EVENT(canplay)
-END OF TEST
-
diff --git a/third_party/WebKit/LayoutTests/http/tests/media/video-load-with-userpass.html b/third_party/WebKit/LayoutTests/http/tests/media/video-load-with-userpass.html
index 9014541..34bac02 100644
--- a/third_party/WebKit/LayoutTests/http/tests/media/video-load-with-userpass.html
+++ b/third_party/WebKit/LayoutTests/http/tests/media/video-load-with-userpass.html
@@ -1,40 +1,29 @@
-<html>
-    <head>
-        <!-- TODO(foolip): Convert test to testharness.js. crbug.com/588956
-             (Please avoid writing new tests using video-test.js) -->
-        <script src=../../media-resources/video-test.js></script>
-        <script src=../../media-resources/media-file.js></script>
-        <script>
-            function loadMediaFrame()
-            {
-                findMediaElement();
+<!DOCTYPE html>
+<title>Tests that the media player does not include authorization information.</title>
+<script src="../resources/testharness.js"></script>
+<script src="../resources/testharnessreport.js"></script>
+<script src="../../media-resources/media-file.js"></script>
+<body>
+<video>
+    <source></source>
+</video>
+<script>
+async_test(function(t) {
+    var frame = document.createElement('iframe');
+    frame.onload = t.step_func(function() {
+        var video = document.querySelector('video');
+        video.onerror = t.unreached_func();
+        video.oncanplay = t.step_func_done();
 
-                var movie = findMediaFile('video', 'test');
-                var type = mimeTypeForExtension(movie.split('.').pop());
-                var frame = document.createElement('iframe');
-                frame.width = 0;
-                frame.height = 0;
-                frame.addEventListener('load', function () {
-                        source = document.getElementById('source');
-                        source.src = 'http://user:pass@127.0.0.1:8080/media/resources/video-check-userpass.php?name=' + movie + '&type=' + type;
-                        source.type = type;
+        var movie = findMediaFile('video', 'test');
+        var type = mimeTypeForExtension(movie.split('.').pop());
+        source = document.querySelector('source');
+        source.type = type;
+        source.src = 'http://user:pass@127.0.0.1:8080/media/resources/video-check-userpass.php?name=' + movie + '&type=' + type;
+        video.load();
+    });
 
-                        waitForEventAndFail('error');
-                        waitForEventAndEnd('canplay');
-                        video.load();
-                });
-
-                frame.src = "data:text/html,<b>test</b>";
-                document.body.appendChild(frame);
-            }
-        </script>
-    </head>
-
-    <body onload="loadMediaFrame()">
-        <video id="video">
-            <source id="source">
-        </video>
-        <br>
-        Tests that the media player does not include authorization information.
-    </body>
-</html>
+    frame.src = 'data:text/html,<b>test</b>';
+    document.body.appendChild(frame);
+});
+</script>
\ No newline at end of file
diff --git a/third_party/WebKit/Source/core/paint/PaintPropertyTreeBuilder.cpp b/third_party/WebKit/Source/core/paint/PaintPropertyTreeBuilder.cpp
index 1540c0f..5ef29d2 100644
--- a/third_party/WebKit/Source/core/paint/PaintPropertyTreeBuilder.cpp
+++ b/third_party/WebKit/Source/core/paint/PaintPropertyTreeBuilder.cpp
@@ -68,6 +68,8 @@
     context.current.transform = frameView.scrollTranslation();
     context.current.paintOffset = LayoutPoint();
     context.current.clip = frameView.contentClip();
+    context.current.renderingContextID = 0;
+    context.current.shouldFlattenInheritedTransform = true;
     context.absolutePosition = context.current;
     context.containerForAbsolutePosition = nullptr;
     context.fixedPosition = context.current;
@@ -115,7 +117,8 @@
             LayoutPoint fractionalPaintOffset = LayoutPoint(context.current.paintOffset - roundedPaintOffset);
 
             updateOrCreatePaintProperty<TransformPaintPropertyNode, &ObjectPaintProperties::paintOffsetTranslation, &ObjectPaintProperties::setPaintOffsetTranslation>(
-                object, context, context.current.transform, TransformationMatrix().translate(roundedPaintOffset.x(), roundedPaintOffset.y()), FloatPoint3D());
+                object, context, context.current.transform, TransformationMatrix().translate(roundedPaintOffset.x(), roundedPaintOffset.y()), FloatPoint3D(),
+                context.current.shouldFlattenInheritedTransform, context.current.renderingContextID);
             context.current.paintOffset = fractionalPaintOffset;
             return;
         }
@@ -148,17 +151,38 @@
             // The origin is included in the local transform, so leave origin empty.
             updateOrCreatePaintProperty<TransformPaintPropertyNode, &ObjectPaintProperties::transform, &ObjectPaintProperties::setTransform>(
                 object, context, context.current.transform, TransformationMatrix(transform), FloatPoint3D());
+            context.current.renderingContextID = 0;
+            context.current.shouldFlattenInheritedTransform = false;
             return;
         }
     } else {
         const ComputedStyle& style = object.styleRef();
-        if (object.isBox() && style.hasTransform()) {
+        if (object.isBox() && (style.hasTransform() || style.preserves3D())) {
             TransformationMatrix matrix;
             style.applyTransform(matrix, toLayoutBox(object).size(), ComputedStyle::ExcludeTransformOrigin,
                 ComputedStyle::IncludeMotionPath, ComputedStyle::IncludeIndependentTransformProperties);
             FloatPoint3D origin = transformOrigin(toLayoutBox(object));
+
+            unsigned renderingContextID = context.current.renderingContextID;
+            unsigned renderingContextIDForChildren = 0;
+            bool flattensInheritedTransform = context.current.shouldFlattenInheritedTransform;
+            bool childrenFlattenInheritedTransform = true;
+
+            // TODO(trchen): transform-style should only be respected if a PaintLayer is
+            // created.
+            if (style.preserves3D()) {
+                // If a node with transform-style: preserve-3d does not exist in an
+                // existing rendering context, it establishes a new one.
+                if (!renderingContextID)
+                    renderingContextID = PtrHash<const LayoutObject>::hash(&object);
+                renderingContextIDForChildren = renderingContextID;
+                childrenFlattenInheritedTransform = false;
+            }
+
             updateOrCreatePaintProperty<TransformPaintPropertyNode, &ObjectPaintProperties::transform, &ObjectPaintProperties::setTransform>(
-                object, context, context.current.transform, matrix, origin);
+                object, context, context.current.transform, matrix, origin, flattensInheritedTransform, renderingContextID);
+            context.current.renderingContextID = renderingContextIDForChildren;
+            context.current.shouldFlattenInheritedTransform = childrenFlattenInheritedTransform;
             return;
         }
     }
@@ -275,9 +299,14 @@
         return;
     }
 
+    // The perspective node must not flatten (else nothing will get
+    // perspective), but it should still extend the rendering context as most
+    // transform nodes do.
+    TransformationMatrix matrix = TransformationMatrix().applyPerspective(style.perspective());
     FloatPoint3D origin = perspectiveOrigin(toLayoutBox(object)) + toLayoutSize(context.current.paintOffset);
     updateOrCreatePaintProperty<TransformPaintPropertyNode, &ObjectPaintProperties::perspective, &ObjectPaintProperties::setPerspective>(
-        object, context, context.current.transform, TransformationMatrix().applyPerspective(style.perspective()), origin);
+        object, context, context.current.transform, matrix, origin, context.current.shouldFlattenInheritedTransform, context.current.renderingContextID);
+    context.current.shouldFlattenInheritedTransform = false;
 }
 
 void PaintPropertyTreeBuilder::updateSvgLocalToBorderBoxTransform(const LayoutObject& object, PaintPropertyTreeBuilderContext& context)
@@ -298,6 +327,8 @@
 
     updateOrCreatePaintProperty<TransformPaintPropertyNode, &ObjectPaintProperties::svgLocalToBorderBoxTransform, &ObjectPaintProperties::setSvgLocalToBorderBoxTransform>(
         object, context, context.current.transform, transformToBorderBox, FloatPoint3D());
+    context.current.shouldFlattenInheritedTransform = false;
+    context.current.renderingContextID = 0;
 }
 
 void PaintPropertyTreeBuilder::updateScrollTranslation(const LayoutObject& object, PaintPropertyTreeBuilderContext& context)
@@ -310,7 +341,8 @@
         if (!scrollOffset.isZero() || layer->scrollsOverflow()) {
             TransformationMatrix matrix = TransformationMatrix().translate(-scrollOffset.width(), -scrollOffset.height());
             updateOrCreatePaintProperty<TransformPaintPropertyNode, &ObjectPaintProperties::scrollTranslation, &ObjectPaintProperties::setScrollTranslation>(
-                object, context, context.current.transform, matrix, FloatPoint3D());
+                object, context, context.current.transform, matrix, FloatPoint3D(), context.current.shouldFlattenInheritedTransform, context.current.renderingContextID);
+            context.current.shouldFlattenInheritedTransform = false;
             return;
         }
     }
@@ -412,8 +444,6 @@
     updateLocalBorderBoxContext(object, context);
     updateScrollbarPaintOffset(object, context);
     updateOverflowClip(object, context);
-    // TODO(trchen): Insert flattening transform here, as specified by
-    // http://www.w3.org/TR/css3-transforms/#transform-style-property
     updatePerspective(object, context);
     updateSvgLocalToBorderBoxTransform(object, context);
     updateScrollTranslation(object, context);
diff --git a/third_party/WebKit/Source/core/paint/PaintPropertyTreeBuilder.h b/third_party/WebKit/Source/core/paint/PaintPropertyTreeBuilder.h
index 72a21f3d..9055a1f 100644
--- a/third_party/WebKit/Source/core/paint/PaintPropertyTreeBuilder.h
+++ b/third_party/WebKit/Source/core/paint/PaintPropertyTreeBuilder.h
@@ -30,6 +30,14 @@
         // the space with its own layout location.
         TransformPaintPropertyNode* transform = nullptr;
         LayoutPoint paintOffset;
+        // Whether newly created children should flatten their inherited transform
+        // (equivalently, draw into the plane of their parent). Should generally
+        // be updated whenever |transform| is; flattening only needs to happen
+        // to immediate children.
+        bool shouldFlattenInheritedTransform = false;
+        // Rendering context for 3D sorting. See
+        // TransformPaintPropertyNode::renderingContextID.
+        unsigned renderingContextID = 0;
         // The clip node describes the accumulated raster clip for the current subtree.
         // Note that the computed raster region in canvas space for a clip node is independent from
         // the transform and paint offset above. Also the actual raster region may be affected
diff --git a/third_party/WebKit/Source/core/paint/PaintPropertyTreeBuilderTest.cpp b/third_party/WebKit/Source/core/paint/PaintPropertyTreeBuilderTest.cpp
index 9979fdd..d395791 100644
--- a/third_party/WebKit/Source/core/paint/PaintPropertyTreeBuilderTest.cpp
+++ b/third_party/WebKit/Source/core/paint/PaintPropertyTreeBuilderTest.cpp
@@ -1109,6 +1109,176 @@
     EXPECT_EQ(svgWithTransformProperties->transform(), rectWithTransformProperties->transform()->parent());
 }
 
+TEST_F(PaintPropertyTreeBuilderTest, NoRenderingContextByDefault)
+{
+    setBodyInnerHTML("<div style=\"transform: translateZ(0)\"></div>");
+
+    ObjectPaintProperties* properties = document().body()->firstChild()->layoutObject()->objectPaintProperties();
+    ASSERT_TRUE(properties->transform());
+    EXPECT_FALSE(properties->transform()->hasRenderingContext());
+}
+
+TEST_F(PaintPropertyTreeBuilderTest, Preserve3DCreatesSharedRenderingContext)
+{
+    setBodyInnerHTML(
+        "<div style=\"transform-style: preserve-3d\">"
+        "  <div id=\"a\" style=\"transform: translateZ(0)\"></div>"
+        "  <div id=\"b\" style=\"transform: translateZ(0)\"></div>"
+        "</div>");
+
+    ObjectPaintProperties* aProperties = document().getElementById("a")->layoutObject()->objectPaintProperties();
+    ObjectPaintProperties* bProperties = document().getElementById("b")->layoutObject()->objectPaintProperties();
+    ASSERT_TRUE(aProperties->transform() && bProperties->transform());
+    EXPECT_NE(aProperties->transform(), bProperties->transform());
+    EXPECT_TRUE(aProperties->transform()->hasRenderingContext());
+    EXPECT_TRUE(bProperties->transform()->hasRenderingContext());
+    EXPECT_EQ(aProperties->transform()->renderingContextID(), bProperties->transform()->renderingContextID());
+}
+
+TEST_F(PaintPropertyTreeBuilderTest, FlatTransformStyleEndsRenderingContext)
+{
+    setBodyInnerHTML(
+        "<div style=\"transform-style: preserve-3d\">"
+        "  <div id=\"a\" style=\"transform: translateZ(0)\">"
+        "    <div id=\"b\" style=\"transform: translateZ(0)\"></div>"
+        "  </div>"
+        "</div>");
+
+    ASSERT_FALSE(document().getElementById("a")->layoutObject()->styleRef().preserves3D());
+    ObjectPaintProperties* aProperties = document().getElementById("a")->layoutObject()->objectPaintProperties();
+    ObjectPaintProperties* bProperties = document().getElementById("b")->layoutObject()->objectPaintProperties();
+    ASSERT_TRUE(aProperties->transform() && bProperties->transform());
+
+    // #a should participate in a rendering context (due to its parent), but its
+    // child #b should not.
+    EXPECT_TRUE(aProperties->transform()->hasRenderingContext());
+    EXPECT_FALSE(bProperties->transform()->hasRenderingContext());
+}
+
+TEST_F(PaintPropertyTreeBuilderTest, NestedRenderingContexts)
+{
+    setBodyInnerHTML(
+        "<div style=\"transform-style: preserve-3d\">"
+        "  <div id=\"a\" style=\"transform: translateZ(0)\">"
+        "    <div style=\"transform-style: preserve-3d\">"
+        "      <div id=\"b\" style=\"transform: translateZ(0)\">"
+        "    </div>"
+        "  </div>"
+        "</div>");
+
+    ASSERT_FALSE(document().getElementById("a")->layoutObject()->styleRef().preserves3D());
+    ObjectPaintProperties* aProperties = document().getElementById("a")->layoutObject()->objectPaintProperties();
+    ObjectPaintProperties* bProperties = document().getElementById("b")->layoutObject()->objectPaintProperties();
+    ASSERT_TRUE(aProperties->transform() && bProperties->transform());
+
+    // #a should participate in a rendering context (due to its parent). Its
+    // child does preserve 3D, but since #a does not, #a's rendering context is
+    // not passed on to its children. Thus #b ends up in a separate rendering
+    // context rooted at its parent.
+    EXPECT_TRUE(aProperties->transform()->hasRenderingContext());
+    EXPECT_TRUE(bProperties->transform()->hasRenderingContext());
+    EXPECT_NE(aProperties->transform()->renderingContextID(), bProperties->transform()->renderingContextID());
+}
+
+// Returns true if the first node has the second as an ancestor.
+static bool nodeHasAncestor(const TransformPaintPropertyNode* node, const TransformPaintPropertyNode* ancestor)
+{
+    while (node) {
+        if (node == ancestor)
+            return true;
+        node = node->parent();
+    }
+    return false;
+}
+
+// Returns true if some node will flatten the transform due to |node| before it
+// is inherited by |node| (including if node->flattensInheritedTransform()).
+static bool someNodeFlattensTransform(const TransformPaintPropertyNode* node, const TransformPaintPropertyNode* ancestor)
+{
+    while (node != ancestor) {
+        if (node->flattensInheritedTransform())
+            return true;
+        node = node->parent();
+    }
+    return false;
+}
+
+TEST_F(PaintPropertyTreeBuilderTest, FlatTransformStylePropagatesToChildren)
+{
+    setBodyInnerHTML(
+        "<div id=\"a\" style=\"transform: translateZ(0); transform-style: flat\">"
+        "  <div id=\"b\" style=\"transform: translateZ(0)\"></div>"
+        "</div>");
+
+    const auto* aTransform = document().getElementById("a")->layoutObject()->objectPaintProperties()->transform();
+    ASSERT_TRUE(aTransform);
+    const auto* bTransform = document().getElementById("b")->layoutObject()->objectPaintProperties()->transform();
+    ASSERT_TRUE(bTransform);
+    ASSERT_TRUE(nodeHasAncestor(bTransform, aTransform));
+
+    // Some node must flatten the inherited transform from #a before it reaches
+    // #b's transform.
+    EXPECT_TRUE(someNodeFlattensTransform(bTransform, aTransform));
+}
+
+TEST_F(PaintPropertyTreeBuilderTest, Preserve3DTransformStylePropagatesToChildren)
+{
+    setBodyInnerHTML(
+        "<div id=\"a\" style=\"transform: translateZ(0); transform-style: preserve-3d\">"
+        "  <div id=\"b\" style=\"transform: translateZ(0)\"></div>"
+        "</div>");
+
+    const auto* aTransform = document().getElementById("a")->layoutObject()->objectPaintProperties()->transform();
+    ASSERT_TRUE(aTransform);
+    const auto* bTransform = document().getElementById("b")->layoutObject()->objectPaintProperties()->transform();
+    ASSERT_TRUE(bTransform);
+    ASSERT_TRUE(nodeHasAncestor(bTransform, aTransform));
+
+    // No node may flatten the inherited transform from #a before it reaches
+    // #b's transform.
+    EXPECT_FALSE(someNodeFlattensTransform(bTransform, aTransform));
+}
+
+TEST_F(PaintPropertyTreeBuilderTest, PerspectiveIsNotFlattened)
+{
+    // It's necessary to make nodes from the one that applies perspective to
+    // ones that combine with it preserve 3D. Otherwise, the perspective doesn't
+    // do anything.
+    setBodyInnerHTML(
+        "<div id=\"a\" style=\"perspective: 800px\">"
+        "  <div id=\"b\" style=\"transform: translateZ(0)\"></div>"
+        "</div>");
+
+    ObjectPaintProperties* aProperties = document().getElementById("a")->layoutObject()->objectPaintProperties();
+    ObjectPaintProperties* bProperties = document().getElementById("b")->layoutObject()->objectPaintProperties();
+    const TransformPaintPropertyNode* aPerspective = aProperties->perspective();
+    ASSERT_TRUE(aPerspective);
+    const TransformPaintPropertyNode* bTransform = bProperties->transform();
+    ASSERT_TRUE(bTransform);
+    ASSERT_TRUE(nodeHasAncestor(bTransform, aPerspective));
+    EXPECT_FALSE(someNodeFlattensTransform(bTransform, aPerspective));
+}
+
+TEST_F(PaintPropertyTreeBuilderTest, PerspectiveDoesNotEstablishRenderingContext)
+{
+    // It's necessary to make nodes from the one that applies perspective to
+    // ones that combine with it preserve 3D. Otherwise, the perspective doesn't
+    // do anything.
+    setBodyInnerHTML(
+        "<div id=\"a\" style=\"perspective: 800px\">"
+        "  <div id=\"b\" style=\"transform: translateZ(0)\"></div>"
+        "</div>");
+
+    ObjectPaintProperties* aProperties = document().getElementById("a")->layoutObject()->objectPaintProperties();
+    ObjectPaintProperties* bProperties = document().getElementById("b")->layoutObject()->objectPaintProperties();
+    const TransformPaintPropertyNode* aPerspective = aProperties->perspective();
+    ASSERT_TRUE(aPerspective);
+    EXPECT_FALSE(aPerspective->hasRenderingContext());
+    const TransformPaintPropertyNode* bTransform = bProperties->transform();
+    ASSERT_TRUE(bTransform);
+    EXPECT_FALSE(bTransform->hasRenderingContext());
+}
+
 TEST_F(PaintPropertyTreeBuilderTest, CachedProperties)
 {
     setBodyInnerHTML(
diff --git a/third_party/WebKit/Source/platform/graphics/compositing/PaintArtifactCompositor.cpp b/third_party/WebKit/Source/platform/graphics/compositing/PaintArtifactCompositor.cpp
index 5e552c2b..da3c470 100644
--- a/third_party/WebKit/Source/platform/graphics/compositing/PaintArtifactCompositor.cpp
+++ b/third_party/WebKit/Source/platform/graphics/compositing/PaintArtifactCompositor.cpp
@@ -415,6 +415,8 @@
     compositorNode.post_local.matrix().setTranslate(
         origin.x(), origin.y(), origin.z());
     compositorNode.needs_local_transform_update = true;
+    compositorNode.flattens_inherited_transform = transformNode->flattensInheritedTransform();
+    compositorNode.sorting_context_id = transformNode->renderingContextID();
 
     m_rootLayer->AddChild(dummyLayer);
     dummyLayer->SetTransformTreeIndex(id);
@@ -585,6 +587,10 @@
         layer->SetEffectTreeIndex(effectId);
         layer->SetScrollTreeIndex(kRealRootNodeId);
 
+        // TODO(jbroman): This probably shouldn't be necessary, but it is still
+        // queried by RenderSurfaceImpl.
+        layer->Set3dSortingContextId(host->property_trees()->transform_tree.Node(transformId)->sorting_context_id);
+
         if (m_extraDataForTestingEnabled)
             m_extraDataForTesting->contentLayers.append(layer);
     }
diff --git a/third_party/WebKit/Source/platform/graphics/compositing/PaintArtifactCompositorTest.cpp b/third_party/WebKit/Source/platform/graphics/compositing/PaintArtifactCompositorTest.cpp
index 3e51ec8..9ac8a06c 100644
--- a/third_party/WebKit/Source/platform/graphics/compositing/PaintArtifactCompositorTest.cpp
+++ b/third_party/WebKit/Source/platform/graphics/compositing/PaintArtifactCompositorTest.cpp
@@ -8,10 +8,12 @@
 #include "base/threading/thread_task_runner_handle.h"
 #include "cc/layers/layer.h"
 #include "cc/test/fake_output_surface.h"
+#include "cc/test/geometry_test_utils.h"
 #include "cc/trees/clip_node.h"
 #include "cc/trees/effect_node.h"
 #include "cc/trees/layer_tree_host.h"
 #include "cc/trees/layer_tree_settings.h"
+#include "cc/trees/transform_node.h"
 #include "platform/RuntimeEnabledFeatures.h"
 #include "platform/graphics/paint/EffectPaintPropertyNode.h"
 #include "platform/graphics/paint/PaintArtifact.h"
@@ -416,6 +418,11 @@
         return *m_webLayerTreeView->layerTreeHost()->property_trees();
     }
 
+    const cc::TransformNode& transformNode(const cc::Layer* layer)
+    {
+        return *propertyTrees().transform_tree.Node(layer->transform_tree_index());
+    }
+
     void update(const PaintArtifact& artifact)
     {
         PaintArtifactCompositorTest::update(artifact);
@@ -527,6 +534,119 @@
         contentLayerAt(1)->transform_tree_index());
 }
 
+TEST_F(PaintArtifactCompositorTestWithPropertyTrees, FlattensInheritedTransform)
+{
+    for (bool transformIsFlattened : { true, false }) {
+        SCOPED_TRACE(transformIsFlattened);
+
+        // The flattens_inherited_transform bit corresponds to whether the _parent_
+        // transform node flattens the transform. This is because Blink's notion of
+        // flattening determines whether content within the node's local transform
+        // is flattened, while cc's notion applies in the parent's coordinate space.
+        RefPtr<TransformPaintPropertyNode> transform1 = TransformPaintPropertyNode::create(
+            nullptr, TransformationMatrix(), FloatPoint3D());
+        RefPtr<TransformPaintPropertyNode> transform2 = TransformPaintPropertyNode::create(
+            transform1, TransformationMatrix().rotate3d(0, 45, 0), FloatPoint3D());
+        RefPtr<TransformPaintPropertyNode> transform3 = TransformPaintPropertyNode::create(
+            transform2, TransformationMatrix().rotate3d(0, 45, 0), FloatPoint3D(),
+            transformIsFlattened);
+
+        TestPaintArtifact artifact;
+        artifact.chunk(transform3, nullptr, nullptr)
+            .rectDrawing(FloatRect(0, 0, 300, 200), Color::white);
+        update(artifact.build());
+
+        ASSERT_EQ(1u, contentLayerCount());
+        const cc::Layer* layer = contentLayerAt(0);
+        EXPECT_THAT(layer->GetPicture(),
+            Pointee(drawsRectangle(FloatRect(0, 0, 300, 200), Color::white)));
+
+        // The leaf transform node should flatten its inherited transform node
+        // if and only if the intermediate rotation transform in the Blink tree
+        // flattens.
+        const cc::TransformNode* transformNode3 = propertyTrees().transform_tree.Node(layer->transform_tree_index());
+        EXPECT_EQ(transformIsFlattened, transformNode3->flattens_inherited_transform);
+
+        // Given this, we should expect the correct screen space transform for
+        // each case. If the transform was flattened, we should see it getting
+        // an effective horizontal scale of 1/sqrt(2) each time, thus it gets
+        // half as wide. If the transform was not flattened, we should see an
+        // empty rectangle (as the total 90 degree rotation makes it
+        // perpendicular to the viewport).
+        gfx::RectF rect(0, 0, 100, 100);
+        layer->screen_space_transform().TransformRect(&rect);
+        if (transformIsFlattened)
+            EXPECT_FLOAT_RECT_EQ(gfx::RectF(0, 0, 50, 100), rect);
+        else
+            EXPECT_TRUE(rect.IsEmpty());
+    }
+}
+
+TEST_F(PaintArtifactCompositorTestWithPropertyTrees, SortingContextID)
+{
+    // Has no 3D rendering context.
+    RefPtr<TransformPaintPropertyNode> transform1 = TransformPaintPropertyNode::create(
+        nullptr, TransformationMatrix(), FloatPoint3D());
+    // Establishes a 3D rendering context.
+    RefPtr<TransformPaintPropertyNode> transform2 = TransformPaintPropertyNode::create(
+        transform1, TransformationMatrix(), FloatPoint3D(), false, 1);
+    // Extends the 3D rendering context of transform2.
+    RefPtr<TransformPaintPropertyNode> transform3 = TransformPaintPropertyNode::create(
+        transform2, TransformationMatrix(), FloatPoint3D(), false, 1);
+    // Establishes a 3D rendering context distinct from transform2.
+    RefPtr<TransformPaintPropertyNode> transform4 = TransformPaintPropertyNode::create(
+        transform2, TransformationMatrix(), FloatPoint3D(), false, 2);
+
+    TestPaintArtifact artifact;
+    artifact.chunk(transform1, nullptr, dummyRootEffect())
+        .rectDrawing(FloatRect(0, 0, 300, 200), Color::white);
+    artifact.chunk(transform2, nullptr, dummyRootEffect())
+        .rectDrawing(FloatRect(0, 0, 300, 200), Color::lightGray);
+    artifact.chunk(transform3, nullptr, dummyRootEffect())
+        .rectDrawing(FloatRect(0, 0, 300, 200), Color::darkGray);
+    artifact.chunk(transform4, nullptr, dummyRootEffect())
+        .rectDrawing(FloatRect(0, 0, 300, 200), Color::black);
+    update(artifact.build());
+
+    ASSERT_EQ(4u, contentLayerCount());
+
+    // The white layer is not 3D sorted.
+    const cc::Layer* whiteLayer = contentLayerAt(0);
+    EXPECT_THAT(whiteLayer->GetPicture(),
+        Pointee(drawsRectangle(FloatRect(0, 0, 300, 200), Color::white)));
+    int whiteSortingContextId = transformNode(whiteLayer).sorting_context_id;
+    EXPECT_EQ(whiteLayer->sorting_context_id(), whiteSortingContextId);
+    EXPECT_EQ(0, whiteSortingContextId);
+
+    // The light gray layer is 3D sorted.
+    const cc::Layer* lightGrayLayer = contentLayerAt(1);
+    EXPECT_THAT(lightGrayLayer->GetPicture(),
+        Pointee(drawsRectangle(FloatRect(0, 0, 300, 200), Color::lightGray)));
+    int lightGraySortingContextId = transformNode(lightGrayLayer).sorting_context_id;
+    EXPECT_EQ(lightGrayLayer->sorting_context_id(), lightGraySortingContextId);
+    EXPECT_NE(0, lightGraySortingContextId);
+
+    // The dark gray layer is 3D sorted with the light gray layer, but has a
+    // separate transform node.
+    const cc::Layer* darkGrayLayer = contentLayerAt(2);
+    EXPECT_THAT(darkGrayLayer->GetPicture(),
+        Pointee(drawsRectangle(FloatRect(0, 0, 300, 200), Color::darkGray)));
+    int darkGraySortingContextId = transformNode(darkGrayLayer).sorting_context_id;
+    EXPECT_EQ(darkGrayLayer->sorting_context_id(), darkGraySortingContextId);
+    EXPECT_EQ(lightGraySortingContextId, darkGraySortingContextId);
+    EXPECT_NE(lightGrayLayer->transform_tree_index(), darkGrayLayer->transform_tree_index());
+
+    // The black layer is 3D sorted, but in a separate context from the previous
+    // layers.
+    const cc::Layer* blackLayer = contentLayerAt(3);
+    EXPECT_THAT(blackLayer->GetPicture(),
+        Pointee(drawsRectangle(FloatRect(0, 0, 300, 200), Color::black)));
+    int blackSortingContextId = transformNode(blackLayer).sorting_context_id;
+    EXPECT_EQ(blackLayer->sorting_context_id(), blackSortingContextId);
+    EXPECT_NE(0, blackSortingContextId);
+    EXPECT_NE(lightGraySortingContextId, blackSortingContextId);
+}
+
 TEST_F(PaintArtifactCompositorTestWithPropertyTrees, OneClip)
 {
     RefPtr<ClipPaintPropertyNode> clip = ClipPaintPropertyNode::create(
diff --git a/third_party/WebKit/Source/platform/graphics/paint/README.md b/third_party/WebKit/Source/platform/graphics/paint/README.md
index 009a752..1bcf259 100644
--- a/third_party/WebKit/Source/platform/graphics/paint/README.md
+++ b/third_party/WebKit/Source/platform/graphics/paint/README.md
@@ -53,6 +53,12 @@
   transform origin will rotate the plane about that point)
 * a pointer to the parent node, which defines the coordinate space relative to
   which the above should be interpreted
+* a boolean indicating whether the transform should be projected into the plane
+  of its parent (i.e., whether the total transform inherited from its parent
+  should be flattened before this node's transform is applied and propagated to
+  children)
+* an integer rendering context ID; content whose transform nodes share a
+  rendering context ID should sort together
 
 The parent node pointers link the transform nodes in a hierarchy (the *transform
 tree*), which defines how the transform for any painted content can be
@@ -66,9 +72,11 @@
 order to create the illusion of depth.
 ***
 
-*** aside
-TODO(jbroman): Explain flattening, etc., once it exists in the paint properties.
-***
+Note that, even though CSS does not permit it in the DOM, the transform tree can
+have nodes whose children do not flatten their inherited transform and
+participate in no 3D rendering context. For example, not flattening is necessary
+to preserve the 3D character of the perspective transform, but this does not
+imply any 3D sorting.
 
 ### Clips
 
diff --git a/third_party/WebKit/Source/platform/graphics/paint/TransformPaintPropertyNode.h b/third_party/WebKit/Source/platform/graphics/paint/TransformPaintPropertyNode.h
index 1481e60..1020a207 100644
--- a/third_party/WebKit/Source/platform/graphics/paint/TransformPaintPropertyNode.h
+++ b/third_party/WebKit/Source/platform/graphics/paint/TransformPaintPropertyNode.h
@@ -21,16 +21,23 @@
 // for the root.
 class PLATFORM_EXPORT TransformPaintPropertyNode : public RefCounted<TransformPaintPropertyNode> {
 public:
-    static PassRefPtr<TransformPaintPropertyNode> create(PassRefPtr<TransformPaintPropertyNode> parent, const TransformationMatrix& matrix, const FloatPoint3D& origin)
+    static PassRefPtr<TransformPaintPropertyNode> create(
+        PassRefPtr<TransformPaintPropertyNode> parent,
+        const TransformationMatrix& matrix,
+        const FloatPoint3D& origin,
+        bool flattensInheritedTransform = false,
+        unsigned renderingContextID = 0)
     {
-        return adoptRef(new TransformPaintPropertyNode(parent, matrix, origin));
+        return adoptRef(new TransformPaintPropertyNode(matrix, origin, parent, flattensInheritedTransform, renderingContextID));
     }
 
-    void update(PassRefPtr<TransformPaintPropertyNode> parent, const TransformationMatrix& matrix, const FloatPoint3D& origin)
+    void update(PassRefPtr<TransformPaintPropertyNode> parent, const TransformationMatrix& matrix, const FloatPoint3D& origin, bool flattensInheritedTransform = false, unsigned renderingContextID = 0)
     {
         m_parent = parent;
         m_matrix = matrix;
         m_origin = origin;
+        m_flattensInheritedTransform = flattensInheritedTransform;
+        m_renderingContextID = renderingContextID;
     }
 
     const TransformationMatrix& matrix() const { return m_matrix; }
@@ -40,13 +47,36 @@
     // is the root transform.
     TransformPaintPropertyNode* parent() const { return m_parent.get(); }
 
-private:
-    TransformPaintPropertyNode(PassRefPtr<TransformPaintPropertyNode> parent, const TransformationMatrix& matrix, const FloatPoint3D& origin)
-        : m_parent(parent), m_matrix(matrix), m_origin(origin) { }
+    // If true, content with this transform node (or its descendant) appears in
+    // the plane of its parent. This is implemented by flattening the total
+    // accumulated transform from its ancestors.
+    bool flattensInheritedTransform() const { return m_flattensInheritedTransform; }
 
-    RefPtr<TransformPaintPropertyNode> m_parent;
+    // Content whose transform nodes have a common rendering context ID are 3D
+    // sorted. If this is 0, content will not be 3D sorted.
+    unsigned renderingContextID() const { return m_renderingContextID; }
+    bool hasRenderingContext() const { return m_renderingContextID; }
+
+private:
+    TransformPaintPropertyNode(
+        const TransformationMatrix& matrix,
+        const FloatPoint3D& origin,
+        PassRefPtr<TransformPaintPropertyNode> parent,
+        bool flattensInheritedTransform,
+        unsigned renderingContextID)
+        : m_matrix(matrix)
+        , m_origin(origin)
+        , m_parent(parent)
+        , m_flattensInheritedTransform(flattensInheritedTransform)
+        , m_renderingContextID(renderingContextID)
+    {
+    }
+
     TransformationMatrix m_matrix;
     FloatPoint3D m_origin;
+    RefPtr<TransformPaintPropertyNode> m_parent;
+    bool m_flattensInheritedTransform;
+    unsigned m_renderingContextID;
 };
 
 // Redeclared here to avoid ODR issues.
diff --git a/tools/perf/benchmarks/system_health.py b/tools/perf/benchmarks/system_health.py
index d7b080a7..a4f8dfb 100644
--- a/tools/perf/benchmarks/system_health.py
+++ b/tools/perf/benchmarks/system_health.py
@@ -83,6 +83,10 @@
                                           take_memory_measurement=True)
 
   @classmethod
+  def ShouldTearDownStateAfterEachStoryRun(cls):
+    return True
+
+  @classmethod
   def Name(cls):
     return 'system_health.memory_%s' % cls.PLATFORM