Import Cobalt 9.76656
diff --git a/src/cobalt/browser/application.cc b/src/cobalt/browser/application.cc
index 934cdfb..236fcef 100644
--- a/src/cobalt/browser/application.cc
+++ b/src/cobalt/browser/application.cc
@@ -509,6 +509,10 @@
     memory_tracker_tool_ =
         memory_tracker::CreateMemoryTrackerTool(command_arg);
   }
+
+  if (command_line->HasSwitch(switches::kDisableImageAnimations)) {
+    options.web_module_options.enable_image_animations = false;
+  }
 #endif  // ENABLE_DEBUG_COMMAND_LINE_SWITCHES
 
   if (command_line->HasSwitch(browser::switches::kDisableNavigationWhitelist)) {
diff --git a/src/cobalt/browser/switches.cc b/src/cobalt/browser/switches.cc
index 01647ae..5469a19 100644
--- a/src/cobalt/browser/switches.cc
+++ b/src/cobalt/browser/switches.cc
@@ -110,6 +110,9 @@
 // Enables memory tracking by installing the memory tracker on startup.
 const char kMemoryTracker[] = "memory_tracker";
 
+// Enables/disables animations on animated images (e.g. animated WebP).
+const char kDisableImageAnimations[] = "disable_image_animations";
+
 #endif  // ENABLE_DEBUG_COMMAND_LINE_SWITCHES
 
 // Disables the hard-coded navigation whitelist without disabling any other
diff --git a/src/cobalt/browser/switches.h b/src/cobalt/browser/switches.h
index 1073a3b..29eb721 100644
--- a/src/cobalt/browser/switches.h
+++ b/src/cobalt/browser/switches.h
@@ -45,6 +45,7 @@
 extern const char kVideoDecoderStub[];
 extern const char kWebDriverPort[];
 extern const char kWebDriverListenIp[];
+extern const char kDisableImageAnimations[];
 #endif  // ENABLE_DEBUG_COMMAND_LINE_SWITCHES
 
 extern const char kDisableNavigationWhitelist[];
diff --git a/src/cobalt/browser/web_module.cc b/src/cobalt/browser/web_module.cc
index 79d56ea..b916179 100644
--- a/src/cobalt/browser/web_module.cc
+++ b/src/cobalt/browser/web_module.cc
@@ -526,6 +526,7 @@
                                        base::Unretained(this)),
       data.options.layout_trigger, data.dom_max_element_depth,
       data.layout_refresh_rate, data.network_module->preferred_language(),
+      data.options.enable_image_animations,
       web_module_stat_tracker_->layout_stat_tracker()));
   DCHECK(layout_manager_);
 
@@ -594,8 +595,8 @@
   local_storage_database_.reset();
   mesh_cache_.reset();
   remote_typeface_cache_.reset();
-  animated_image_tracker_.reset();
   image_cache_.reset();
+  animated_image_tracker_.reset();
   fetcher_factory_.reset();
   dom_parser_.reset();
   css_parser_.reset();
@@ -770,6 +771,9 @@
   // Clear out the loader factory's resource provider, possibly aborting any
   // in-progress loads.
   loader_factory_->Suspend();
