diff --git a/DEPS b/DEPS
index 4d9810f..fb23eca 100644
--- a/DEPS
+++ b/DEPS
@@ -39,7 +39,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': '6950de6c4166fabb35e6c756fc009e0cf1c47819',
+  'skia_revision': 'f514eae53227bcd9f5125cd4479942644f0d4c84',
   # 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/base/trace_event/malloc_dump_provider.h b/base/trace_event/malloc_dump_provider.h
index f463d0a6..63fc1b0 100644
--- a/base/trace_event/malloc_dump_provider.h
+++ b/base/trace_event/malloc_dump_provider.h
@@ -13,8 +13,8 @@
 #include "build/build_config.h"
 
 #if defined(OS_LINUX) || defined(OS_ANDROID) || \
-    (defined(OS_MACOXS) && !defined(OS_IOS))
-#define SUPPORTS_MALLOC_MEMORY_TRACING
+    (defined(OS_MACOSX) && !defined(OS_IOS))
+#define MALLOC_MEMORY_TRACING_SUPPORTED
 #endif
 
 namespace base {
diff --git a/base/trace_event/memory_dump_manager.cc b/base/trace_event/memory_dump_manager.cc
index 43c784b..19bdcc4 100644
--- a/base/trace_event/memory_dump_manager.cc
+++ b/base/trace_event/memory_dump_manager.cc
@@ -96,7 +96,7 @@
 
 // static
 const char* const MemoryDumpManager::kSystemAllocatorPoolName =
-#if defined(SUPPORTS_MALLOC_MEMORY_TRACING)
+#if defined(MALLOC_MEMORY_TRACING_SUPPORTED)
     MallocDumpProvider::kAllocatedObjects;
 #elif defined(OS_WIN)
     WinHeapDumpProvider::kAllocatedObjects;
@@ -155,7 +155,7 @@
                        "ProcessMemoryTotals", nullptr);
 #endif
 
-#if defined(SUPPORTS_MALLOC_MEMORY_TRACING)
+#if defined(MALLOC_MEMORY_TRACING_SUPPORTED)
   RegisterDumpProvider(MallocDumpProvider::GetInstance(), "Malloc", nullptr);
 #endif
 
diff --git a/chrome/VERSION b/chrome/VERSION
index dce00fe8..636f02d4 100644
--- a/chrome/VERSION
+++ b/chrome/VERSION
@@ -1,4 +1,4 @@
 MAJOR=48
 MINOR=0
-BUILD=2558
+BUILD=2559
 PATCH=0
diff --git a/chromeos/CHROMEOS_LKGM b/chromeos/CHROMEOS_LKGM
index 4fe43d0de..03056240 100644
--- a/chromeos/CHROMEOS_LKGM
+++ b/chromeos/CHROMEOS_LKGM
@@ -1 +1 @@
-7618.0.0
\ No newline at end of file
+7621.0.0
\ No newline at end of file
diff --git a/net/cert/ct_log_response_parser.cc b/net/cert/ct_log_response_parser.cc
index 0069fc70..0c006cc 100644
--- a/net/cert/ct_log_response_parser.cc
+++ b/net/cert/ct_log_response_parser.cc
@@ -7,6 +7,7 @@
 #include "base/base64.h"
 #include "base/json/json_value_converter.h"
 #include "base/logging.h"
+#include "base/memory/scoped_vector.h"
 #include "base/strings/string_piece.h"
 #include "base/time/time.h"
 #include "base/values.h"
@@ -101,6 +102,33 @@
   return true;
 }
 
+// Structure for making JSON decoding easier. The string fields
+// are base64-encoded so will require further decoding.
+struct JsonConsistencyProof {
+  ScopedVector<std::string> proof_nodes;
+
+  static void RegisterJSONConverter(
+      base::JSONValueConverter<JsonConsistencyProof>* converter);
+};
+
+bool ConvertIndividualProofNode(const base::Value* value, std::string* result) {
+  std::string b64_encoded_node;
+  if (!value->GetAsString(&b64_encoded_node))
+    return false;
+
+  if (!ConvertSHA256RootHash(b64_encoded_node, result))
+    return false;
+
+  return true;
+}
+
+void JsonConsistencyProof::RegisterJSONConverter(
+    base::JSONValueConverter<JsonConsistencyProof>* converter) {
+  converter->RegisterRepeatedCustomValue<std::string>(
+      "consistency", &JsonConsistencyProof::proof_nodes,
+      &ConvertIndividualProofNode);
+}
+
 }  // namespace
 
 bool FillSignedTreeHead(const base::Value& json_signed_tree_head,
@@ -127,6 +155,23 @@
   return true;
 }
 
