Mozart: Enhance tile app to test various sync behaviors.

By passing URL arguments, tile can now be made to construct its
scene using different version modes, combinator modes, or orientations.
Note that the "views=" key must now be used to specify the set of
view URLs.  See README for details.

BUG=
R=abarth@google.com

Review URL: https://codereview.chromium.org/1869263002 .
diff --git a/examples/ui/tile/README.md b/examples/ui/tile/README.md
index 86ec7a8..4d05618 100644
--- a/examples/ui/tile/README.md
+++ b/examples/ui/tile/README.md
@@ -8,8 +8,31 @@
 
 ## USAGE
 
-  out/Debug/mojo_shell "mojo:launcher mojo:tile_view?<app1>[,<app2>[,...]]"
+Specify the urls of the views to embed as a comma-delimited query string.
 
-Specify the urls of the views to embed as a comma-delimited query parameter.
+  out/Debug/mojo_shell "mojo:launcher mojo:tile_view?views=<app1>[,<app2>[,...]]"
 
-  eg. out/Debug/mojo_shell "mojo:launcher mojo:tile_view?mojo:spinning_cube_view,mojo:noodles_view"
+  eg. out/Debug/mojo_shell "mojo:launcher mojo:tile_view?views=mojo:spinning_cube_view,mojo:noodles_view"
+
+The query string may also encode tiling options by appending parameters to
+the end of the query string.
+
+  Version mode for child views:
+
+    &vm=any   : composite most recent unblocked version of each child (default)
+    &vm=exact : composite only exact version of child specified during
+                layout (forces frame-level synchronization of resizing)
+
+  Combinator mode for child views:
+
+    &cm=merge : use MERGE combinator (default)
+    &cm=prune : use PRUNE combinator
+    &cm=flash : use FALLBACK combinator with solid red color as
+                alternate content
+    &cm=dim   : use FALLBACK combinator with a dimmed layer containing the
+                most recent unblocked version of the child
+
+  Orientation mode for child views:
+
+    &o=h : tile children horizontally
+    &o=v : tile children vertically
diff --git a/examples/ui/tile/tile_app.cc b/examples/ui/tile/tile_app.cc
index 23f1b30..31a721e 100644
--- a/examples/ui/tile/tile_app.cc
+++ b/examples/ui/tile/tile_app.cc
@@ -2,12 +2,10 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include <vector>
-
 #include "base/strings/string_split.h"
 #include "examples/ui/tile/tile_app.h"
 #include "examples/ui/tile/tile_view.h"
-#include "url/gurl.h"
+#include "url/url_parse.h"
 
 namespace examples {
 
@@ -20,17 +18,61 @@
     mojo::InterfaceRequest<mojo::ui::ViewOwner> view_owner_request,
     mojo::InterfaceRequest<mojo::ServiceProvider> services,
     mojo::InterfaceHandle<mojo::ServiceProvider> exposed_services) {
-  GURL url(connection_url);
-  std::vector<std::string> view_urls;
-  base::SplitString(url.query(), ',', &view_urls);
-
-  if (view_urls.empty()) {
-    LOG(ERROR) << "Must supply comma-delimited URLs of mojo views to tile as a "
-                  "query parameter.";
+  TileParams params;
+  if (!ParseParams(connection_url, &params)) {
+    LOG(ERROR) << "Missing or invalid URL parameters.  See README.";
     return;
   }
 
-  new TileView(app_impl(), view_owner_request.Pass(), view_urls);
+  new TileView(app_impl(), view_owner_request.Pass(), params);
+}
+
+bool TileApp::ParseParams(const std::string& connection_url,
+                          TileParams* params) {
+  url::Parsed parsed;
+  url::ParseStandardURL(connection_url.c_str(), connection_url.size(), &parsed);
+  url::Component query = parsed.query;
+
+  for (;;) {
+    url::Component key, value;
+    if (!url::ExtractQueryKeyValue(connection_url.c_str(), &query, &key,
+                                   &value))
+      break;
+    std::string key_str(connection_url, key.begin, key.len);
+    std::string value_str(connection_url, value.begin, value.len);
+    if (key_str == "views") {
+      base::SplitString(value_str, ',', &params->view_urls);
+    } else if (key_str == "vm") {
+      if (value_str == "any")
+        params->version_mode = TileParams::VersionMode::kAny;
+      else if (value_str == "exact")
+        params->version_mode = TileParams::VersionMode::kExact;
+      else
+        return false;
+    } else if (key_str == "cm") {
+      if (value_str == "merge")
+        params->combinator_mode = TileParams::CombinatorMode::kMerge;
+      else if (value_str == "prune")
+        params->combinator_mode = TileParams::CombinatorMode::kPrune;
+      else if (value_str == "flash")
+        params->combinator_mode = TileParams::CombinatorMode::kFallbackFlash;
+      else if (value_str == "dim")
+        params->combinator_mode = TileParams::CombinatorMode::kFallbackDim;
+      else
+        return false;
+    } else if (key_str == "o") {
+      if (value_str == "h")
+        params->orientation_mode = TileParams::OrientationMode::kHorizontal;
+      else if (value_str == "v")
+        params->orientation_mode = TileParams::OrientationMode::kVertical;
+      else
+        return false;
+    } else {
+      return false;
+    }
+  }
+
+  return !params->view_urls.empty();
 }
 
 }  // namespace examples