+
+  // Clear out any currently tracked animating images.
+  animated_image_tracker_->Reset();
 }
 
 void WebModule::Impl::FinishSuspend() {
@@ -865,7 +869,8 @@
       image_cache_capacity_multiplier_when_playing_video(1.0f),
       thread_priority(base::kThreadPriority_Normal),
       loader_thread_priority(base::kThreadPriority_Low),
-      animated_image_decode_thread_priority(base::kThreadPriority_Low) {}
+      animated_image_decode_thread_priority(base::kThreadPriority_Low),
+      enable_image_animations(true) {}
 
 WebModule::WebModule(
     const GURL& initial_url,
diff --git a/src/cobalt/browser/web_module.h b/src/cobalt/browser/web_module.h
index 4bed614..d86bbbc 100644
--- a/src/cobalt/browser/web_module.h
+++ b/src/cobalt/browser/web_module.h
@@ -170,6 +170,10 @@
     scoped_refptr<input::InputPoller> input_poller;
 
     script::JavaScriptEngine::Options javascript_options;
+
+    // Allows image animations to be enabled/disabled.  Its default value
+    // is true to enable them.
+    bool enable_image_animations;
   };
 
   typedef layout::LayoutManager::LayoutResults LayoutResults;
diff --git a/src/cobalt/build/build.id b/src/cobalt/build/build.id
index b145a60..7b38425 100644
--- a/src/cobalt/build/build.id
+++ b/src/cobalt/build/build.id
@@ -1 +1 @@
-70858
\ No newline at end of file
+76656
\ No newline at end of file
diff --git a/src/cobalt/layout/box.cc b/src/cobalt/layout/box.cc
index 269f2d1..77ea1a4 100644
--- a/src/cobalt/layout/box.cc
+++ b/src/cobalt/layout/box.cc
@@ -763,11 +763,27 @@
 
 const ContainerBox* Box::GetStackingContext() const {
   if (!parent_) return AsContainerBox();
-  if (GetZIndex() == 0) {
-    return GetContainingBlock();
+
+  const bool is_positioned = IsPositioned();
+
+  // If the box is an in-flow, non-positioned element, then simply return the
+  // parent as the stacking context.
+  //   https://www.w3.org/TR/CSS21/visuren.html#z-index
+  if (!is_positioned && !IsStackingContext()) {
+    return parent_;
   }
+
+  // If the box's position is not fixed and the z-index is 0, then the nearest
+  // absolute containing block is accepted if it is closer than the nearest
+  // stacking context.
+  bool accept_absolute_containing_block =
+      computed_style()->position() != cssom::KeywordValue::GetFixed() &&
+      GetZIndex() == 0;
+
   ContainerBox* containing_block = parent_;
-  while (!containing_block->IsStackingContext()) {
+  while (!containing_block->IsStackingContext() &&
+         (!accept_absolute_containing_block ||
+          !containing_block->IsContainingBlockForPositionAbsoluteElements())) {
     containing_block = containing_block->parent_;
   }
   return containing_block;
@@ -783,42 +799,63 @@
 }
 
 void Box::UpdateCrossReferencesOfContainerBox(
-    ContainerBox* source_box, bool is_nearest_containing_block,
-    bool is_nearest_absolute_containing_block,
-    bool is_nearest_fixed_containing_block, bool is_nearest_stacking_context) {
-  // Containing blocks and stacking contexts only matter for positioned boxes.
-  if (IsPositioned() || IsTransformed()) {
-    bool is_my_containing_block;
-    // Establish the containing block, as described in
-    // http://www.w3.org/TR/CSS21/visudet.html#containing-block-details.
-    if (computed_style()->position() == cssom::KeywordValue::GetAbsolute()) {
-      // If the element has 'position: absolute', the containing block is
-      // established by the nearest ancestor with a 'position' of 'absolute',
-      // 'relative' or 'fixed'.
-      is_my_containing_block = is_nearest_absolute_containing_block;
-    } else if (computed_style()->position() ==
-               cssom::KeywordValue::GetFixed()) {
+    ContainerBox* source_box, RelationshipToBox nearest_containing_block,
+    RelationshipToBox nearest_absolute_containing_block,
+    RelationshipToBox nearest_fixed_containing_block,
+    RelationshipToBox nearest_stacking_context) {
+  const bool is_positioned = IsPositioned();
+  bool is_position_fixed = false;
+
+  RelationshipToBox my_nearest_containing_block = nearest_containing_block;
+
+  // Establish the containing block, as described in
+  // http://www.w3.org/TR/CSS21/visudet.html#containing-block-details.
+  // Containing blocks only matter for descendant positioned boxes.
+  if (is_positioned) {
+    const scoped_refptr<cssom::PropertyValue>& position_property =
+        computed_style()->position();
+    if (position_property == cssom::KeywordValue::GetFixed()) {
+      is_position_fixed = true;
       // If the element has 'position: fixed', the containing block is
       // established by the viewport in the case of continuous media or the page
       // area in the case of paged media.
-      is_my_containing_block = is_nearest_fixed_containing_block;
-    } else {
-      // If the element's position is "relative" or "static", the containing
-      // block is formed by the content edge of the nearest block container
-      // ancestor box.
-      is_my_containing_block = is_nearest_containing_block;
+      my_nearest_containing_block = nearest_fixed_containing_block;
+    } else if (position_property == cssom::KeywordValue::GetAbsolute()) {
+      // If the element has 'position: absolute', the containing block is
+      // established by the nearest ancestor with a 'position' of 'absolute',
+      // 'relative' or 'fixed'.
+      my_nearest_containing_block = nearest_absolute_containing_block;
     }
+    // Otherwise, the element's position is "relative"; the containing block is
+    // formed by the content edge of the nearest block container ancestor box,
+    // which is the initial value of |my_nearest_containing_block|.
 
-    // If this box has a z_index of zero, then its containing block is its
-    // stacking context. Otherwise, the nearest stacking context is used.
-    bool is_my_stacking_context =
-        GetZIndex() == 0 ? is_my_containing_block : is_nearest_stacking_context;
-
-    if (is_my_containing_block) {
+    if (my_nearest_containing_block == kIsBox) {
       source_box->AddContainingBlockChild(this);
     }
+  }
+
+  // Establish the stacking context, as described in
+  // https://www.w3.org/TR/CSS21/visuren.html#z-index,
+  // https://www.w3.org/TR/css3-color/#transparency, and
+  // https://www.w3.org/TR/css3-transforms/#transform-rendering.
+  // Stacking contexts only matter for descendant positioned boxes and child
+  // stacking contexts.
+  if (is_positioned || IsStackingContext()) {
+    // If the box's position is not fixed and the z-index is 0, then the nearest
+    // box between the absolute containing block and the stacking context is
+    // used; otherwise, the stacking context is always used.
+    bool is_my_stacking_context =
+        !is_position_fixed && GetZIndex() == 0
+            ? ((nearest_absolute_containing_block == kIsBox &&
+                nearest_stacking_context != kIsBoxDescendant) ||
+               (nearest_stacking_context == kIsBox &&
+                nearest_absolute_containing_block != kIsBoxDescendant))
+            : nearest_stacking_context == kIsBox;
+
     if (is_my_stacking_context) {
-      source_box->AddStackingContextChild(this);
+      source_box->AddStackingContextChild(this, my_nearest_containing_block,
+                                          GetZIndex());
     }
   }
 }
diff --git a/src/cobalt/layout/box.h b/src/cobalt/layout/box.h
index 3484227..0d444c9 100644
--- a/src/cobalt/layout/box.h
+++ b/src/cobalt/layout/box.h
@@ -107,6 +107,12 @@
     kInlineLevel,
   };
 
+  enum RelationshipToBox {
+    kIsBoxAncestor,
+    kIsBox,
+    kIsBoxDescendant,
+  };
+
   Box(const scoped_refptr<cssom::CSSComputedStyleDeclaration>&
           css_computed_style_declaration,
       UsedStyleProvider* used_style_provider,
@@ -155,6 +161,14 @@
   //   https://www.w3.org/TR/CSS21/visuren.html#absolutely-positioned
   bool IsAbsolutelyPositioned() const;
 
+  // Returns true if the box serves as a stacking context for descendant
+  // elements. The core stacking context creation criteria is given here
+  // (https://www.w3.org/TR/CSS21/visuren.html#z-index) however it is extended
+  // by various other specification documents such as those describing opacity
+  // (https://www.w3.org/TR/css3-color/#transparency) and transforms
+  // (https://www.w3.org/TR/css3-transforms/#transform-rendering).
+  virtual bool IsStackingContext() const { return false; }
+
   // Updates the size of margin, border, padding, and content boxes. Lays out
   // in-flow descendants, estimates static positions (but not sizes) of
   // out-of-flow descendants. Does not update the position of the box.
@@ -554,9 +568,10 @@
   // the box tree that have it as their containing block or stacking context.
   // This function is called recursively.
   virtual void UpdateCrossReferencesOfContainerBox(
-      ContainerBox* source_box, bool is_nearest_containing_block,
-      bool is_nearest_absolute_containing_block,
-      bool is_nearest_fixed_containing_block, bool is_nearest_stacking_context);
+      ContainerBox* source_box, RelationshipToBox nearest_containing_block,
+      RelationshipToBox nearest_absolute_containing_block,
+      RelationshipToBox nearest_fixed_containing_block,
+      RelationshipToBox nearest_stacking_context);
 
   // Updates the horizontal margins for block level in-flow boxes. This is used
   // for both non-replaced and replaced elements. See
diff --git a/src/cobalt/layout/container_box.cc b/src/cobalt/layout/container_box.cc
index a13d889..8f18e09 100644
--- a/src/cobalt/layout/container_box.cc
+++ b/src/cobalt/layout/container_box.cc
@@ -144,9 +144,6 @@
   InvalidateRenderTreeNodesOfBoxAndAncestors();
 }
 
-// Returns true if the given style allows a container box to act as a containing
-// block for absolutely positioned elements.  For example it will be true if
-// this box's style is itself 'absolute'.
 bool ContainerBox::IsContainingBlockForPositionAbsoluteElements() const {
   return parent() == NULL || IsPositioned() || IsTransformed();
 }
@@ -155,12 +152,6 @@
   return parent() == NULL || IsTransformed();
 }
 
-// Returns true if this container box serves as a stacking context for
-// descendant elements.  The core stacking context creation criteria is given
-// here (https://www.w3.org/TR/CSS21/visuren.html#z-index) however it is
-// extended by various other specification documents such as those describing
-// opacity (https://www.w3.org/TR/css3-color/#transparency) and transforms
-// (https://www.w3.org/TR/css3-transforms/#transform-rendering).
 bool ContainerBox::IsStackingContext() const {
   bool has_opacity =
       base::polymorphic_downcast<const cssom::NumberValue*>(
@@ -189,20 +180,22 @@
     negative_z_index_child_.clear();
     non_negative_z_index_child_.clear();
 
-    const bool kIsNearestContainingBlockOfChildren = true;
-    bool is_nearest_absolute_containing_block =
-        IsContainingBlockForPositionAbsoluteElements();
-    bool is_nearest_fixed_containing_block =
-        IsContainingBlockForPositionFixedElements();
-    bool is_nearest_stacking_context = IsStackingContext();
+    const RelationshipToBox kNearestContainingBlockOfChildren = kIsBox;
+    RelationshipToBox nearest_absolute_containing_block =
+        IsContainingBlockForPositionAbsoluteElements() ? kIsBox
+                                                       : kIsBoxAncestor;
+    RelationshipToBox nearest_fixed_containing_block =
+        IsContainingBlockForPositionFixedElements() ? kIsBox : kIsBoxAncestor;
+    RelationshipToBox nearest_stacking_context =
+        IsStackingContext() ? kIsBox : kIsBoxAncestor;
 
     for (Boxes::const_iterator child_box_iterator = child_boxes_.begin();
          child_box_iterator != child_boxes_.end(); ++child_box_iterator) {
       Box* child_box = *child_box_iterator;
       child_box->UpdateCrossReferencesOfContainerBox(
-          this, kIsNearestContainingBlockOfChildren,
-          is_nearest_absolute_containing_block,
-          is_nearest_fixed_containing_block, is_nearest_stacking_context);
+          this, kNearestContainingBlockOfChildren,
+          nearest_absolute_containing_block, nearest_fixed_containing_block,
+          nearest_stacking_context);
     }
 
     are_cross_references_valid_ = true;
@@ -215,7 +208,9 @@
   positioned_child_boxes_.push_back(child_box);
 }
 
-void ContainerBox::AddStackingContextChild(Box* child_box) {
+void ContainerBox::AddStackingContextChild(
+    Box* child_box, RelationshipToBox containing_block_relationship,
+    int z_index) {
   DCHECK_NE(this, child_box);
   DCHECK_EQ(this, child_box->GetStackingContext());
   int child_z_index = child_box->GetZIndex();
@@ -224,9 +219,11 @@
          "boxes that establish stacking contexts.";
 
   if (child_z_index < 0) {
-    negative_z_index_child_.insert(child_box);
+    negative_z_index_child_.insert(StackingContextChildInfo(
+        child_box, containing_block_relationship, z_index));
   } else {
-    non_negative_z_index_child_.insert(child_box);
+    non_negative_z_index_child_.insert(StackingContextChildInfo(
+        child_box, containing_block_relationship, z_index));
   }
 }
 
@@ -400,84 +397,68 @@
 
 namespace {
 
-Vector2dLayoutUnit GetOffsetFromContainingBlockToStackingContext(
-    Box* child_box) {
-  DCHECK(child_box->IsPositioned() || child_box->IsTransformed());
-
+Vector2dLayoutUnit GetOffsetFromStackingContextToContainingBlock(
+    const Box* child_box,
+    const Box::RelationshipToBox
+        containing_block_relationship_to_stacking_context) {
   Vector2dLayoutUnit relative_position;
-  for (Box *containing_block = child_box->GetContainingBlock(),
-           *current_box = child_box->GetStackingContext();
-       current_box != containing_block;
-       current_box = current_box->GetContainingBlock()) {
-    if (!current_box) {
-      DLOG(WARNING)
-          << "Unsupported stacking context and containing block relation.";
-      break;
-    }
+  if (containing_block_relationship_to_stacking_context != Box::kIsBox) {
+    const Box* current_box =
+        containing_block_relationship_to_stacking_context == Box::kIsBoxAncestor
+            ? child_box->GetStackingContext()
+            : child_box->GetContainingBlock();
+    const Box* end_box =
+        containing_block_relationship_to_stacking_context == Box::kIsBoxAncestor
+            ? child_box->GetContainingBlock()
+            : child_box->GetStackingContext();
+
+    while (current_box != end_box) {
+      if (!current_box) {
+        DLOG(WARNING)
+            << "Unsupported stacking context and containing block relation.";
+        break;
+      }
 #if !defined(NDEBUG)
-    // We should not determine a used position through a transform, as
-    // rectangles may not remain rectangles past it, and thus obtaining
-    // a position may be misleading.
-    if (current_box->IsTransformed()) {
-      DLOG(WARNING) << "Boxes with stacking contexts above containing blocks "
-                       "with transforms may not be positioned correctly.";
-    }
+      // We should not determine a used position through a transform, as
+      // rectangles may not remain rectangles past it, and thus obtaining
+      // a position may be misleading.
+      if (current_box->IsTransformed()) {
+        DLOG(WARNING) << "Boxes with stacking contexts unequal to their "
+                         "containing blocks that include transforms may not be "
+                         "positioned correctly.";
+      }
 #endif
 
-    relative_position += current_box->GetContentBoxOffsetFromMarginBox();
-    relative_position += current_box->margin_box_offset_from_containing_block();
-  }
-  return relative_position;
-}
+      relative_position += current_box->GetContentBoxOffsetFromMarginBox();
+      relative_position +=
+          current_box->margin_box_offset_from_containing_block();
 
-Vector2dLayoutUnit GetOffsetFromStackingContextToContainingBlock(
-    Box* child_box) {
-  const scoped_refptr<cssom::PropertyValue>& child_box_position =
-      child_box->computed_style()->position();
-  if (child_box_position == cssom::KeywordValue::GetFixed()) {
-    // Elements with fixed position will have their containing block farther
-    // up the hierarchy than the stacking context, so handle this case
-    // specially.
-    return -GetOffsetFromContainingBlockToStackingContext(child_box);
+      if (current_box->computed_style()->position() ==
+          cssom::KeywordValue::GetAbsolute()) {
+        relative_position -= current_box->GetContainingBlock()
+                                 ->GetContentBoxOffsetFromPaddingBox();
+      }
+      current_box = current_box->GetContainingBlock();
+    }
+
+    // If the containing block is an ancestor of the stacking context, then
+    // reverse the relative position now. The earlier calculations were for the
+    // containing block being a descendant of the stacking context.
+    if (containing_block_relationship_to_stacking_context ==
+        Box::kIsBoxAncestor) {
+      relative_position = -relative_position;
+    }
   }
 
-  Vector2dLayoutUnit relative_position;
-  if (child_box_position == cssom::KeywordValue::GetAbsolute()) {
+  if (child_box->computed_style()->position() ==
+      cssom::KeywordValue::GetAbsolute()) {
     // The containing block is formed by the padding box instead of the content
     // box, as described in
     // http://www.w3.org/TR/CSS21/visudet.html#containing-block-details.
     relative_position -=
         child_box->GetContainingBlock()->GetContentBoxOffsetFromPaddingBox();
   }
-  for (Box *current_box = child_box->GetContainingBlock(),
-           *stacking_context = child_box->GetStackingContext();
-       current_box != stacking_context;
-       current_box = current_box->GetContainingBlock()) {
-    if (!current_box) {
-      // Positioned elements may have their containing block farther
-      // up the hierarchy than the stacking context, so handle this case here.
-      DCHECK(child_box->IsPositioned() || child_box->IsTransformed());
-      return -GetOffsetFromContainingBlockToStackingContext(child_box);
-    }
-#if !defined(NDEBUG)
-    // We should not determine a used position through a transform, as
-    // rectangles may not remain rectangles past it, and thus obtaining
-    // a position may be misleading.
-    if (current_box->IsTransformed()) {
-      DLOG(WARNING) << "Boxes with stacking contexts below containing blocks "
-                       "with transforms may not be positioned correctly.";
-    }
-#endif
 
-    relative_position += current_box->GetContentBoxOffsetFromMarginBox();
-    relative_position += current_box->margin_box_offset_from_containing_block();
-
-    if (current_box->computed_style()->position() ==
-        cssom::KeywordValue::GetAbsolute()) {
-      relative_position -= current_box->GetContainingBlock()
-                               ->GetContentBoxOffsetFromPaddingBox();
-    }
-  }
   return relative_position;
 }
 
@@ -490,11 +471,14 @@
   // Render all children of the passed in list in sorted order.
   for (ZIndexSortedList::const_iterator iter = z_index_child_list.begin();
        iter != z_index_child_list.end(); ++iter) {
-    Box* child_box = *iter;
+    const StackingContextChildInfo& stacking_context_child_info = *iter;
+    Box* child_box = stacking_context_child_info.box;
 
     DCHECK_EQ(this, child_box->GetStackingContext());
     Vector2dLayoutUnit position_offset =
-        GetOffsetFromStackingContextToContainingBlock(child_box) +
+        GetOffsetFromStackingContextToContainingBlock(
+            child_box,
+            stacking_context_child_info.containing_block_relationship) +
         offset_from_parent_node;
 
     child_box->RenderAndAnimate(content_node_builder, position_offset);
@@ -516,14 +500,14 @@
 }
 
 void ContainerBox::UpdateCrossReferencesOfContainerBox(
-    ContainerBox* source_box, bool is_nearest_containing_block,
-    bool is_nearest_absolute_containing_block,
-    bool is_nearest_fixed_containing_block, bool is_nearest_stacking_context) {
+    ContainerBox* source_box, RelationshipToBox nearest_containing_block,
+    RelationshipToBox nearest_absolute_containing_block,
+    RelationshipToBox nearest_fixed_containing_block,
+    RelationshipToBox nearest_stacking_context) {
   // First update the source container box's cross references with this box.
   Box::UpdateCrossReferencesOfContainerBox(
-      source_box, is_nearest_containing_block,
-      is_nearest_absolute_containing_block, is_nearest_fixed_containing_block,
-      is_nearest_stacking_context);
+      source_box, nearest_containing_block, nearest_absolute_containing_block,
+      nearest_fixed_containing_block, nearest_stacking_context);
 
   // In addition to updating the source container box's cross references with
   // this box, we also recursively update it with our children.
@@ -532,30 +516,36 @@
   // specified types, then the target container box cannot be the nearest box of
   // that type for the children.
 
-  const bool kIsNearestContainingBlockOfChildren = false;
-  bool is_nearest_absolute_containing_block_of_children =
-      is_nearest_absolute_containing_block &&
-      !IsContainingBlockForPositionAbsoluteElements();
-  bool is_nearest_fixed_containing_block_of_children =
-      is_nearest_fixed_containing_block &&
-      !IsContainingBlockForPositionFixedElements();
-  bool is_nearest_stacking_context_of_children =
-      is_nearest_stacking_context && !IsStackingContext();
+  const RelationshipToBox kNearestContainingBlockOfChildren = kIsBoxDescendant;
+  RelationshipToBox nearest_absolute_containing_block_of_children =
+      nearest_absolute_containing_block != kIsBoxDescendant &&
+              IsContainingBlockForPositionAbsoluteElements()
+          ? kIsBoxDescendant
+          : nearest_absolute_containing_block;
+  RelationshipToBox nearest_fixed_containing_block_of_children =
+      nearest_fixed_containing_block != kIsBoxDescendant &&
+              IsContainingBlockForPositionFixedElements()
+          ? kIsBoxDescendant
+          : nearest_fixed_containing_block;
+  RelationshipToBox nearest_stacking_context_of_children =
+      nearest_stacking_context != kIsBoxDescendant && IsStackingContext()
+          ? kIsBoxDescendant
+          : nearest_stacking_context;
 
   // Only process the children if the target container box is still the nearest
   // box of one of the types. If it is not, then it is impossible for any of the
   // children to be added to the cross references.
-  if (is_nearest_absolute_containing_block_of_children ||
-      is_nearest_fixed_containing_block_of_children ||
-      is_nearest_stacking_context_of_children) {
+  if (nearest_absolute_containing_block_of_children == kIsBox ||
+      nearest_fixed_containing_block_of_children == kIsBox ||
+      nearest_stacking_context_of_children == kIsBox) {
     for (Boxes::const_iterator child_box_iterator = child_boxes_.begin();
          child_box_iterator != child_boxes_.end(); ++child_box_iterator) {
       Box* child_box = *child_box_iterator;
       child_box->UpdateCrossReferencesOfContainerBox(
-          source_box, kIsNearestContainingBlockOfChildren,
-          is_nearest_absolute_containing_block_of_children,
-          is_nearest_fixed_containing_block_of_children,
-          is_nearest_stacking_context_of_children);
+          source_box, kNearestContainingBlockOfChildren,
+          nearest_absolute_containing_block_of_children,
+          nearest_fixed_containing_block_of_children,
+          nearest_stacking_context_of_children);
     }
   }
 }
@@ -567,20 +557,24 @@
 
   Vector2dLayoutUnit content_box_offset(border_left_width() + padding_left(),
                                         border_top_width() + padding_top());
-  // Render all positioned children in our stacking context that have negative
-  // z-index values.
+
+  // Render all child stacking contexts and positioned children in our stacking
+  // context that have negative z-index values.
   //   https://www.w3.org/TR/CSS21/visuren.html#z-index
   RenderAndAnimateStackingContextChildren(
       negative_z_index_child_, border_node_builder, content_box_offset);
-  // Render laid out child boxes.
+
+  // Render in-flow, non-positioned child boxes.
+  //   https://www.w3.org/TR/CSS21/visuren.html#z-index
   for (Boxes::const_iterator child_box_iterator = child_boxes_.begin();
        child_box_iterator != child_boxes_.end(); ++child_box_iterator) {
     Box* child_box = *child_box_iterator;
-    if (!child_box->IsPositioned() && !child_box->IsTransformed()) {
+    if (!child_box->IsPositioned() && !child_box->IsStackingContext()) {
       child_box->RenderAndAnimate(border_node_builder, content_box_offset);
     }
   }
-  // Render all positioned children with non-negative z-index values.
+  // Render all child stacking contexts and positioned children in our stacking
+  // context that have non-negative z-index values.
   //   https://www.w3.org/TR/CSS21/visuren.html#z-index
   RenderAndAnimateStackingContextChildren(
       non_negative_z_index_child_, border_node_builder, content_box_offset);
diff --git a/src/cobalt/layout/container_box.h b/src/cobalt/layout/container_box.h
index 1f8f60a..bc64882 100644
--- a/src/cobalt/layout/container_box.h
+++ b/src/cobalt/layout/container_box.h
@@ -78,20 +78,40 @@
   // section: https://www.w3.org/TR/css3-transforms/#transform-rendering.
   bool IsContainingBlockForPositionFixedElements() const;
 
-  // Returns true if this container box serves as a stacking context for
-  // descendant elements.
-  bool IsStackingContext() const;
+  // Returns true if the box serves as a stacking context for descendant
+  // elements. The core stacking context creation criteria is given here
+  // (https://www.w3.org/TR/CSS21/visuren.html#z-index) however it is extended
+  // by various other specification documents such as those describing opacity
+  // (https://www.w3.org/TR/css3-color/#transparency) and transforms
+  // (https://www.w3.org/TR/css3-transforms/#transform-rendering).
+  bool IsStackingContext() const OVERRIDE;
 
  protected:
+  struct StackingContextChildInfo {
+    StackingContextChildInfo(Box* box,
+                             RelationshipToBox containing_block_relationship,
+                             int z_index)
+        : box(box),
+          containing_block_relationship(containing_block_relationship),
+          z_index(z_index) {}
+
+    Box* box;
+    RelationshipToBox containing_block_relationship;
+    int z_index;
+  };
+
   class ZIndexComparator {
    public:
-    bool operator()(const Box* lhs, const Box* rhs) const {
-      return lhs->GetZIndex() < rhs->GetZIndex();
+    bool operator()(const StackingContextChildInfo& lhs,
+                    const StackingContextChildInfo& rhs) const {
+      return lhs.z_index < rhs.z_index;
     }
   };
-  // Note: find(Box*) and erase(Box*) on ZIndexSortedList may not work as
-  // expected due to the use of reflexive comparison for equality.
-  typedef std::multiset<Box*, ZIndexComparator> ZIndexSortedList;
+  // Note: find(StackingContextChildInfo) and erase(StackingContextChildInfo) on
+  // ZIndexSortedList may not work as expected due to the use of reflexive
+  // comparison for equality.
+  typedef std::multiset<StackingContextChildInfo, ZIndexComparator>
+      ZIndexSortedList;
 
   void UpdateRectOfPositionedChildBoxes(
       const LayoutParams& relative_child_layout_params,
@@ -115,10 +135,10 @@
   const Boxes& child_boxes() const { return child_boxes_; }
 
   void UpdateCrossReferencesOfContainerBox(
-      ContainerBox* source_box, bool is_nearest_containing_block,
-      bool is_nearest_absolute_containing_block,
-      bool is_nearest_fixed_containing_block,
-      bool is_nearest_stacking_context) OVERRIDE;
+      ContainerBox* source_box, RelationshipToBox nearest_containing_block,
+      RelationshipToBox nearest_absolute_containing_block,
+      RelationshipToBox nearest_fixed_containing_block,
+      RelationshipToBox nearest_stacking_context) OVERRIDE;
 
   bool ValidateUpdateSizeInputs(const LayoutParams& params) OVERRIDE;
   void InvalidateUpdateSizeInputs() { update_size_results_valid_ = false; }
@@ -134,7 +154,9 @@
   // These helper functions are called from
   // Box::UpdateCrossReferencesOfContainerBox().
   void AddContainingBlockChild(Box* child_box);
-  void AddStackingContextChild(Box* child_box);
+  void AddStackingContextChild(Box* child_box,
+                               RelationshipToBox containing_block_relationship,
+                               int z_index);
 
   // Updates used values of left/top/right/bottom given the child_box's
   // 'position' property is set to 'relative'.
diff --git a/src/cobalt/layout/layout_manager.cc b/src/cobalt/layout/layout_manager.cc
index 88f5a41..0ada5d3 100644
--- a/src/cobalt/layout/layout_manager.cc
+++ b/src/cobalt/layout/layout_manager.cc
@@ -43,7 +43,7 @@
        const OnRenderTreeProducedCallback& on_render_tree_produced,
        LayoutTrigger layout_trigger, int dom_max_element_depth,
        float layout_refresh_rate, const std::string& language,
-       LayoutStatTracker* layout_stat_tracker);
+       bool enable_image_animations, LayoutStatTracker* layout_stat_tracker);
   ~Impl();
 
   // From dom::DocumentObserver.
@@ -143,12 +143,12 @@
     const OnRenderTreeProducedCallback& on_render_tree_produced,
     LayoutTrigger layout_trigger, int dom_max_element_depth,
     float layout_refresh_rate, const std::string& language,
-    LayoutStatTracker* layout_stat_tracker)
+    bool enable_image_animations, LayoutStatTracker* layout_stat_tracker)
     : window_(window),
       locale_(icu::Locale::createCanonical(language.c_str())),
       used_style_provider_(new UsedStyleProvider(
           window->html_element_context(), window->document()->font_cache(),
-          base::Bind(&AttachCameraNodes, window))),
+          base::Bind(&AttachCameraNodes, window), enable_image_animations)),
       on_render_tree_produced_callback_(on_render_tree_produced),
       layout_trigger_(layout_trigger),
       layout_dirty_(StringPrintf("%s.Layout.IsDirty", name.c_str()), true,
@@ -359,10 +359,10 @@
     const OnRenderTreeProducedCallback& on_render_tree_produced,
     LayoutTrigger layout_trigger, const int dom_max_element_depth,
     const float layout_refresh_rate, const std::string& language,
-    LayoutStatTracker* layout_stat_tracker)
+    bool enable_image_animations, LayoutStatTracker* layout_stat_tracker)
     : impl_(new Impl(name, window, on_render_tree_produced, layout_trigger,
                      dom_max_element_depth, layout_refresh_rate, language,
-                     layout_stat_tracker)) {}
+                     enable_image_animations, layout_stat_tracker)) {}
 
 LayoutManager::~LayoutManager() {}
 