+bool FillConsistencyProof(const base::Value& json_consistency_proof,
+                          std::vector<std::string>* consistency_proof) {
+  JsonConsistencyProof parsed_proof;
+  base::JSONValueConverter<JsonConsistencyProof> converter;
+  if (!converter.Convert(json_consistency_proof, &parsed_proof)) {
+    DVLOG(1) << "Invalid consistency proof.";
+    return false;
+  }
+
+  consistency_proof->reserve(parsed_proof.proof_nodes.size());
+  for (std::string* proof_node : parsed_proof.proof_nodes) {
+    consistency_proof->push_back(*proof_node);
+  }
+
+  return true;
+}
+
 }  // namespace ct
 
 }  // namespace net
diff --git a/net/cert/ct_log_response_parser.h b/net/cert/ct_log_response_parser.h
index b7a012b..403f8bfb 100644
--- a/net/cert/ct_log_response_parser.h
+++ b/net/cert/ct_log_response_parser.h
@@ -5,6 +5,9 @@
 #ifndef NET_CERT_CT_LOG_RESPONSE_PARSER_H_
 #define NET_CERT_CT_LOG_RESPONSE_PARSER_H_
 
+#include <string>
+#include <vector>
+
 #include "base/strings/string_piece.h"
 #include "net/base/net_export.h"
 
@@ -24,6 +27,10 @@
 NET_EXPORT bool FillSignedTreeHead(const base::Value& json_signed_tree_head,
                                    SignedTreeHead* signed_tree_head);
 
+NET_EXPORT bool FillConsistencyProof(
+    const base::Value& json_signed_tree_head,
+    std::vector<std::string>* consistency_proof);
+
 }  // namespace ct
 
 }  // namespace net
diff --git a/net/cert/ct_log_response_parser_unittest.cc b/net/cert/ct_log_response_parser_unittest.cc
index 9f02699..d2c192e7 100644
--- a/net/cert/ct_log_response_parser_unittest.cc
+++ b/net/cert/ct_log_response_parser_unittest.cc
@@ -95,6 +95,47 @@
   ASSERT_FALSE(FillSignedTreeHead(*too_short_hash_json.get(), &tree_head));
 }
 
+TEST(CTLogResponseParserTest, ParsesConsistencyProofSuccessfully) {
+  std::string first(32, 'a');
+  std::string second(32, 'b');
+  std::string third(32, 'c');
+
+  std::vector<std::string> raw_nodes;
+  raw_nodes.push_back(first);
+  raw_nodes.push_back(second);
+  raw_nodes.push_back(third);
+  scoped_ptr<base::Value> sample_consistency_proof =
+      ParseJson(CreateConsistencyProofJsonString(raw_nodes));
+
+  std::vector<std::string> output;
+
+  ASSERT_TRUE(FillConsistencyProof(*sample_consistency_proof.get(), &output));
+
+  EXPECT_EQ(output[0], first);
+  EXPECT_EQ(output[1], second);
+  EXPECT_EQ(output[2], third);
+}
+
+TEST(CTLogResponseParserTest, FailsOnInvalidProofJson) {
+  std::vector<std::string> output;
+
+  scoped_ptr<base::Value> badly_encoded =
+      ParseJson(std::string("{\"consistency\": [\"notbase64\"]}"));
+  EXPECT_FALSE(FillConsistencyProof(*badly_encoded.get(), &output));
+
+  scoped_ptr<base::Value> not_a_string =
+      ParseJson(std::string("{\"consistency\": [42, 16]}"));
+  EXPECT_FALSE(FillConsistencyProof(*badly_encoded.get(), &output));
+}
+
+TEST(CTLogResponseParserTest, ParsesProofJsonWithExtraFields) {
+  std::vector<std::string> output;
+
+  scoped_ptr<base::Value> badly_encoded =
+      ParseJson(std::string("{\"consistency\": [], \"somethingelse\": 3}"));
+  EXPECT_TRUE(FillConsistencyProof(*badly_encoded.get(), &output));
+}
+
 }  // namespace ct
 
 }  // namespace net
diff --git a/net/test/ct_test_util.cc b/net/test/ct_test_util.cc
index 65a8122..3bd30ff4 100644
--- a/net/test/ct_test_util.cc
+++ b/net/test/ct_test_util.cc
@@ -312,6 +312,23 @@
   return sth_json;
 }
 
+std::string CreateConsistencyProofJsonString(
+    const std::vector<std::string>& raw_nodes) {
+  std::string consistency_proof_json = std::string("{\"consistency\":[");
+
+  for (auto it = raw_nodes.begin(); it != raw_nodes.end(); ++it) {
+    std::string proof_node_b64;
+    base::Base64Encode(*it, &proof_node_b64);
+    consistency_proof_json +=
+        base::StringPrintf("\"%s\"", proof_node_b64.c_str());
+    if (it + 1 != raw_nodes.end())
+      consistency_proof_json += std::string(",");
+  }
+  consistency_proof_json += std::string("]}");
+
+  return consistency_proof_json;
+}
+
 }  // namespace ct
 
 }  // namespace net