diff --git a/examples/ui/tile/tile_app.h b/examples/ui/tile/tile_app.h
index 05060c6..325a21f 100644
--- a/examples/ui/tile/tile_app.h
+++ b/examples/ui/tile/tile_app.h
@@ -9,6 +9,8 @@
 
 namespace examples {
 
+struct TileParams;
+
 class TileApp : public mojo::ui::ViewProviderApp {
  public:
   TileApp();
@@ -21,6 +23,8 @@
       mojo::InterfaceHandle<mojo::ServiceProvider> exposed_services) override;
 
  private:
+  bool ParseParams(const std::string& connection_url, TileParams* params);
+
   DISALLOW_COPY_AND_ASSIGN(TileApp);
 };
 
diff --git a/examples/ui/tile/tile_view.cc b/examples/ui/tile/tile_view.cc
index a5875b1..ced3af7 100644
--- a/examples/ui/tile/tile_view.cc
+++ b/examples/ui/tile/tile_view.cc
@@ -16,16 +16,20 @@
 constexpr uint32_t kViewNodeIdBase = 100;
 constexpr uint32_t kViewNodeIdSpacing = 100;
 constexpr uint32_t kViewSceneNodeIdOffset = 1;
-constexpr uint32_t kViewFallbackSceneNodeIdOffset = 2;
-constexpr uint32_t kViewFallbackColorNodeIdOffset = 3;
+constexpr uint32_t kViewFallbackColorNodeIdOffset = 2;
+constexpr uint32_t kViewFallbackDimLayerNodeIdOffset = 3;
+constexpr uint32_t kViewFallbackDimSceneNodeIdOffset = 4;
 }  // namespace
 
+TileParams::TileParams() {}
+
+TileParams::~TileParams() {}
+
 TileView::TileView(
     mojo::ApplicationImpl* app_impl,
     mojo::InterfaceRequest<mojo::ui::ViewOwner> view_owner_request,
-    const std::vector<std::string>& view_urls)
-    : BaseView(app_impl, view_owner_request.Pass(), "Tile"),
-      view_urls_(view_urls) {
+    const TileParams& params)
+    : BaseView(app_impl, view_owner_request.Pass(), "Tile"), params_(params) {
   ConnectViews();
 }
 