diff --git a/src/cobalt/layout/layout_manager.h b/src/cobalt/layout/layout_manager.h
index e02cc0b..a2cde5c 100644
--- a/src/cobalt/layout/layout_manager.h
+++ b/src/cobalt/layout/layout_manager.h
@@ -64,6 +64,7 @@
                 const OnRenderTreeProducedCallback& on_render_tree_produced,
                 LayoutTrigger layout_trigger, const int dom_max_element_depth,
                 const float layout_refresh_rate, const std::string& language,
+                bool enable_image_animations,
                 LayoutStatTracker* layout_stat_tracker);
   ~LayoutManager();
 
diff --git a/src/cobalt/layout/used_style.cc b/src/cobalt/layout/used_style.cc
index 91e64d4..0d3dd43 100644
--- a/src/cobalt/layout/used_style.cc
+++ b/src/cobalt/layout/used_style.cc
@@ -408,12 +408,14 @@
 
 UsedStyleProvider::UsedStyleProvider(
     dom::HTMLElementContext* html_element_context, dom::FontCache* font_cache,
-    const AttachCameraNodeFunction& attach_camera_node_function)
+    const AttachCameraNodeFunction& attach_camera_node_function,
+    bool enable_image_animations)
     : font_cache_(font_cache),
       animated_image_tracker_(html_element_context->animated_image_tracker()),
       image_cache_(html_element_context->image_cache()),
       mesh_cache_(html_element_context->mesh_cache()),