diff --git a/net/test/ct_test_util.h b/net/test/ct_test_util.h
index 5701a288..5bb2d7a 100644
--- a/net/test/ct_test_util.h
+++ b/net/test/ct_test_util.h
@@ -6,6 +6,7 @@
 #define NET_CERT_CT_TEST_UTIL_H_
 
 #include <string>
+#include <vector>
 
 #include "base/memory/ref_counted.h"
 
@@ -86,6 +87,10 @@
                                            std::string sha256_root_hash,
                                            std::string tree_head_signature);
 
+// Assembles, and returns, a sample consistency proof in JSON format using
+// the provided raw nodes (i.e. the raw nodes will be base64-encoded).
+std::string CreateConsistencyProofJsonString(
+    const std::vector<std::string>& raw_nodes);
 }  // namespace ct
 
 }  // namespace net
diff --git a/third_party/WebKit/Source/core/css/invalidation/StyleInvalidator.cpp b/third_party/WebKit/Source/core/css/invalidation/StyleInvalidator.cpp
index 3cff622e..db5fa17 100644
--- a/third_party/WebKit/Source/core/css/invalidation/StyleInvalidator.cpp
+++ b/third_party/WebKit/Source/core/css/invalidation/StyleInvalidator.cpp
@@ -147,7 +147,7 @@
     m_invalidationEntries.append(Entry(&invalidationSet, invalidationLimit));
 }
 