@@ -33,7 +37,7 @@
 
 void TileView::ConnectViews() {
   uint32_t child_key = 0;
-  for (const auto& url : view_urls_) {
+  for (const auto& url : params_.view_urls) {
     // Start connecting to the view provider.
     mojo::ui::ViewProviderPtr provider;
     app_impl()->ConnectToService(url, &provider);
@@ -82,33 +86,44 @@
   // Layout all children in a row.
   if (!views_.empty()) {
     const mojo::Size& size = *properties()->view_layout->size;
+    const bool vertical =
+        (params_.orientation_mode == TileParams::OrientationMode::kVertical);
+
     uint32_t index = 0;
-    uint32_t base_width = size.width / views_.size();
-    uint32_t excess_width = size.width % views_.size();
-    uint32_t x = 0;
+    uint32_t space = vertical ? size.height : size.width;
+    uint32_t base = space / views_.size();
+    uint32_t excess = space % views_.size();
+    uint32_t offset = 0;
     for (auto it = views_.begin(); it != views_.end(); ++it, ++index) {
       ViewData* view_data = it->second.get();
 
       // Distribute any excess width among the leading children.
-      uint32_t child_width = base_width;
-      if (excess_width) {
-        child_width++;
-        excess_width--;
+      uint32_t extent = base;
+      if (excess) {
+        extent++;
+        excess--;
       }
-      uint32_t child_height = size.height;
-      uint32_t child_x = x;
-      x += child_width;
 
-      view_data->layout_bounds.x = child_x;
-      view_data->layout_bounds.y = 0;
-      view_data->layout_bounds.width = child_width;
-      view_data->layout_bounds.height = child_height;
+      if (vertical) {
+        view_data->layout_bounds.x = 0;
+        view_data->layout_bounds.y = offset;
+        view_data->layout_bounds.width = size.width;
+        view_data->layout_bounds.height = extent;
+      } else {
+        view_data->layout_bounds.x = offset;
+        view_data->layout_bounds.y = 0;
+        view_data->layout_bounds.width = extent;
+        view_data->layout_bounds.height = size.height;
+      }
+      offset += extent;
 
       auto view_properties = mojo::ui::ViewProperties::New();
       view_properties->view_layout = mojo::ui::ViewLayout::New();
       view_properties->view_layout->size = mojo::Size::New();
-      view_properties->view_layout->size->width = child_width;
-      view_properties->view_layout->size->height = child_height;
+      view_properties->view_layout->size->width =
+          view_data->layout_bounds.width;
+      view_properties->view_layout->size->height =
+          view_data->layout_bounds.height;
 
       if (view_data->view_properties.Equals(view_properties))
         continue;  // no layout work to do
@@ -140,7 +155,6 @@
         kViewResourceIdBase + view_data.key * kViewResourceIdSpacing;
     const uint32_t container_node_id =
         kViewNodeIdBase + view_data.key * kViewNodeIdSpacing;
-    const uint32_t scene_node_id = container_node_id + kViewSceneNodeIdOffset;
 
     mojo::RectF extent;
     extent.width = view_data.layout_bounds.width;
@@ -155,8 +169,6 @@
     SetTranslationTransform(container_node->content_transform.get(),
                             view_data.layout_bounds.x,
                             view_data.layout_bounds.y, 0.f);
-    container_node->combinator =
-        mojo::gfx::composition::Node::Combinator::FALLBACK;
 
     // If we have the view, add it to the scene.
     if (view_data.view_info) {
@@ -166,41 +178,64 @@
           view_data.view_info->scene_token.Clone();
       update->resources.insert(scene_resource_id, scene_resource.Pass());
 
+      const uint32_t scene_node_id = container_node_id + kViewSceneNodeIdOffset;
       auto scene_node = mojo::gfx::composition::Node::New();
       scene_node->op = mojo::gfx::composition::NodeOp::New();
       scene_node->op->set_scene(mojo::gfx::composition::SceneNodeOp::New());
       scene_node->op->get_scene()->scene_resource_id = scene_resource_id;
-      scene_node->op->get_scene()->scene_version = view_data.scene_version;
+      if (params_.version_mode == TileParams::VersionMode::kExact)
+        scene_node->op->get_scene()->scene_version = view_data.scene_version;
       update->nodes.insert(scene_node_id, scene_node.Pass());
       container_node->child_node_ids.push_back(scene_node_id);
     }
 
-    // TODO(jeffbrown): Reenable once everything works or make configurable.
-    if (false) {
-      // Add the fallback scene content, use last available version.
-      const uint32_t fallback_node_id =
-          container_node_id + kViewFallbackSceneNodeIdOffset;
-      auto fallback_node = mojo::gfx::composition::Node::New();
-      fallback_node->op = mojo::gfx::composition::NodeOp::New();
-      fallback_node->op->set_scene(mojo::gfx::composition::SceneNodeOp::New());
-      fallback_node->op->get_scene()->scene_resource_id = scene_resource_id;
-      update->nodes.insert(fallback_node_id, fallback_node.Pass());
-      container_node->child_node_ids.push_back(fallback_node_id);
-    }
-    if (false) {
-      // Add the fallback color content, fill with solid color.
-      const uint32_t fallback_node_id =
+    if (params_.combinator_mode == TileParams::CombinatorMode::kPrune) {
+      container_node->combinator =
+          mojo::gfx::composition::Node::Combinator::PRUNE;
+    } else if (params_.combinator_mode ==
+               TileParams::CombinatorMode::kFallbackFlash) {
+      container_node->combinator =
+          mojo::gfx::composition::Node::Combinator::FALLBACK;
+
+      const uint32_t color_node_id =
           container_node_id + kViewFallbackColorNodeIdOffset;
-      auto fallback_node = mojo::gfx::composition::Node::New();
-      fallback_node->op = mojo::gfx::composition::NodeOp::New();
-      fallback_node->op->set_rect(mojo::gfx::composition::RectNodeOp::New());
-      fallback_node->op->get_rect()->content_rect = extent.Clone();
-      fallback_node->op->get_rect()->color =
-          mojo::gfx::composition::Color::New();
-      fallback_node->op->get_rect()->color->red = 255;
-      fallback_node->op->get_rect()->color->alpha = 255;
-      update->nodes.insert(fallback_node_id, fallback_node.Pass());
-      container_node->child_node_ids.push_back(fallback_node_id);
+      auto color_node = mojo::gfx::composition::Node::New();
+      color_node->op = mojo::gfx::composition::NodeOp::New();
+      color_node->op->set_rect(mojo::gfx::composition::RectNodeOp::New());
+      color_node->op->get_rect()->content_rect = extent.Clone();
+      color_node->op->get_rect()->color = mojo::gfx::composition::Color::New();
+      color_node->op->get_rect()->color->red = 255;
+      color_node->op->get_rect()->color->alpha = 255;
+      update->nodes.insert(color_node_id, color_node.Pass());
+      container_node->child_node_ids.push_back(color_node_id);
+    } else if (params_.combinator_mode ==
+               TileParams::CombinatorMode::kFallbackDim) {
+      container_node->combinator =
+          mojo::gfx::composition::Node::Combinator::FALLBACK;
+
+      const uint32_t dim_node_id =
+          container_node_id + kViewFallbackDimLayerNodeIdOffset;
+      auto dim_node = mojo::gfx::composition::Node::New();
+      dim_node->combinator = mojo::gfx::composition::Node::Combinator::PRUNE;
+      dim_node->op = mojo::gfx::composition::NodeOp::New();
+      dim_node->op->set_layer(mojo::gfx::composition::LayerNodeOp::New());
+      dim_node->op->get_layer()->layer_rect = extent.Clone();
+      dim_node->op->get_layer()->blend = mojo::gfx::composition::Blend::New();
+      dim_node->op->get_layer()->blend->alpha = 200;
+
+      if (view_data.view_info) {
+        const uint32_t scene_node_id =
+            container_node_id + kViewFallbackDimSceneNodeIdOffset;
+        auto scene_node = mojo::gfx::composition::Node::New();
+        scene_node->op = mojo::gfx::composition::NodeOp::New();
+        scene_node->op->set_scene(mojo::gfx::composition::SceneNodeOp::New());
+        scene_node->op->get_scene()->scene_resource_id = scene_resource_id;
+        update->nodes.insert(scene_node_id, scene_node.Pass());
+        dim_node->child_node_ids.push_back(scene_node_id);
+      }
+
+      update->nodes.insert(dim_node_id, dim_node.Pass());
+      container_node->child_node_ids.push_back(dim_node_id);
     }
 
     // Add the container.
diff --git a/examples/ui/tile/tile_view.h b/examples/ui/tile/tile_view.h
index f3f4df3..3ea7bbd 100644
--- a/examples/ui/tile/tile_view.h
+++ b/examples/ui/tile/tile_view.h
@@ -7,17 +7,44 @@
 
 #include <map>
 #include <memory>
+#include <vector>
 
 #include "mojo/services/ui/views/interfaces/view_provider.mojom.h"
 #include "mojo/ui/base_view.h"
 
 namespace examples {
 
+struct TileParams {
+  enum class VersionMode {
+    kAny,    // specify |kSceneVersionNone|
+    kExact,  // specify exact version
+  };
+  enum class CombinatorMode {
+    kMerge,          // use merge combinator
+    kPrune,          // use prune combinator
+    kFallbackFlash,  // use fallback combinator with red flash
+    kFallbackDim,    // use fallback combinator with old content dimmed
+  };
+  enum class OrientationMode {
+    kHorizontal,
+    kVertical,
+  };
+
+  TileParams();
+  ~TileParams();
+
+  VersionMode version_mode = VersionMode::kAny;
+  CombinatorMode combinator_mode = CombinatorMode::kPrune;
+  OrientationMode orientation_mode = OrientationMode::kHorizontal;
+
+  std::vector<std::string> view_urls;
+};
+
 class TileView : public mojo::ui::BaseView {
  public:
   TileView(mojo::ApplicationImpl* app_impl_,
            mojo::InterfaceRequest<mojo::ui::ViewOwner> view_owner_request,
-           const std::vector<std::string>& view_urls);
+           const TileParams& tile_params);
 
   ~TileView() override;
 
@@ -47,7 +74,7 @@
 
   void OnFrameSubmitted();
 
-  std::vector<std::string> view_urls_;
+  TileParams params_;
   std::map<uint32_t, std::unique_ptr<ViewData>> views_;
 
   DISALLOW_COPY_AND_ASSIGN(TileView);