-      attach_camera_node_function_(attach_camera_node_function) {}
+      attach_camera_node_function_(attach_camera_node_function),
+      enable_image_animations_(enable_image_animations) {}
 
 scoped_refptr<dom::FontList> UsedStyleProvider::GetUsedFontList(
     const scoped_refptr<cssom::PropertyValue>& font_family_refptr,
@@ -657,19 +659,23 @@
     scoped_refptr<loader::image::AnimatedImage> animated_image =
         base::polymorphic_downcast<loader::image::AnimatedImage*>(
             used_background_image.get());
-    DCHECK(animated_image);
     scoped_refptr<render_tree::ImageNode> image_node =
         new render_tree::ImageNode(
-            NULL, image_rect, image_transform_data.image_node_transform_matrix);
-    render_tree::animations::AnimateNode::Builder animate_node_builder;
-    animate_node_builder.Add(
-        image_node,
-        base::Bind(&loader::image::AnimatedImage::AnimateCallback,
-                   animated_image, image_rect,
-                   image_transform_data.image_node_transform_matrix));
+            animated_image->GetFrameProvider()->GetFrame(), image_rect,
+            image_transform_data.image_node_transform_matrix);
+    if (!used_style_provider_->enable_image_animations()) {
+      background_node_ = image_node;
+    } else {
+      render_tree::animations::AnimateNode::Builder animate_node_builder;
+      animate_node_builder.Add(
+          image_node,
+          base::Bind(&loader::image::AnimatedImage::AnimateCallback,
+                     animated_image->GetFrameProvider(), image_rect,
+                     image_transform_data.image_node_transform_matrix));
 
-    background_node_ = new render_tree::animations::AnimateNode(
-        animate_node_builder, image_node);
+      background_node_ = new render_tree::animations::AnimateNode(
+          animate_node_builder, image_node);
+    }
   }
 }
 