-ALWAYS_INLINE bool StyleInvalidator::SiblingData::matchCurrentInvalidationSets(Element& element, RecursionData& recursionData)
+bool StyleInvalidator::SiblingData::matchCurrentInvalidationSets(Element& element, RecursionData& recursionData) const
 {
     bool thisElementNeedsStyleRecalc = false;
     ASSERT(!recursionData.wholeSubtreeInvalid());
@@ -155,6 +155,7 @@
     unsigned index = 0;
     while (index < m_invalidationEntries.size()) {
         if (m_elementIndex > m_invalidationEntries[index].m_invalidationLimit) {
+            // m_invalidationEntries[index] only applies to earlier siblings. Remove it.
             m_invalidationEntries[index] = m_invalidationEntries.last();
             m_invalidationEntries.removeLast();
             continue;
@@ -186,6 +187,26 @@
     return thisElementNeedsStyleRecalc;
 }
 
+void StyleInvalidator::pushInvalidationSetsForElement(Element& element, RecursionData& recursionData, SiblingData& siblingData)
+{
+    PendingInvalidations* pendingInvalidations = m_pendingInvalidationMap.get(&element);
+    ASSERT(pendingInvalidations);
+
+    for (const auto& invalidationSet : pendingInvalidations->siblings())
+        siblingData.pushInvalidationSet(toSiblingInvalidationSet(*invalidationSet));
+
+    if (!pendingInvalidations->descendants().isEmpty()) {
+        for (const auto& invalidationSet : pendingInvalidations->descendants())
+            recursionData.pushInvalidationSet(toDescendantInvalidationSet(*invalidationSet));
+        if (UNLIKELY(*s_tracingEnabled)) {
+            TRACE_EVENT_INSTANT1(TRACE_DISABLED_BY_DEFAULT("devtools.timeline.invalidationTracking"),
+                "StyleInvalidatorInvalidationTracking",
+                TRACE_EVENT_SCOPE_THREAD,
+                "data", InspectorStyleInvalidatorInvalidateEvent::invalidationList(element, pendingInvalidations->descendants()));
+        }
+    }
+}
+
 ALWAYS_INLINE bool StyleInvalidator::checkInvalidationSetsAgainstElement(Element& element, RecursionData& recursionData, SiblingData& siblingData)
 {
     if (element.styleChangeType() >= SubtreeStyleChange || recursionData.wholeSubtreeInvalid()) {
@@ -194,36 +215,21 @@
     }
 
     bool thisElementNeedsStyleRecalc = recursionData.matchesCurrentInvalidationSets(element);
-    thisElementNeedsStyleRecalc |= siblingData.matchCurrentInvalidationSets(element, recursionData);
+    if (UNLIKELY(!siblingData.isEmpty()))
+        thisElementNeedsStyleRecalc |= siblingData.matchCurrentInvalidationSets(element, recursionData);
 
-    if (UNLIKELY(element.needsStyleInvalidation())) {
-        PendingInvalidations* pendingInvalidations = m_pendingInvalidationMap.get(&element);
-        ASSERT(pendingInvalidations);
-
-        for (const auto& invalidationSet : pendingInvalidations->siblings())
-            siblingData.pushInvalidationSet(toSiblingInvalidationSet(*invalidationSet));
-
-        if (!pendingInvalidations->descendants().isEmpty()) {
-            for (const auto& invalidationSet : pendingInvalidations->descendants())
-                recursionData.pushInvalidationSet(toDescendantInvalidationSet(*invalidationSet));
-            if (UNLIKELY(*s_tracingEnabled)) {
-                TRACE_EVENT_INSTANT1(TRACE_DISABLED_BY_DEFAULT("devtools.timeline.invalidationTracking"),
-                    "StyleInvalidatorInvalidationTracking",
-                    TRACE_EVENT_SCOPE_THREAD,
-                    "data", InspectorStyleInvalidatorInvalidateEvent::invalidationList(element, pendingInvalidations->descendants()));
-            }
-        }
-    }
+    if (UNLIKELY(element.needsStyleInvalidation()))
+        pushInvalidationSetsForElement(element, recursionData, siblingData);
     return thisElementNeedsStyleRecalc;
 }
 
-bool StyleInvalidator::invalidateChildren(Element& element, RecursionData& recursionData)
+bool StyleInvalidator::invalidateShadowRootChildren(Element& element, RecursionData& recursionData)
 {
-    SiblingData siblingData;
     bool someChildrenNeedStyleRecalc = false;
     for (ShadowRoot* root = element.youngestShadowRoot(); root; root = root->olderShadowRoot()) {
         if (!recursionData.treeBoundaryCrossing() && !root->childNeedsStyleInvalidation() && !root->needsStyleInvalidation())
             continue;
+        SiblingData siblingData;
         for (Element* child = ElementTraversal::firstChild(*root); child; child = ElementTraversal::nextSibling(*child)) {
             bool childRecalced = invalidate(*child, recursionData, siblingData);
             someChildrenNeedStyleRecalc = someChildrenNeedStyleRecalc || childRecalced;
@@ -231,6 +237,17 @@
         root->clearChildNeedsStyleInvalidation();
         root->clearNeedsStyleInvalidation();
     }
+    return someChildrenNeedStyleRecalc;
+}
+
+bool StyleInvalidator::invalidateChildren(Element& element, RecursionData& recursionData)
+{
+    SiblingData siblingData;
+    bool someChildrenNeedStyleRecalc = false;
+    if (UNLIKELY(!!element.youngestShadowRoot())) {
+        someChildrenNeedStyleRecalc = invalidateShadowRootChildren(element, recursionData);
+    }
+
     for (Element* child = ElementTraversal::firstChild(element); child; child = ElementTraversal::nextSibling(*child)) {
         bool childRecalced = invalidate(*child, recursionData, siblingData);
         someChildrenNeedStyleRecalc = someChildrenNeedStyleRecalc || childRecalced;
@@ -240,8 +257,8 @@
 
 bool StyleInvalidator::invalidate(Element& element, RecursionData& recursionData, SiblingData& siblingData)
 {
-    RecursionCheckpoint checkpoint(&recursionData);
     siblingData.advance();
+    RecursionCheckpoint checkpoint(&recursionData);
 
     bool thisElementNeedsStyleRecalc = checkInvalidationSetsAgainstElement(element, recursionData, siblingData);
 
diff --git a/third_party/WebKit/Source/core/css/invalidation/StyleInvalidator.h b/third_party/WebKit/Source/core/css/invalidation/StyleInvalidator.h
index f99f005..dabe953 100644
--- a/third_party/WebKit/Source/core/css/invalidation/StyleInvalidator.h
+++ b/third_party/WebKit/Source/core/css/invalidation/StyleInvalidator.h
@@ -63,8 +63,9 @@
         { }
 
         void pushInvalidationSet(const SiblingInvalidationSet&);
-        bool matchCurrentInvalidationSets(Element&, RecursionData&);
+        bool matchCurrentInvalidationSets(Element&, RecursionData&) const;
 
+        bool isEmpty() const { return m_invalidationEntries.isEmpty(); }
         void advance() { m_elementIndex++; }
 
     private:
@@ -79,13 +80,15 @@
             unsigned m_invalidationLimit;
         };
 
-        Vector<Entry, 16> m_invalidationEntries;
+        mutable Vector<Entry, 16> m_invalidationEntries;
         unsigned m_elementIndex;
     };
 
     bool invalidate(Element&, RecursionData&, SiblingData&);
+    bool invalidateShadowRootChildren(Element&, RecursionData&);
     bool invalidateChildren(Element&, RecursionData&);
     bool checkInvalidationSetsAgainstElement(Element&, RecursionData&, SiblingData&);
+    void pushInvalidationSetsForElement(Element&, RecursionData&, SiblingData&);
 
     class RecursionCheckpoint {
     public: