1// Copyright (c) 2012 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "chrome/browser/extensions/api/declarative_content/content_action.h"
6
7#include "base/base64.h"
8#include "base/run_loop.h"
9#include "base/test/values_test_util.h"
10#include "chrome/browser/extensions/extension_action.h"
11#include "chrome/browser/extensions/extension_action_manager.h"
12#include "chrome/browser/extensions/extension_service_test_base.h"
13#include "chrome/browser/extensions/extension_tab_util.h"
14#include "chrome/browser/extensions/test_extension_environment.h"
15#include "chrome/browser/extensions/test_extension_system.h"
16#include "chrome/test/base/testing_profile.h"
17#include "chrome/test/base/testing_profile.h"
18#include "content/public/browser/web_contents.h"
19#include "extensions/browser/extension_system.h"
20#include "extensions/common/extension.h"
21#include "extensions/common/extension_builder.h"
22#include "extensions/common/value_builder.h"
23#include "ipc/ipc_message_utils.h"
24#include "testing/gmock/include/gmock/gmock.h"
25#include "testing/gtest/include/gtest/gtest.h"
26#include "ui/gfx/image/image_skia.h"
27#include "ui/gfx/ipc/gfx_param_traits.h"
28
29namespace extensions {
30namespace {
31
32using base::test::ParseJson;
33using testing::HasSubstr;
34
35
36scoped_ptr<base::DictionaryValue> SimpleManifest() {
37  return DictionaryBuilder()
38      .Set("name", "extension")
39      .Set("manifest_version", 2)
40      .Set("version", "1.0")
41      .Build();
42}
43
44class RequestContentScriptTest : public ExtensionServiceTestBase {
45 public:
46  RequestContentScriptTest()
47      : extension_(ExtensionBuilder().SetManifest(SimpleManifest()).Build()) {};
48
49  // TODO(rdevlin.cronin): This should be SetUp(), but an issues with invoking
50  // InitializeEmptyExtensionService() within SetUp() means that we have to
51  // call this manually within every test. This can be cleaned up once said
52  // issue is fixed.
53  virtual void Init() {
54    InitializeEmptyExtensionService();
55    static_cast<TestExtensionSystem*>(ExtensionSystem::Get(profile()))->
56        SetReady();
57    base::RunLoop().RunUntilIdle();
58  }
59
60  Profile* profile() { return profile_.get(); }
61  Extension* extension() { return extension_.get(); }
62
63 private:
64  scoped_refptr<Extension> extension_;
65};
66
67TEST(DeclarativeContentActionTest, InvalidCreation) {
68  TestExtensionEnvironment env;
69  std::string error;
70  bool bad_message = false;
71  scoped_refptr<const ContentAction> result;
72
73  // Test wrong data type passed.
74  error.clear();
75  result = ContentAction::Create(
76      NULL, NULL, *ParseJson("[]"), &error, &bad_message);
77  EXPECT_TRUE(bad_message);
78  EXPECT_EQ("", error);
79  EXPECT_FALSE(result.get());
80
81  // Test missing instanceType element.
82  error.clear();
83  result = ContentAction::Create(
84      NULL, NULL, *ParseJson("{}"), &error, &bad_message);
85  EXPECT_TRUE(bad_message);
86  EXPECT_EQ("", error);
87  EXPECT_FALSE(result.get());
88
89  // Test wrong instanceType element.
90  error.clear();
91  result = ContentAction::Create(NULL, NULL, *ParseJson(
92      "{\n"
93      "  \"instanceType\": \"declarativeContent.UnknownType\",\n"
94      "}"),
95                                 &error, &bad_message);
96  EXPECT_THAT(error, HasSubstr("invalid instanceType"));
97  EXPECT_FALSE(result.get());
98}
99
100TEST(DeclarativeContentActionTest, ShowPageActionWithoutPageAction) {
101  TestExtensionEnvironment env;
102
103  const Extension* extension = env.MakeExtension(base::DictionaryValue());
104  std::string error;
105  bool bad_message = false;
106  scoped_refptr<const ContentAction> result = ContentAction::Create(
107      NULL,
108      extension,
109      *ParseJson(
110           "{\n"
111           "  \"instanceType\": \"declarativeContent.ShowPageAction\",\n"
112           "}"),
113      &error,
114      &bad_message);
115  EXPECT_THAT(error, testing::HasSubstr("without a page action"));
116  EXPECT_FALSE(bad_message);
117  ASSERT_FALSE(result.get());
118}
119
120TEST(DeclarativeContentActionTest, ShowPageAction) {
121  TestExtensionEnvironment env;
122
123  const Extension* extension = env.MakeExtension(
124      *ParseJson("{\"page_action\": { \"default_title\": \"Extension\" } }"));
125  std::string error;
126  bool bad_message = false;
127  scoped_refptr<const ContentAction> result = ContentAction::Create(
128      NULL,
129      extension,
130      *ParseJson(
131           "{\n"
132           "  \"instanceType\": \"declarativeContent.ShowPageAction\",\n"
133           "}"),
134      &error,
135      &bad_message);
136  EXPECT_EQ("", error);
137  EXPECT_FALSE(bad_message);
138  ASSERT_TRUE(result.get());
139  EXPECT_EQ(ContentAction::ACTION_SHOW_PAGE_ACTION, result->GetType());
140
141  ExtensionAction* page_action =
142      ExtensionActionManager::Get(env.profile())->GetPageAction(*extension);
143  scoped_ptr<content::WebContents> contents = env.MakeTab();
144  const int tab_id = ExtensionTabUtil::GetTabId(contents.get());
145  EXPECT_FALSE(page_action->GetIsVisible(tab_id));
146  ContentAction::ApplyInfo apply_info = {
147    env.profile(), contents.get()
148  };
149  result->Apply(extension->id(), base::Time(), &apply_info);
150  EXPECT_TRUE(page_action->GetIsVisible(tab_id));
151  result->Apply(extension->id(), base::Time(), &apply_info);
152  EXPECT_TRUE(page_action->GetIsVisible(tab_id));
153  result->Revert(extension->id(), base::Time(), &apply_info);
154  EXPECT_TRUE(page_action->GetIsVisible(tab_id));
155  result->Revert(extension->id(), base::Time(), &apply_info);
156  EXPECT_FALSE(page_action->GetIsVisible(tab_id));
157}
158
159TEST(DeclarativeContentActionTest, SetIcon) {
160  TestExtensionEnvironment env;
161
162  // Simulate the process of passing ImageData to SetIcon::Create.
163  SkBitmap bitmap;
164  EXPECT_TRUE(bitmap.tryAllocN32Pixels(19, 19));
165  bitmap.eraseARGB(0,0,0,0);
166  uint32_t* pixels = bitmap.getAddr32(0, 0);
167  for (int i = 0; i < 19 * 19; ++i)
168    pixels[i] = i;
169  IPC::Message bitmap_pickle;
170  IPC::WriteParam(&bitmap_pickle, bitmap);
171  std::string binary_data = std::string(
172      static_cast<const char*>(bitmap_pickle.data()), bitmap_pickle.size());
173  std::string data64;
174  base::Base64Encode(binary_data, &data64);
175
176  scoped_ptr<base::DictionaryValue> dict =
177      DictionaryBuilder().Set("instanceType", "declarativeContent.SetIcon")
178                         .Set("imageData",
179                              DictionaryBuilder().Set("19", data64)).Build();
180
181  const Extension* extension = env.MakeExtension(
182      *ParseJson("{\"page_action\": { \"default_title\": \"Extension\" } }"));
183  std::string error;
184  bool bad_message = false;
185  scoped_refptr<const ContentAction> result = ContentAction::Create(
186      NULL,
187      extension,
188      *dict,
189      &error,
190      &bad_message);
191  EXPECT_EQ("", error);
192  EXPECT_FALSE(bad_message);
193  ASSERT_TRUE(result.get());
194  EXPECT_EQ(ContentAction::ACTION_SET_ICON, result->GetType());
195
196  ExtensionAction* page_action =
197      ExtensionActionManager::Get(env.profile())->GetPageAction(*extension);
198  scoped_ptr<content::WebContents> contents = env.MakeTab();
199  const int tab_id = ExtensionTabUtil::GetTabId(contents.get());
200  EXPECT_FALSE(page_action->GetIsVisible(tab_id));
201  ContentAction::ApplyInfo apply_info = {
202    env.profile(), contents.get()
203  };
204
205  // The declarative icon shouldn't exist unless the content action is applied.
206  EXPECT_TRUE(page_action->GetDeclarativeIcon(tab_id).bitmap()->empty());
207  result->Apply(extension->id(), base::Time(), &apply_info);
208  EXPECT_FALSE(page_action->GetDeclarativeIcon(tab_id).bitmap()->empty());
209  result->Revert(extension->id(), base::Time(), &apply_info);
210  EXPECT_TRUE(page_action->GetDeclarativeIcon(tab_id).bitmap()->empty());
211}
212
213TEST_F(RequestContentScriptTest, MissingScripts) {
214  Init();
215  std::string error;
216  bool bad_message = false;
217  scoped_refptr<const ContentAction> result = ContentAction::Create(
218      profile(),
219      extension(),
220      *ParseJson(
221          "{\n"
222          "  \"instanceType\": \"declarativeContent.RequestContentScript\",\n"
223          "  \"allFrames\": true,\n"
224          "  \"matchAboutBlank\": true\n"
225          "}"),
226      &error,
227      &bad_message);
228  EXPECT_THAT(error, testing::HasSubstr("Missing parameter is required"));
229  EXPECT_FALSE(bad_message);
230  ASSERT_FALSE(result.get());
231}
232
233TEST_F(RequestContentScriptTest, CSS) {
234  Init();
235  std::string error;
236  bool bad_message = false;
237  scoped_refptr<const ContentAction> result = ContentAction::Create(
238      profile(),
239      extension(),
240      *ParseJson(
241          "{\n"
242          "  \"instanceType\": \"declarativeContent.RequestContentScript\",\n"
243          "  \"css\": [\"style.css\"]\n"
244          "}"),
245      &error,
246      &bad_message);
247  EXPECT_EQ("", error);
248  EXPECT_FALSE(bad_message);
249  ASSERT_TRUE(result.get());
250  EXPECT_EQ(ContentAction::ACTION_REQUEST_CONTENT_SCRIPT, result->GetType());
251}
252
253TEST_F(RequestContentScriptTest, JS) {
254  Init();
255  std::string error;
256  bool bad_message = false;
257  scoped_refptr<const ContentAction> result = ContentAction::Create(
258      profile(),
259      extension(),
260      *ParseJson(
261          "{\n"
262          "  \"instanceType\": \"declarativeContent.RequestContentScript\",\n"
263          "  \"js\": [\"script.js\"]\n"
264          "}"),
265      &error,
266      &bad_message);
267  EXPECT_EQ("", error);
268  EXPECT_FALSE(bad_message);
269  ASSERT_TRUE(result.get());
270  EXPECT_EQ(ContentAction::ACTION_REQUEST_CONTENT_SCRIPT, result->GetType());
271}
272
273TEST_F(RequestContentScriptTest, CSSBadType) {
274  Init();
275  std::string error;
276  bool bad_message = false;
277  scoped_refptr<const ContentAction> result = ContentAction::Create(
278      profile(),
279      extension(),
280      *ParseJson(
281          "{\n"
282          "  \"instanceType\": \"declarativeContent.RequestContentScript\",\n"
283          "  \"css\": \"style.css\"\n"
284          "}"),
285      &error,
286      &bad_message);
287  EXPECT_TRUE(bad_message);
288  ASSERT_FALSE(result.get());
289}
290
291TEST_F(RequestContentScriptTest, JSBadType) {
292  Init();
293  std::string error;
294  bool bad_message = false;
295  scoped_refptr<const ContentAction> result = ContentAction::Create(
296      profile(),
297      extension(),
298      *ParseJson(
299          "{\n"
300          "  \"instanceType\": \"declarativeContent.RequestContentScript\",\n"
301          "  \"js\": \"script.js\"\n"
302          "}"),
303      &error,
304      &bad_message);
305  EXPECT_TRUE(bad_message);
306  ASSERT_FALSE(result.get());
307}
308
309TEST_F(RequestContentScriptTest, AllFrames) {
310  Init();
311  std::string error;
312  bool bad_message = false;
313  scoped_refptr<const ContentAction> result = ContentAction::Create(
314      profile(),
315      extension(),
316      *ParseJson(
317          "{\n"
318          "  \"instanceType\": \"declarativeContent.RequestContentScript\",\n"
319          "  \"js\": [\"script.js\"],\n"
320          "  \"allFrames\": true\n"
321          "}"),
322      &error,
323      &bad_message);
324  EXPECT_EQ("", error);
325  EXPECT_FALSE(bad_message);
326  ASSERT_TRUE(result.get());
327  EXPECT_EQ(ContentAction::ACTION_REQUEST_CONTENT_SCRIPT, result->GetType());
328}
329
330TEST_F(RequestContentScriptTest, MatchAboutBlank) {
331  Init();
332  std::string error;
333  bool bad_message = false;
334  scoped_refptr<const ContentAction> result = ContentAction::Create(
335      profile(),
336      extension(),
337      *ParseJson(
338          "{\n"
339          "  \"instanceType\": \"declarativeContent.RequestContentScript\",\n"
340          "  \"js\": [\"script.js\"],\n"
341          "  \"matchAboutBlank\": true\n"
342          "}"),
343      &error,
344      &bad_message);
345  EXPECT_EQ("", error);
346  EXPECT_FALSE(bad_message);
347  ASSERT_TRUE(result.get());
348  EXPECT_EQ(ContentAction::ACTION_REQUEST_CONTENT_SCRIPT, result->GetType());
349}
350
351TEST_F(RequestContentScriptTest, AllFramesBadType) {
352  Init();
353  std::string error;
354  bool bad_message = false;
355  scoped_refptr<const ContentAction> result = ContentAction::Create(
356      profile(),
357      extension(),
358      *ParseJson(
359          "{\n"
360          "  \"instanceType\": \"declarativeContent.RequestContentScript\",\n"
361          "  \"js\": [\"script.js\"],\n"
362          "  \"allFrames\": null\n"
363          "}"),
364      &error,
365      &bad_message);
366  EXPECT_TRUE(bad_message);
367  ASSERT_FALSE(result.get());
368}
369
370TEST_F(RequestContentScriptTest, MatchAboutBlankBadType) {
371  Init();
372  std::string error;
373  bool bad_message = false;
374  scoped_refptr<const ContentAction> result = ContentAction::Create(
375      profile(),
376      extension(),
377      *ParseJson(
378          "{\n"
379          "  \"instanceType\": \"declarativeContent.RequestContentScript\",\n"
380          "  \"js\": [\"script.js\"],\n"
381          "  \"matchAboutBlank\": null\n"
382          "}"),
383      &error,
384      &bad_message);
385  EXPECT_TRUE(bad_message);
386  ASSERT_FALSE(result.get());
387}
388
389}  // namespace
390}  // namespace extensions
391