diff --git a/src/cobalt/layout/used_style.h b/src/cobalt/layout/used_style.h
index 3e670df..f2ec746 100644
--- a/src/cobalt/layout/used_style.h
+++ b/src/cobalt/layout/used_style.h
@@ -68,9 +68,10 @@
       const scoped_refptr<render_tree::Node>&, float max_horizontal_fov_rad,
       float max_vertical_fov_rad)> AttachCameraNodeFunction;
 
-  UsedStyleProvider(
-      dom::HTMLElementContext* html_element_context, dom::FontCache* font_cache,
-      const AttachCameraNodeFunction& attach_camera_node_function);
+  UsedStyleProvider(dom::HTMLElementContext* html_element_context,
+                    dom::FontCache* font_cache,
+                    const AttachCameraNodeFunction& attach_camera_node_function,
+                    bool enable_image_animations);
 
   scoped_refptr<dom::FontList> GetUsedFontList(
       const scoped_refptr<cssom::PropertyValue>& font_family_refptr,
@@ -90,6 +91,8 @@
   // images.
   void UpdateAnimatedImages();
 
+  bool enable_image_animations() const { return enable_image_animations_; }
+
  private:
   // Called after layout is completed so that it can perform any necessary
   // cleanup. Its primary current purpose is making a request to the font cache
@@ -117,6 +120,9 @@
   scoped_refptr<cssom::PropertyValue> last_font_weight_refptr_;
   scoped_refptr<dom::FontList> last_font_list_;
 
+  // If true, animated WebP images should animate, otherwise they should not.
+  const bool enable_image_animations_;
+
   friend class UsedStyleProviderLayoutScope;
   DISALLOW_COPY_AND_ASSIGN(UsedStyleProvider);
 };
diff --git a/src/cobalt/layout_tests/testdata/css-2-1/10-1-containing-block-above-stacking-context-should-be-padding-edge-for-absolute-positioned-elements-expected.png b/src/cobalt/layout_tests/testdata/css-2-1/10-1-containing-block-above-stacking-context-should-be-padding-edge-for-absolute-positioned-elements-expected.png
new file mode 100644
index 0000000..6e56609
--- /dev/null
+++ b/src/cobalt/layout_tests/testdata/css-2-1/10-1-containing-block-above-stacking-context-should-be-padding-edge-for-absolute-positioned-elements-expected.png
Binary files differ
diff --git a/src/cobalt/layout_tests/testdata/css-2-1/10-1-containing-block-above-stacking-context-should-be-padding-edge-for-absolute-positioned-elements.html b/src/cobalt/layout_tests/testdata/css-2-1/10-1-containing-block-above-stacking-context-should-be-padding-edge-for-absolute-positioned-elements.html
new file mode 100644
index 0000000..a52c5c7
--- /dev/null
+++ b/src/cobalt/layout_tests/testdata/css-2-1/10-1-containing-block-above-stacking-context-should-be-padding-edge-for-absolute-positioned-elements.html
@@ -0,0 +1,43 @@
+<!DOCTYPE html>
+<!--
+ | If the element has "position: absolute", the containing block is established
+ | by the padding edge of the nearest ancestor with a "position" of "absolute".
+ |   http://www.w3.org/TR/CSS21/visudet.html#containing-block-details
+ -->
+<html>
+<head>
+  <style>
+    .containing-block-with-padding {
+      position: absolute;
+
+      width: 150px;
+      height: 150px;
+
+      padding-top: 50px;
+      padding-left: 50px;
+
+      background-color: rgb(100, 100, 255);
+    }
+    .stacking-context {
+      position: static;
+
+      opacity: .9;
+    }
+    .absolute-element {
+      position: absolute;
+
+      width: 100px;
+      height: 100px;
+
+      background-color: rgb(255, 100, 100);
+    }
+  </style>
+</head>
+<body>
+  <div class="containing-block-with-padding">
+    <div class="stacking-context">
+      <div class="absolute-element"></div>
+    </div>
+  </div>
+</body>
+</html>
diff --git a/src/cobalt/layout_tests/testdata/css-2-1/9-9-1-nearest-ancestor-stacking-context-should-contain-element-expected.png b/src/cobalt/layout_tests/testdata/css-2-1/9-9-1-nearest-ancestor-stacking-context-should-contain-element-expected.png
new file mode 100644
index 0000000..4abe46a
--- /dev/null
+++ b/src/cobalt/layout_tests/testdata/css-2-1/9-9-1-nearest-ancestor-stacking-context-should-contain-element-expected.png
Binary files differ
diff --git a/src/cobalt/layout_tests/testdata/css-2-1/9-9-1-nearest-ancestor-stacking-context-should-contain-element.html b/src/cobalt/layout_tests/testdata/css-2-1/9-9-1-nearest-ancestor-stacking-context-should-contain-element.html
new file mode 100644
index 0000000..27788d4
--- /dev/null
+++ b/src/cobalt/layout_tests/testdata/css-2-1/9-9-1-nearest-ancestor-stacking-context-should-contain-element.html
@@ -0,0 +1,42 @@
+<!DOCTYPE html>
+<!--
+ | Stacking contexts are different from containing blocks. This test ensures
+ | that elements with an 'auto' z-index still use the correct stacking context
+ | when their containing block is further up the tree than their stacking
+ | context. The results should show the blue box in front of the gray box.
+ |   https://www.w3.org/TR/CSS21/visuren.html#z-index
+ -->
+<html>
+<head>
+  <style>
+    .blue-container {
+      position: absolute;
+
+      left: 20px;
+      top: 20px;
+
+      width: 150px;
+      height: 150px;
+      z-index: 3;
+
+      background-color: rgb(100, 100, 100);
+    }
+    .blue {
+      position: fixed;
+
+      left: -20px;
+      top: -20px;
+
+      width: 100px;
+      height: 100px;
+
+      background-color: rgb(100, 100, 255);
+    }
+  </style>
+</head>
+<body>
+  <div class="blue-container">
+    <div class="blue"></div>
+  </div>
+</body>
+</html>
diff --git a/src/cobalt/layout_tests/testdata/css-2-1/9-9-1-relative-positioned-element-should-be-included-in-containing-stacking-context-expected.png b/src/cobalt/layout_tests/testdata/css-2-1/9-9-1-relative-positioned-element-should-be-included-in-containing-stacking-context-expected.png
new file mode 100644
index 0000000..3fad754
--- /dev/null
+++ b/src/cobalt/layout_tests/testdata/css-2-1/9-9-1-relative-positioned-element-should-be-included-in-containing-stacking-context-expected.png
Binary files differ
diff --git a/src/cobalt/layout_tests/testdata/css-2-1/9-9-1-relative-positioned-element-should-be-included-in-containing-stacking-context.html b/src/cobalt/layout_tests/testdata/css-2-1/9-9-1-relative-positioned-element-should-be-included-in-containing-stacking-context.html
new file mode 100644
index 0000000..5ba4ab5
--- /dev/null
+++ b/src/cobalt/layout_tests/testdata/css-2-1/9-9-1-relative-positioned-element-should-be-included-in-containing-stacking-context.html
@@ -0,0 +1,61 @@
+<!DOCTYPE html>
+<!--
+ | Relative positioned elements should be placed in the correct stacking
+ | context, even when their containing block is nearer and is an in-flow,
+ | non-positioned descendants The results should show the blue box in front of
+ | the green box.
+ |   https://www.w3.org/TR/CSS21/visuren.html#z-index
+ -->
+<html>
+<head>
+  <style>
+    .container {
+      position: absolute;
+
+      width: 150px;
+      height: 150px;
+
+      background-color: rgb(158, 158, 158);
+    }
+    .green {
+      position: absolute;
+
+      left: 0px;
+      top: 0px;
+
+      width: 80px;
+      height: 80px;
+
+      background-color: rgb(15, 157, 88);
+    }
+    .blue-container {
+      position: static;
+
+      left: 20px;
+      top: 20px;
+
+      width: 150px;
+      height: 150px;
+    }
+    .blue {
+      position: relative;
+
+      left: 40px;
+      top: 40px;
+
+      width: 80px;
+      height: 80px;
+
+      background-color: rgb(66, 133, 244);
+    }
+  </style>
+</head>
+<body>
+  <div class="container">
+    <div class="green"></div>
+    <div class="blue-container">
+      <div class="blue"></div>
+    </div>
+  </div>
+</body>
+</html>
diff --git a/src/cobalt/layout_tests/testdata/css-2-1/9-9-1-relative-positioned-element-should-not-appear-on-top-of-later-sibling-expected.png b/src/cobalt/layout_tests/testdata/css-2-1/9-9-1-relative-positioned-element-should-not-appear-on-top-of-later-sibling-expected.png
new file mode 100644
index 0000000..3fad754
--- /dev/null
+++ b/src/cobalt/layout_tests/testdata/css-2-1/9-9-1-relative-positioned-element-should-not-appear-on-top-of-later-sibling-expected.png
Binary files differ
diff --git a/src/cobalt/layout_tests/testdata/css-2-1/9-9-1-relative-positioned-element-should-not-appear-on-top-of-later-sibling.html b/src/cobalt/layout_tests/testdata/css-2-1/9-9-1-relative-positioned-element-should-not-appear-on-top-of-later-sibling.html
new file mode 100644
index 0000000..fecd57c
--- /dev/null
+++ b/src/cobalt/layout_tests/testdata/css-2-1/9-9-1-relative-positioned-element-should-not-appear-on-top-of-later-sibling.html
@@ -0,0 +1,49 @@
+<!DOCTYPE html>
+<!--
+ | Relative positioned elements should appear in the correct draw order when
+ | they have later, absolutely positioned siblings. The results should show the
+ | blue box in front of the green box.
+ |   https://www.w3.org/TR/CSS21/visuren.html#z-index
+ -->
+<html>
+<head>
+  <style>
+    .container {
+      position: absolute;
+
+      width: 150px;
+      height: 150px;
+
+      background-color: rgb(158, 158, 158);
+    }
+    .green {
+      position: relative;
+
+      left: 0px;
+      top: 0px;
+
+      width: 80px;
+      height: 80px;
+
+      background-color: rgb(15, 157, 88);
+    }
+    .blue {
+      position: absolute;
+
+      left: 40px;
+      top: 40px;
+
+      width: 80px;
+      height: 80px;
+
+      background-color: rgb(66, 133, 244);
+    }
+  </style>
+</head>
+<body>
+  <div class="container">
+    <div class="green"></div>
+    <div class="blue"></div>
+  </div>
+</body>
+</html>
diff --git a/src/cobalt/layout_tests/testdata/css-2-1/layout_tests.txt b/src/cobalt/layout_tests/testdata/css-2-1/layout_tests.txt
index 8b56461..823ef54 100644
--- a/src/cobalt/layout_tests/testdata/css-2-1/layout_tests.txt
+++ b/src/cobalt/layout_tests/testdata/css-2-1/layout_tests.txt
@@ -5,6 +5,7 @@
 10-1-absolute-positioned-elements-do-not-effect-containing-block-size
 10-1-containing-block-should-be-ancestor-padding-edge-for-absolutely-positioned-elements
 10-1-containing-block-should-be-ancestor-padding-edge-for-percentage-of-absolutely-positioned-elements
