Drop extensions use of legacy IPC serialization for SkBitmap, use mojo

This converts to using the skia::mojom::Bitmap serialize functions to
turn an SkBitmap into a string and back for putting into a base::Value
and sending over IPC.

This is a reland as the original CL would fail to serialize correctly
for large bitmaps, due to Bitmap mojoms trying to use a shared memory
handle, which does not encode/decode correctly as a string. Switched to
using InlineBitmap which will always serialize to bytes in the string
itself, and added a unit test for this.

Also a bonus browser test for the setIcon path from the extension
process.

[email protected]
[email protected]

Bug: 1144462
Change-Id: Idc2cfb0d70335557464be2abdeaa0bad11d38e9a
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2527518
Commit-Queue: danakj <[email protected]>
Reviewed-by: Reilly Grant <[email protected]>
Cr-Commit-Position: refs/heads/master@{#826091}
diff --git a/chrome/browser/extensions/api/DEPS b/chrome/browser/extensions/api/DEPS
index 9898aa8..88f2d8a 100644
--- a/chrome/browser/extensions/api/DEPS
+++ b/chrome/browser/extensions/api/DEPS
@@ -12,6 +12,7 @@
     "+chrome/browser/ui/views/frame",
     "+components/captive_portal",
     "+components/web_package/test_support",
+    "+skia/public/mojom/bitmap.mojom.h",
   ],
   "tls_socket_unittest\.cc": [
     "+services/network/network_context.h",
diff --git a/chrome/browser/extensions/api/declarative_content/content_action_unittest.cc b/chrome/browser/extensions/api/declarative_content/content_action_unittest.cc
index 3a8724a..a45f0f1 100644
--- a/chrome/browser/extensions/api/declarative_content/content_action_unittest.cc
+++ b/chrome/browser/extensions/api/declarative_content/content_action_unittest.cc
@@ -24,12 +24,12 @@
 #include "extensions/common/extension.h"
 #include "extensions/common/extension_builder.h"
 #include "extensions/common/value_builder.h"
-#include "ipc/ipc_message_utils.h"
+#include "mojo/public/cpp/base/big_buffer.h"
+#include "skia/public/mojom/bitmap.mojom.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "third_party/skia/include/core/SkBitmap.h"
 #include "ui/gfx/image/image.h"
-#include "ui/gfx/ipc/skia/gfx_skia_param_traits.h"
 
 namespace extensions {
 namespace {
@@ -212,65 +212,89 @@
                     ExtensionBuilder::ActionType::PAGE_ACTION));
 
 TEST(DeclarativeContentActionTest, SetIcon) {
-  TestExtensionEnvironment env;
-  content::RenderViewHostTestEnabler rvh_enabler;
+  enum Mode { Base64, Mojo, MojoHuge };
+  for (Mode mode : {Base64, Mojo, MojoHuge}) {
+    SCOPED_TRACE(mode);
 
-  // Simulate the process of passing ImageData to SetIcon::Create.
-  SkBitmap bitmap;
-  EXPECT_TRUE(bitmap.tryAllocN32Pixels(19, 19));
-  // Fill the bitmap with red pixels.
-  bitmap.eraseARGB(255, 255, 0, 0);
-  IPC::Message bitmap_pickle;
-  IPC::WriteParam(&bitmap_pickle, bitmap);
-  std::string binary_data = std::string(
-      static_cast<const char*>(bitmap_pickle.data()), bitmap_pickle.size());
-  std::string data64;
-  base::Base64Encode(binary_data, &data64);
+    TestExtensionEnvironment env;
+    content::RenderViewHostTestEnabler rvh_enabler;
 
-  std::unique_ptr<base::DictionaryValue> dict =
-      DictionaryBuilder()
-          .Set("instanceType", "declarativeContent.SetIcon")
-          .Set("imageData", DictionaryBuilder().Set("19", data64).Build())
-          .Build();
+    // Simulate the process of passing ImageData to SetIcon::Create.
+    SkBitmap bitmap;
+    EXPECT_TRUE(bitmap.tryAllocN32Pixels(19, 19));
+    bitmap.eraseARGB(255, 255, 0, 0);
 
-  const Extension* extension = env.MakeExtension(
-      ParseJson(R"({"page_action": {"default_title": "Extension"}})"));
-  base::HistogramTester histogram_tester;
-  TestingProfile profile;
-  std::string error;
-  ContentAction::SetAllowInvisibleIconsForTest(false);
-  std::unique_ptr<const ContentAction> result =
-      ContentAction::Create(&profile, extension, *dict, &error);
-  ContentAction::SetAllowInvisibleIconsForTest(true);
-  EXPECT_EQ("", error);
-  ASSERT_TRUE(result.get());
-  EXPECT_THAT(
-      histogram_tester.GetAllSamples("Extensions.DeclarativeSetIconWasVisible"),
-      testing::ElementsAre(base::Bucket(1, 1)));
-  EXPECT_THAT(histogram_tester.GetAllSamples(
-                  "Extensions.DeclarativeSetIconWasVisibleRendered"),
-              testing::ElementsAre(base::Bucket(1, 1)));
-  histogram_tester.ExpectUniqueSample(
-      "Extensions.DeclarativeContentActionCreated", ContentActionType::kSetIcon,
-      1);
-  histogram_tester.ExpectTotalCount(
-      "Extensions.DeclarativeContentActionCreated", 1);
+    DictionaryBuilder builder;
+    builder.Set("instanceType", "declarativeContent.SetIcon");
+    switch (mode) {
+      case Base64: {
+        std::string data64 =
+            base::Base64Encode(skia::mojom::InlineBitmap::Serialize(&bitmap));
+        builder.Set("imageData", DictionaryBuilder().Set("19", data64).Build());
+        break;
+      }
+      case Mojo: {
+        std::vector<uint8_t> s = skia::mojom::InlineBitmap::Serialize(&bitmap);
+        builder.Set("imageData", DictionaryBuilder().Set("19", s).Build());
+        break;
+      }
+      case MojoHuge: {
+        // Normal skia::mojom::Bitmaps would serialize as a SharedMemory handle,
+        // which is not valid when serializing to a string. We use InlineBitmap
+        // instead, and this case verifies it does the right thing for a large
+        // image.
+        const int dimension =
+            std::ceil(std::sqrt(mojo_base::BigBuffer::kMaxInlineBytes));
+        EXPECT_TRUE(bitmap.tryAllocN32Pixels(dimension / 4 + 1, dimension));
+        EXPECT_GT(bitmap.computeByteSize(),
+                  mojo_base::BigBuffer::kMaxInlineBytes);
+        bitmap.eraseARGB(255, 255, 0, 0);
+        std::vector<uint8_t> s = skia::mojom::InlineBitmap::Serialize(&bitmap);
+        builder.Set("imageData", DictionaryBuilder().Set("19", s).Build());
+        break;
+      }
+    }
+    std::unique_ptr<base::DictionaryValue> dict = builder.Build();
 
-  ExtensionAction* action = ExtensionActionManager::Get(env.profile())
-                                ->GetExtensionAction(*extension);
-  std::unique_ptr<content::WebContents> contents = env.MakeTab();
-  const int tab_id = ExtensionTabUtil::GetTabId(contents.get());
-  EXPECT_FALSE(action->GetIsVisible(tab_id));
-  ContentAction::ApplyInfo apply_info = {
-    extension, env.profile(), contents.get(), 100
-  };
+    const Extension* extension = env.MakeExtension(
+        ParseJson(R"({"page_action": {"default_title": "Extension"}})"));
+    base::HistogramTester histogram_tester;
+    TestingProfile profile;
+    std::string error;
+    ContentAction::SetAllowInvisibleIconsForTest(false);
+    std::unique_ptr<const ContentAction> result =
+        ContentAction::Create(&profile, extension, *dict, &error);
+    ContentAction::SetAllowInvisibleIconsForTest(true);
+    EXPECT_EQ("", error);
+    ASSERT_TRUE(result.get());
+    EXPECT_THAT(histogram_tester.GetAllSamples(
+                    "Extensions.DeclarativeSetIconWasVisible"),
+                testing::ElementsAre(base::Bucket(1, 1)));
+    EXPECT_THAT(histogram_tester.GetAllSamples(
+                    "Extensions.DeclarativeSetIconWasVisibleRendered"),
+                testing::ElementsAre(base::Bucket(1, 1)));
+    histogram_tester.ExpectUniqueSample(
+        "Extensions.DeclarativeContentActionCreated",
+        ContentActionType::kSetIcon, 1);
+    histogram_tester.ExpectTotalCount(
+        "Extensions.DeclarativeContentActionCreated", 1);
 
-  // The declarative icon shouldn't exist unless the content action is applied.
-  EXPECT_TRUE(action->GetDeclarativeIcon(tab_id).IsEmpty());
-  result->Apply(apply_info);
-  EXPECT_FALSE(action->GetDeclarativeIcon(tab_id).IsEmpty());
-  result->Revert(apply_info);
-  EXPECT_TRUE(action->GetDeclarativeIcon(tab_id).IsEmpty());
+    ExtensionAction* action = ExtensionActionManager::Get(env.profile())
+                                  ->GetExtensionAction(*extension);
+    std::unique_ptr<content::WebContents> contents = env.MakeTab();
+    const int tab_id = ExtensionTabUtil::GetTabId(contents.get());
+    EXPECT_FALSE(action->GetIsVisible(tab_id));
+    ContentAction::ApplyInfo apply_info = {extension, env.profile(),
+                                           contents.get(), 100};
+
+    // The declarative icon shouldn't exist unless the content action is
+    // applied.
+    EXPECT_TRUE(action->GetDeclarativeIcon(tab_id).IsEmpty());
+    result->Apply(apply_info);
+    EXPECT_FALSE(action->GetDeclarativeIcon(tab_id).IsEmpty());
+    result->Revert(apply_info);
+    EXPECT_TRUE(action->GetDeclarativeIcon(tab_id).IsEmpty());
+  }
 }
 
 TEST(DeclarativeContentActionTest, SetInvisibleIcon) {
@@ -283,12 +307,8 @@
   uint32_t* pixels = bitmap.getAddr32(0, 0);
   // Set a single pixel, which isn't enough to consider the icon visible.
   pixels[0] = SkColorSetARGB(255, 255, 0, 0);
-  IPC::Message bitmap_pickle;
-  IPC::WriteParam(&bitmap_pickle, bitmap);
-  std::string binary_data = std::string(
-      static_cast<const char*>(bitmap_pickle.data()), bitmap_pickle.size());
-  std::string data64;
-  base::Base64Encode(binary_data, &data64);
+  std::string data64 =
+      base::Base64Encode(skia::mojom::InlineBitmap::Serialize(&bitmap));
 
   std::unique_ptr<base::DictionaryValue> dict =
       DictionaryBuilder()
diff --git a/chrome/browser/extensions/api/extension_action/browser_action_apitest.cc b/chrome/browser/extensions/api/extension_action/browser_action_apitest.cc
index 71ff951..ef5d846 100644
--- a/chrome/browser/extensions/api/extension_action/browser_action_apitest.cc
+++ b/chrome/browser/extensions/api/extension_action/browser_action_apitest.cc
@@ -565,6 +565,40 @@
   EXPECT_EQ("hi!", GetBrowserActionsBar()->GetTooltip(0));
 }
 
+// Test that calling chrome.browserAction.setIcon() can set the icon for
+// extension.
+IN_PROC_BROWSER_TEST_P(BrowserActionApiLazyTest, BrowserActionSetIcon) {
+  ASSERT_TRUE(RunExtensionTest("browser_action/set_icon")) << message_;
+  const Extension* extension = GetSingleLoadedExtension();
+  ASSERT_TRUE(extension) << message_;
+
+  int tab_id = ExtensionTabUtil::GetTabId(
+      browser()->tab_strip_model()->GetActiveWebContents());
+
+  ExtensionAction* browser_action = GetBrowserAction(browser(), *extension);
+  ASSERT_TRUE(browser_action)
+      << "Browser action test extension should have a browser action.";
+
+  EXPECT_FALSE(browser_action->default_icon());
+  EXPECT_EQ(0u,
+            browser_action->GetExplicitlySetIcon(tab_id).RepresentationCount());
+
+  // Simulate a click on the browser action icon. The onClicked handler will
+  // call setIcon().
+  {
+    ResultCatcher catcher;
+    GetBrowserActionsBar()->Press(0);
+    ASSERT_TRUE(catcher.GetNextResult());
+  }
+
+  // The call to setIcon in background.html set an icon, so the
+  // current tab's setting should have changed, but the default setting
+  // should not have changed.
+  EXPECT_FALSE(browser_action->default_icon());
+  EXPECT_EQ(1u,
+            browser_action->GetExplicitlySetIcon(tab_id).RepresentationCount());
+}
+
 // Test that calling chrome.browserAction.setPopup() can enable and change
 // a popup.
 IN_PROC_BROWSER_TEST_P(BrowserActionApiLazyTest, BrowserActionAddPopup) {
diff --git a/chrome/test/data/extensions/api_test/browser_action/set_icon/background.js b/chrome/test/data/extensions/api_test/browser_action/set_icon/background.js
new file mode 100644
index 0000000..b6e3953
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/browser_action/set_icon/background.js
@@ -0,0 +1,24 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+function getImageData() {
+  var canvas = document.createElement("canvas");
+  var ctx = canvas.getContext("2d");
+
+  ctx.fillStyle = "green";
+  ctx.fillRect(10, 10, 100, 100);
+
+  return ctx.getImageData(50, 50, 100, 100);
+}
+
+chrome.tabs.getSelected(null, function(tab) {
+  // When the browser action is clicked, add an icon.
+  chrome.browserAction.onClicked.addListener(function(tab) {
+    chrome.browserAction.setIcon({
+      imageData: getImageData()
+    });
+    chrome.test.notifyPass();
+  });
+  chrome.test.notifyPass();
+});
diff --git a/chrome/test/data/extensions/api_test/browser_action/set_icon/manifest.json b/chrome/test/data/extensions/api_test/browser_action/set_icon/manifest.json
new file mode 100644
index 0000000..aa9f00c
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/browser_action/set_icon/manifest.json
@@ -0,0 +1,14 @@
+{
+  "name": "A browser action to test chrome.browserAction.setIcon()",
+  "version": "1.0",
+  "manifest_version": 2,
+  "background": {
+    "scripts": ["background.js"]
+  },
+  "permissions": [
+    "tabs", "http://*/*"
+  ],
+  "browser_action": {
+    "default_title": "Test setIcon()"
+  }
+}
diff --git a/extensions/DEPS b/extensions/DEPS
index c37c9ba3..4d8fbad 100644
--- a/extensions/DEPS
+++ b/extensions/DEPS
@@ -18,6 +18,7 @@
   "+extensions/test",
   "+mojo/public",
   "+services/service_manager/public",
+  "+skia/public/mojom",
   "+testing",
   "+third_party/blink/public/common/loader/url_loader_throttle.h",
   "+third_party/skia/include",
diff --git a/extensions/browser/extension_action.cc b/extensions/browser/extension_action.cc
index 05a4547..a6564858 100644
--- a/extensions/browser/extension_action.cc
+++ b/extensions/browser/extension_action.cc
@@ -17,8 +17,7 @@
 #include "extensions/common/extension_icon_set.h"
 #include "extensions/common/manifest_handlers/icons_handler.h"
 #include "extensions/grit/extensions_browser_resources.h"
-#include "ipc/ipc_message.h"
-#include "ipc/ipc_message_utils.h"
+#include "skia/public/mojom/bitmap.mojom.h"
 #include "third_party/skia/include/core/SkBitmap.h"
 #include "third_party/skia/include/core/SkCanvas.h"
 #include "third_party/skia/include/core/SkPaint.h"
@@ -32,7 +31,6 @@
 #include "ui/gfx/image/image.h"
 #include "ui/gfx/image/image_skia.h"
 #include "ui/gfx/image/image_skia_source.h"
-#include "ui/gfx/ipc/skia/gfx_skia_param_traits.h"
 #include "ui/gfx/skbitmap_operations.h"
 #include "url/gurl.h"
 
@@ -123,23 +121,23 @@
     gfx::ImageSkia* icon) {
   for (base::DictionaryValue::Iterator iter(dict); !iter.IsAtEnd();
        iter.Advance()) {
-    std::string binary_string64;
-    IPC::Message pickle;
+    std::string byte_string;
+    std::string base64_string;
+    const void* bytes = nullptr;
+    size_t num_bytes = 0;
     if (iter.value().is_blob()) {
-      pickle = IPC::Message(
-          reinterpret_cast<const char*>(iter.value().GetBlob().data()),
-          iter.value().GetBlob().size());
-    } else if (iter.value().GetAsString(&binary_string64)) {
-      std::string binary_string;
-      if (!base::Base64Decode(binary_string64, &binary_string))
+      bytes = iter.value().GetBlob().data();
+      num_bytes = iter.value().GetBlob().size();
+    } else if (iter.value().GetAsString(&base64_string)) {
+      if (!base::Base64Decode(base64_string, &byte_string))
         return IconParseResult::kDecodeFailure;
-      pickle = IPC::Message(binary_string.c_str(), binary_string.length());
+      bytes = byte_string.c_str();
+      num_bytes = byte_string.length();
     } else {
       continue;
     }
-    base::PickleIterator pickle_iter(pickle);
     SkBitmap bitmap;
-    if (!IPC::ReadParam(&pickle, &pickle_iter, &bitmap))
+    if (!skia::mojom::InlineBitmap::Deserialize(bytes, num_bytes, &bitmap))
       return IconParseResult::kUnpickleFailure;
     CHECK(!bitmap.isNull());
 
diff --git a/extensions/common/extension_utility_types.h b/extensions/common/extension_utility_types.h
index 941a338..07a5d1e 100644
--- a/extensions/common/extension_utility_types.h
+++ b/extensions/common/extension_utility_types.h
@@ -8,8 +8,6 @@
 #include <tuple>
 #include <vector>
 
-#include "ui/gfx/ipc/skia/gfx_skia_param_traits.h"
-
 class SkBitmap;
 
 namespace base {
diff --git a/extensions/renderer/set_icon_natives.cc b/extensions/renderer/set_icon_natives.cc
index 0d008b4f73..7af1f5f 100644
--- a/extensions/renderer/set_icon_natives.cc
+++ b/extensions/renderer/set_icon_natives.cc
@@ -13,10 +13,9 @@
 #include "base/bind.h"
 #include "base/macros.h"
 #include "base/strings/string_number_conversions.h"
-#include "content/public/common/common_param_traits.h"
 #include "extensions/renderer/script_context.h"
 #include "gin/data_object_builder.h"
-#include "ipc/ipc_message_utils.h"
+#include "skia/public/mojom/bitmap.mojom.h"
 #include "third_party/blink/public/web/web_array_buffer_converter.h"
 #include "third_party/skia/include/core/SkBitmap.h"
 
@@ -144,11 +143,9 @@
   }
 
   // Construct the Value object.
-  IPC::Message bitmap_pickle;
-  IPC::WriteParam(&bitmap_pickle, bitmap);
-  blink::WebArrayBuffer buffer =
-      blink::WebArrayBuffer::Create(bitmap_pickle.size(), 1);
-  memcpy(buffer.Data(), bitmap_pickle.data(), bitmap_pickle.size());
+  std::vector<uint8_t> s = skia::mojom::InlineBitmap::Serialize(&bitmap);
+  blink::WebArrayBuffer buffer = blink::WebArrayBuffer::Create(s.size(), 1);
+  memcpy(buffer.Data(), s.data(), s.size());
   *image_data_bitmap = blink::WebArrayBufferConverter::ToV8Value(
       &buffer, context()->v8_context()->Global(), isolate);