+10-1-containing-block-above-stacking-context-should-be-padding-edge-for-absolute-positioned-elements
 10-3-1-auto-margin-should-become-zero-in-inline-non-replaced-elements
 10-3-1-width-should-not-apply-to-inline-non-replaced-elements
 10-3-2-auto-margin-should-become-zero-in-inline-replaced-elements
@@ -136,7 +137,10 @@
 9-9-1-absolute-positioned-elements-should-render-on-top-of-static-elements
 9-9-1-containing-block-may-be-farther-than-stacking-context
 9-9-1-elements-with-transform-should-appear-over-normal-flow-elements
+9-9-1-nearest-ancestor-stacking-context-should-contain-element
 9-9-1-negative-z-indices
+9-9-1-relative-positioned-element-should-not-appear-on-top-of-later-sibling
+9-9-1-relative-positioned-element-should-be-included-in-containing-stacking-context
 9-9-1-simple-positive-z-indices
 9-9-1-stacking-context-should-take-into-account-intermediate-containing-blocks
 9-9-1-stacking-contexts-differ-from-containing-blocks
diff --git a/src/cobalt/layout_tests/testdata/css3-color/3-2-non-positioned-element-with-opacity-forms-stacking-context-expected.png b/src/cobalt/layout_tests/testdata/css3-color/3-2-non-positioned-element-with-opacity-forms-stacking-context-expected.png
new file mode 100644
index 0000000..4f6d2da
--- /dev/null
+++ b/src/cobalt/layout_tests/testdata/css3-color/3-2-non-positioned-element-with-opacity-forms-stacking-context-expected.png
Binary files differ
diff --git a/src/cobalt/layout_tests/testdata/css3-color/3-2-non-positioned-element-with-opacity-forms-stacking-context.html b/src/cobalt/layout_tests/testdata/css3-color/3-2-non-positioned-element-with-opacity-forms-stacking-context.html
new file mode 100644
index 0000000..2b01d07
--- /dev/null
+++ b/src/cobalt/layout_tests/testdata/css3-color/3-2-non-positioned-element-with-opacity-forms-stacking-context.html
@@ -0,0 +1,57 @@
+<!DOCTYPE html>
+<!--
+ | This test ensures that a non-positioned element with an opacity less than 1
+ | is painted by its containing stacking context in the order used for child
+ | stacking contexts and positioned elements. The results should draw the blue
+ | rectangle in front of the green rectangle.
+ |   https://www.w3.org/TR/css3-color/#opacity
+ -->
+<html>
+<head>
+  <style>
+    .container {
+      position: absolute;
+
+      width: 150px;
+      height: 150px;
+
+      background-color: rgb(158, 158, 158);
+    }
+    .green {
+      position: absolute;
+
+      left: 20px;
+      top: 20px;
+
+      width: 80px;
+      height: 80px;
+
+      background-color: rgb(15, 157, 88);
+    }
+    .blue-container {
+      position: static;
+
+      opacity: .9;
+
+      width: 150px;
+      height: 150px;
+    }
+    .blue {
+      position: static;
+
+      width: 80px;
+      height: 80px;
+
+      background-color: rgb(66, 133, 244);
+    }
+  </style>
+</head>
+<body>
+  <div class="container">
+    <div class="green"></div>
+    <div class="blue-container">
+      <div class="blue"></div>
+    </div>
+  </div>
+</body>
+</html>
diff --git a/src/cobalt/layout_tests/testdata/css3-color/3-2-non-positioned-element-with-opacity-impacts-positioned-descendants-expected.png b/src/cobalt/layout_tests/testdata/css3-color/3-2-non-positioned-element-with-opacity-impacts-positioned-descendants-expected.png
new file mode 100644
index 0000000..8886f53
--- /dev/null
+++ b/src/cobalt/layout_tests/testdata/css3-color/3-2-non-positioned-element-with-opacity-impacts-positioned-descendants-expected.png
Binary files differ
diff --git a/src/cobalt/layout_tests/testdata/css3-color/3-2-non-positioned-element-with-opacity-impacts-positioned-descendants.html b/src/cobalt/layout_tests/testdata/css3-color/3-2-non-positioned-element-with-opacity-impacts-positioned-descendants.html
new file mode 100644
index 0000000..da1c3c3
--- /dev/null
+++ b/src/cobalt/layout_tests/testdata/css3-color/3-2-non-positioned-element-with-opacity-impacts-positioned-descendants.html
@@ -0,0 +1,49 @@
+<!DOCTYPE html>
+<html>
+<!--
+ | This test ensures that a non-positioned element with an opacity less than 1
+ | paints the layer it creates at the same stacking order that would be used if
+ | it were a positioned element with ‘z-index: 0’ and ‘opacity: 1’, impacting
+ | all of its positioned ancestors. The results should not display "This line is
+ | invisible."
+ |   https://www.w3.org/TR/css3-color/#transparency
+ -->
+<head>
+  <style>
+    body {
+      font-family: Roboto;
+      font-size: 16px;
+    }
+
+    .positioned-container {
+      position: absolute;
+      width: 300px;
+      height: 50px;
+      background-color: #00CC00;
+    }
+
+    .opacity-test {
+      position: static;
+      opacity: 0;
+      width: 300px;
+      height: 50px;
+      background-color: #00CC00;
+    }
+
+    .positioned-red {
+      position: absolute;
+      width: 400px;
+      height: 20px;
+      background-color: #FF0000;
+      top: 60px;
+    }
+  </style>
+</head>
+<body>
+  <div class="positioned-container">
+    <div class="opacity-test">
+      <div class="positioned-red"> This line is invisible. </div>
+    </div>
+  </div>
+</body>
+</html>
diff --git a/src/cobalt/layout_tests/testdata/css3-color/layout_tests.txt b/src/cobalt/layout_tests/testdata/css3-color/layout_tests.txt
index 8b6aa39..678bedd 100644
--- a/src/cobalt/layout_tests/testdata/css3-color/layout_tests.txt
+++ b/src/cobalt/layout_tests/testdata/css3-color/layout_tests.txt
@@ -1,4 +1,6 @@
 3-2-nested-opacity-compounds
+3-2-non-positioned-element-with-opacity-forms-stacking-context
+3-2-non-positioned-element-with-opacity-impacts-positioned-descendants
 3-2-opacity-applies-to-children
 3-2-opacity-applies-to-text
 3-2-opacity-does-apply-to-background
diff --git a/src/cobalt/loader/image/animated_image_tracker.cc b/src/cobalt/loader/image/animated_image_tracker.cc
index 25280aa..870acce 100644
--- a/src/cobalt/loader/image/animated_image_tracker.cc
+++ b/src/cobalt/loader/image/animated_image_tracker.cc
@@ -16,6 +16,7 @@
 
 #include <algorithm>
 
+#include "base/debug/trace_event.h"
 #include "cobalt/base/polymorphic_downcast.h"
 
 namespace cobalt {
@@ -25,6 +26,7 @@
 AnimatedImageTracker::AnimatedImageTracker(
     base::ThreadPriority animated_image_decode_thread_priority)
     : animated_image_decode_thread_("AnimatedImage") {
+  TRACE_EVENT0("cobalt::loader::image", "AnimatedImageTracker::RecordImage()");
   base::Thread::Options options(MessageLoop::TYPE_DEFAULT,
                                 0 /* default stack size */,
                                 animated_image_decode_thread_priority);
@@ -32,25 +34,37 @@
 }
 
 AnimatedImageTracker::~AnimatedImageTracker() {
-  animated_image_decode_thread_.Stop();
+  TRACE_EVENT0("cobalt::loader::image", "AnimatedImageTracker::RecordImage()");
+  DCHECK(thread_checker_.CalledOnValidThread());
 }
 
 void AnimatedImageTracker::IncreaseURLCount(const GURL& url) {
+  TRACE_EVENT0("cobalt::loader::image",
+               "AnimatedImageTracker::IncreaseURLCount()");
+  DCHECK(thread_checker_.CalledOnValidThread());
   current_url_counts_[url]++;
 }
 
 void AnimatedImageTracker::DecreaseURLCount(const GURL& url) {
+  TRACE_EVENT0("cobalt::loader::image",
+               "AnimatedImageTracker::DecreaseURLCount()");
+  DCHECK(thread_checker_.CalledOnValidThread());
   DCHECK_GT(current_url_counts_[url], 0);
   current_url_counts_[url]--;
 }
 
 void AnimatedImageTracker::RecordImage(
     const GURL& url, loader::image::AnimatedImage* animated_image) {
+  DCHECK(thread_checker_.CalledOnValidThread());
   DCHECK(animated_image);
+  TRACE_EVENT0("cobalt::loader::image", "AnimatedImageTracker::RecordImage()");
   image_map_[url] = animated_image;
 }
 
 void AnimatedImageTracker::ProcessRecordedImages() {
+  DCHECK(thread_checker_.CalledOnValidThread());
+  TRACE_EVENT0("cobalt::loader::image",
+               "AnimatedImageTracker::ProcessRecordedImages()");
   for (URLToIntMap::iterator it = current_url_counts_.begin();
        it != current_url_counts_.end();) {
     const GURL& url = it->first;
@@ -83,16 +97,33 @@
 
 void AnimatedImageTracker::OnDisplayStart(
     loader::image::AnimatedImage* animated_image) {
+  DCHECK(thread_checker_.CalledOnValidThread());
   DCHECK(animated_image);
+  TRACE_EVENT0("cobalt::loader::image",
+               "AnimatedImageTracker::OnDisplayStart()");
   animated_image->Play(animated_image_decode_thread_.message_loop_proxy());
 }
 
 void AnimatedImageTracker::OnDisplayEnd(
     loader::image::AnimatedImage* animated_image) {
+  DCHECK(thread_checker_.CalledOnValidThread());
   DCHECK(animated_image);
+  TRACE_EVENT0("cobalt::loader::image", "AnimatedImageTracker::OnDisplayEnd()");
   animated_image->Stop();
 }
 
+void AnimatedImageTracker::Reset() {
+  for (URLSet::const_iterator iter = playing_urls_.begin();
+       iter != playing_urls_.end(); ++iter) {
+    OnDisplayEnd(image_map_[iter->first]);
+  }
+
+  image_map_.clear();
+  current_url_counts_.clear();
+  previous_url_counts_.clear();
+  playing_urls_.clear();
+}
+
 }  // namespace image
 }  // namespace loader
 }  // namespace cobalt
diff --git a/src/cobalt/loader/image/animated_image_tracker.h b/src/cobalt/loader/image/animated_image_tracker.h
index 6fdb115..2cb9c72 100644
--- a/src/cobalt/loader/image/animated_image_tracker.h
+++ b/src/cobalt/loader/image/animated_image_tracker.h
@@ -50,19 +50,32 @@
   // be called at the end of a layout.
   void ProcessRecordedImages();
 
+  // Clears the AnimatedImageTracker, releasing any tracked in-progress
+  // animations.
+  void Reset();
+
  private:
   void OnDisplayStart(loader::image::AnimatedImage* image);
   void OnDisplayEnd(loader::image::AnimatedImage* image);
 
-  typedef std::map<GURL, loader::image::AnimatedImage*> URLToImageMap;
+  typedef std::map<GURL, scoped_refptr<loader::image::AnimatedImage> >
+      URLToImageMap;
   typedef std::map<GURL, int> URLToIntMap;
   typedef base::SmallMap<std::map<GURL, base::Unused>, 1> URLSet;
 
+  // The image decode thread is a thread created and owned by the
+  // AnimatedImageTracker, but used by the AnimatedImage decoders.
+  base::Thread animated_image_decode_thread_;
+
   URLToImageMap image_map_;
   URLToIntMap previous_url_counts_;
   URLToIntMap current_url_counts_;
   URLSet playing_urls_;
-  base::Thread animated_image_decode_thread_;
+
+  // Used to ensure that all AnimatedImageTracker methods are called on the
+  // same thread (*not* |animated_image_decode_thread_|), the thread that we
+  // were constructed on.
+  base::ThreadChecker thread_checker_;
 };
 
 }  // namespace image
diff --git a/src/cobalt/loader/image/animated_webp_image.cc b/src/cobalt/loader/image/animated_webp_image.cc
index e5b1b3b..eef28b9 100644
--- a/src/cobalt/loader/image/animated_webp_image.cc
+++ b/src/cobalt/loader/image/animated_webp_image.cc
@@ -16,6 +16,7 @@
 
 #include "cobalt/loader/image/animated_webp_image.h"
 
+#include "base/synchronization/waitable_event.h"
 #include "cobalt/base/polymorphic_downcast.h"
 #include "cobalt/loader/image/image_decoder.h"
 #include "cobalt/render_tree/brush.h"
@@ -52,15 +53,22 @@
       current_frame_index_(0),
       next_frame_index_(0),
       should_dispose_previous_frame_to_background_(false),
-      resource_provider_(resource_provider) {}
+      resource_provider_(resource_provider),
+      frame_provider_(new FrameProvider()) {
+  TRACE_EVENT0("cobalt::loader::image",
+               "AnimatedWebPImage::AnimatedWebPImage()");
+}
 
-scoped_refptr<render_tree::Image> AnimatedWebPImage::GetFrame() {
-  base::AutoLock lock(lock_);
-  return current_canvas_;
+scoped_refptr<const AnimatedImage::FrameProvider>
+AnimatedWebPImage::GetFrameProvider() {
+  TRACE_EVENT0("cobalt::loader::image",
+               "AnimatedWebPImage::GetFrameProvider()");
+  return frame_provider_;
 }
 
 void AnimatedWebPImage::Play(
     const scoped_refptr<base::MessageLoopProxy>& message_loop) {
+  TRACE_EVENT0("cobalt::loader::image", "AnimatedWebPImage::Play()");
   base::AutoLock lock(lock_);
 
   if (is_playing_) {
@@ -75,17 +83,21 @@
 }
 
 void AnimatedWebPImage::Stop() {
-  if (!decode_closure_.callback().is_null()) {
+  TRACE_EVENT0("cobalt::loader::image", "AnimatedWebPImage::Stop()");
+  base::AutoLock lock(lock_);
+  if (is_playing_) {
     message_loop_->PostTask(
         FROM_HERE,
         base::Bind(&AnimatedWebPImage::StopInternal, base::Unretained(this)));
   }
 }
 
-void AnimatedWebPImage::AppendChunk(const uint8* data, size_t input_byte) {
+void AnimatedWebPImage::AppendChunk(const uint8* data, size_t size) {
+  TRACE_EVENT0("cobalt::loader::image", "AnimatedWebPImage::AppendChunk()");
+  TRACK_MEMORY_SCOPE("Rendering");
   base::AutoLock lock(lock_);
 
-  data_buffer_.insert(data_buffer_.end(), data, data + input_byte);
+  data_buffer_.insert(data_buffer_.end(), data, data + size);
   WebPData webp_data = {&data_buffer_[0], data_buffer_.size()};
   WebPDemuxDelete(demux_);
   demux_ = WebPDemuxPartial(&webp_data, &demux_state_);
@@ -116,19 +128,38 @@
 }
 
 AnimatedWebPImage::~AnimatedWebPImage() {
+  TRACE_EVENT0("cobalt::loader::image",
+               "AnimatedWebPImage::~AnimatedWebPImage()");
   Stop();
-  base::AutoLock lock(lock_);
+  bool is_playing = false;
+  {
+    base::AutoLock lock(lock_);
+    is_playing = is_playing_;
+  }
+  if (is_playing) {
+    base::WaitableEvent fence(true /* manual reset */,
+                              false /* initially unsignaled */);
+    message_loop_->PostTask(FROM_HERE,
+                            base::Bind(&base::WaitableEvent::Signal,
+                                        base::Unretained(&fence)));
+    fence.Wait();
+  }
   WebPDemuxDelete(demux_);
 }
 
 void AnimatedWebPImage::StopInternal() {
-  is_playing_ = false;
-  decode_closure_.Cancel();
+  TRACE_EVENT0("cobalt::loader::image", "AnimatedWebPImage::StopInternal()");
+  DCHECK(message_loop_->BelongsToCurrentThread());
+  base::AutoLock lock(lock_);
+  if (!decode_closure_.callback().is_null()) {
+    is_playing_ = false;
+    decode_closure_.Cancel();
+  }
 }
 
 void AnimatedWebPImage::PlayInternal() {
+  TRACE_EVENT0("cobalt::loader::image", "AnimatedWebPImage::PlayInternal()");
   current_frame_time_ = base::TimeTicks::Now();
-  DCHECK(message_loop_);
   message_loop_->PostTask(
       FROM_HERE,
       base::Bind(&AnimatedWebPImage::DecodeFrames, base::Unretained(this)));
@@ -136,9 +167,12 @@
 
 void AnimatedWebPImage::DecodeFrames() {
   TRACE_EVENT0("cobalt::loader::image", "AnimatedWebPImage::DecodeFrames()");
+  TRACK_MEMORY_SCOPE("Rendering");
   DCHECK(is_playing_ && received_first_frame_);
   DCHECK(message_loop_->BelongsToCurrentThread());
 
+  base::AutoLock lock(lock_);
+
   if (decode_closure_.callback().is_null()) {
     decode_closure_.Reset(
         base::Bind(&AnimatedWebPImage::DecodeFrames, base::Unretained(this)));
@@ -156,7 +190,6 @@
   current_frame_index_ = next_frame_index_;
 
   // Set up the next time to call the decode callback.
-  base::AutoLock lock(lock_);
   if (is_playing_) {
     base::TimeDelta delay = next_frame_time_ - base::TimeTicks::Now();
     const base::TimeDelta min_delay =
@@ -182,7 +215,11 @@
 }  // namespace
 
 bool AnimatedWebPImage::DecodeOneFrame(int frame_index) {
-  base::AutoLock lock(lock_);
+  TRACE_EVENT0("cobalt::loader::image", "AnimatedWebPImage::DecodeOneFrame()");
+  TRACK_MEMORY_SCOPE("Rendering");
+  DCHECK(message_loop_->BelongsToCurrentThread());
+  lock_.AssertAcquired();
+
   WebPIterator webp_iterator;
   scoped_refptr<render_tree::Image> next_frame_image;
 
@@ -239,6 +276,7 @@
         new render_tree::CompositionNode(builder);
 
     current_canvas_ = resource_provider_->DrawOffscreenImage(root);
+    frame_provider_->SetFrame(current_canvas_);
   }
 
   if (webp_iterator.dispose_method == WEBP_MUX_DISPOSE_BACKGROUND) {
@@ -257,7 +295,12 @@
 }
 
 void AnimatedWebPImage::UpdateTimelineInfo() {
-  base::AutoLock lock(lock_);
+  TRACE_EVENT0("cobalt::loader::image",
+               "AnimatedWebPImage::UpdateTimelineInfo()");
+  TRACK_MEMORY_SCOPE("Rendering");
+  DCHECK(message_loop_->BelongsToCurrentThread());
+  lock_.AssertAcquired();
+
   base::TimeTicks current_time = base::TimeTicks::Now();
   next_frame_index_ = current_frame_index_ ? current_frame_index_ : 1;
   while (true) {
@@ -293,6 +336,8 @@
 
 scoped_ptr<render_tree::ImageData> AnimatedWebPImage::AllocateImageData(
     const math::Size& size) {
+  TRACE_EVENT0("cobalt::loader::image",
+               "AnimatedWebPImage::AllocateImageData()");
   TRACK_MEMORY_SCOPE("Rendering");
   scoped_ptr<render_tree::ImageData> image_data =
       resource_provider_->AllocateImageData(
diff --git a/src/cobalt/loader/image/animated_webp_image.h b/src/cobalt/loader/image/animated_webp_image.h
index e988519..179d42e 100644
--- a/src/cobalt/loader/image/animated_webp_image.h
+++ b/src/cobalt/loader/image/animated_webp_image.h
@@ -50,13 +50,13 @@
 
   bool IsOpaque() const OVERRIDE { return is_opaque_; }
 
-  scoped_refptr<render_tree::Image> GetFrame() OVERRIDE;
+  scoped_refptr<const FrameProvider> GetFrameProvider() OVERRIDE;
 
   void Play(const scoped_refptr<base::MessageLoopProxy>& message_loop) OVERRIDE;
 
   void Stop() OVERRIDE;
 
-  void AppendChunk(const uint8* data, size_t input_byte);
+  void AppendChunk(const uint8* data, size_t size);
 
  private:
   ~AnimatedWebPImage() OVERRIDE;
@@ -102,6 +102,7 @@
   // The original encoded data.
   std::vector<uint8> data_buffer_;
   scoped_refptr<render_tree::Image> current_canvas_;
+  scoped_refptr<FrameProvider> frame_provider_;
   base::Lock lock_;
 };
 
diff --git a/src/cobalt/loader/image/image.h b/src/cobalt/loader/image/image.h
index feb1bd3..fe77dac 100644
--- a/src/cobalt/loader/image/image.h
+++ b/src/cobalt/loader/image/image.h
@@ -19,6 +19,7 @@
 
 #include "base/memory/ref_counted.h"
 #include "base/message_loop_proxy.h"
+#include "base/synchronization/lock.h"
 #include "base/time.h"
 #include "cobalt/math/size_f.h"
 #include "cobalt/math/transform_2d.h"
@@ -77,13 +78,38 @@
 // when playing animation.
 class AnimatedImage : public Image {
  public:
+  // FrameProvider nested classes are used as "frame containers".  Typically
+  // they will be created by an AnimatedImage and have frames pushed into them
+  // by the creating AnimatedImage object.  They can be handed to consumers to
+  // have frames pulled out of them.  Its purpose is to allow AnimatedImage
+  // objects to be destroyed without affecting consumers of the FrameProvider.
+  // If the AnimatedImage is destroyed before the consumer is done pulling
+  // frames out of FrameProvider, they will simply be left with the last frame
+  // that was in there.
+  class FrameProvider : public base::RefCountedThreadSafe<FrameProvider> {
+   public:
+    void SetFrame(const scoped_refptr<render_tree::Image>& frame) {
+      base::AutoLock lock(mutex_);
+      frame_ = frame;
+    }
+
+    scoped_refptr<render_tree::Image> GetFrame() const {
+      base::AutoLock lock(mutex_);
+      return frame_;
+    }
+
+   private:
+    virtual ~FrameProvider() {}
+    friend class base::RefCountedThreadSafe<FrameProvider>;
+
+    mutable base::Lock mutex_;
+    scoped_refptr<render_tree::Image> frame_;
+  };
+
   bool IsAnimated() const OVERRIDE { return true; }
 
   bool IsOpaque() const OVERRIDE { return false; }
 
-  // Get the current frame. Implementation should be thread safe.
-  virtual scoped_refptr<render_tree::Image> GetFrame() = 0;
-
   // Start playing the animation, decoding on the given message loop.
   // Implementation should be thread safe.
   virtual void Play(
@@ -92,14 +118,21 @@
   // Stop playing the animation.
   virtual void Stop() = 0;
 
+  // Returns a FrameProvider object from which frames can be pulled out of.
+  // The AnimatedImage object is expected to push frames into the FrameProvider
+  // as it generates them.
+  virtual scoped_refptr<const FrameProvider> GetFrameProvider() = 0;
+
   // This callback is intended to be used in a render_tree::AnimateNode.
-  void AnimateCallback(const math::RectF& destination_rect,
-                       const math::Matrix3F& local_transform,
-                       render_tree::ImageNode::Builder* image_node_builder,
-                       base::TimeDelta time) {
+  static void AnimateCallback(
+      scoped_refptr<const FrameProvider> frame_provider,
+      const math::RectF& destination_rect,
+      const math::Matrix3F& local_transform,
+      render_tree::ImageNode::Builder* image_node_builder,
+      base::TimeDelta time) {
     UNREFERENCED_PARAMETER(time);
 
-    image_node_builder->source = GetFrame();
+    image_node_builder->source = frame_provider->GetFrame();
     image_node_builder->destination_rect = destination_rect;
     image_node_builder->local_transform = local_transform;
   }
diff --git a/src/cobalt/webdriver_benchmarks/tv_testcase_runner.py b/src/cobalt/webdriver_benchmarks/tv_testcase_runner.py
index 6f9981c..45c8556 100755
--- a/src/cobalt/webdriver_benchmarks/tv_testcase_runner.py
+++ b/src/cobalt/webdriver_benchmarks/tv_testcase_runner.py
@@ -134,8 +134,9 @@
       for command_line_arg in command_line_args:
         args.append("--" + command_line_arg)
     args.append("--enable_webdriver")
-    args.append("--null_savegame")
     args.append("--debug_console=off")
+    args.append("--disable_image_animations")
+    args.append("--null_savegame")
     args.append("--url=" + _default_url)
 
     self.launcher.SetArgs(args)