1// Copyright 2014 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 "extensions/browser/guest_view/extension_options/extension_options_guest.h"
6
7#include "base/values.h"
8#include "components/crx_file/id_util.h"
9#include "content/public/browser/render_process_host.h"
10#include "content/public/browser/site_instance.h"
11#include "content/public/browser/web_contents.h"
12#include "extensions/browser/api/extensions_api_client.h"
13#include "extensions/browser/extension_function_dispatcher.h"
14#include "extensions/browser/extension_registry.h"
15#include "extensions/browser/extension_web_contents_observer.h"
16#include "extensions/browser/guest_view/extension_options/extension_options_constants.h"
17#include "extensions/browser/guest_view/extension_options/extension_options_guest_delegate.h"
18#include "extensions/browser/guest_view/guest_view_manager.h"
19#include "extensions/common/api/extension_options_internal.h"
20#include "extensions/common/constants.h"
21#include "extensions/common/extension.h"
22#include "extensions/common/extension_messages.h"
23#include "extensions/common/feature_switch.h"
24#include "extensions/common/manifest_handlers/options_page_info.h"
25#include "extensions/common/permissions/permissions_data.h"
26#include "extensions/strings/grit/extensions_strings.h"
27#include "ipc/ipc_message_macros.h"
28
29using content::WebContents;
30using namespace extensions::core_api;
31
32namespace extensions {
33
34// static
35const char ExtensionOptionsGuest::Type[] = "extensionoptions";
36
37ExtensionOptionsGuest::ExtensionOptionsGuest(
38    content::BrowserContext* browser_context,
39    int guest_instance_id)
40    : GuestView<ExtensionOptionsGuest>(browser_context, guest_instance_id),
41      extension_options_guest_delegate_(
42          extensions::ExtensionsAPIClient::Get()
43              ->CreateExtensionOptionsGuestDelegate(this)) {
44}
45
46ExtensionOptionsGuest::~ExtensionOptionsGuest() {
47}
48
49// static
50extensions::GuestViewBase* ExtensionOptionsGuest::Create(
51    content::BrowserContext* browser_context,
52    int guest_instance_id) {
53  if (!extensions::FeatureSwitch::embedded_extension_options()->IsEnabled()) {
54    return NULL;
55  }
56  return new ExtensionOptionsGuest(browser_context, guest_instance_id);
57}
58
59void ExtensionOptionsGuest::CreateWebContents(
60    const std::string& embedder_extension_id,
61    int embedder_render_process_id,
62    const GURL& embedder_site_url,
63    const base::DictionaryValue& create_params,
64    const WebContentsCreatedCallback& callback) {
65  // Get the extension's base URL.
66  std::string extension_id;
67  create_params.GetString(extensionoptions::kExtensionId, &extension_id);
68
69  if (!crx_file::id_util::IdIsValid(extension_id)) {
70    callback.Run(NULL);
71    return;
72  }
73
74  if (crx_file::id_util::IdIsValid(embedder_extension_id) &&
75      extension_id != embedder_extension_id) {
76    // Extensions cannot embed other extensions' options pages.
77    callback.Run(NULL);
78    return;
79  }
80
81  GURL extension_url =
82      extensions::Extension::GetBaseURLFromExtensionId(extension_id);
83  if (!extension_url.is_valid()) {
84    callback.Run(NULL);
85    return;
86  }
87
88  // Get the options page URL for later use.
89  extensions::ExtensionRegistry* registry =
90      extensions::ExtensionRegistry::Get(browser_context());
91  const extensions::Extension* extension =
92      registry->enabled_extensions().GetByID(extension_id);
93  options_page_ = extensions::OptionsPageInfo::GetOptionsPage(extension);
94  if (!options_page_.is_valid()) {
95    callback.Run(NULL);
96    return;
97  }
98
99  // Create a WebContents using the extension URL. The options page's
100  // WebContents should live in the same process as its parent extension's
101  // WebContents, so we can use |extension_url| for creating the SiteInstance.
102  content::SiteInstance* options_site_instance =
103      content::SiteInstance::CreateForURL(browser_context(), extension_url);
104  WebContents::CreateParams params(browser_context(), options_site_instance);
105  params.guest_delegate = this;
106  callback.Run(WebContents::Create(params));
107}
108
109void ExtensionOptionsGuest::DidAttachToEmbedder() {
110  SetUpAutoSize();
111  web_contents()->GetController().LoadURL(options_page_,
112                                          content::Referrer(),
113                                          ui::PAGE_TRANSITION_LINK,
114                                          std::string());
115}
116
117void ExtensionOptionsGuest::DidInitialize() {
118  extension_function_dispatcher_.reset(
119      new extensions::ExtensionFunctionDispatcher(browser_context(), this));
120  if (extension_options_guest_delegate_) {
121    extension_options_guest_delegate_->DidInitialize();
122  }
123}
124
125void ExtensionOptionsGuest::DidStopLoading() {
126  scoped_ptr<base::DictionaryValue> args(new base::DictionaryValue());
127  DispatchEventToEmbedder(new extensions::GuestViewBase::Event(
128      extension_options_internal::OnLoad::kEventName, args.Pass()));
129}
130
131const char* ExtensionOptionsGuest::GetAPINamespace() const {
132  return extensionoptions::kAPINamespace;
133}
134
135int ExtensionOptionsGuest::GetTaskPrefix() const {
136  return IDS_EXTENSION_TASK_MANAGER_EXTENSIONOPTIONS_TAG_PREFIX;
137}
138
139void ExtensionOptionsGuest::GuestSizeChangedDueToAutoSize(
140    const gfx::Size& old_size,
141    const gfx::Size& new_size) {
142  scoped_ptr<base::DictionaryValue> args(new base::DictionaryValue());
143  args->SetInteger(extensionoptions::kNewWidth, new_size.width());
144  args->SetInteger(extensionoptions::kNewHeight, new_size.height());
145  args->SetInteger(extensionoptions::kOldWidth, old_size.width());
146  args->SetInteger(extensionoptions::kOldHeight, old_size.height());
147  DispatchEventToEmbedder(new extensions::GuestViewBase::Event(
148      extension_options_internal::OnSizeChanged::kEventName, args.Pass()));
149}
150
151bool ExtensionOptionsGuest::IsAutoSizeSupported() const {
152  return true;
153}
154
155content::WebContents* ExtensionOptionsGuest::GetAssociatedWebContents() const {
156  return web_contents();
157}
158
159content::WebContents* ExtensionOptionsGuest::OpenURLFromTab(
160    content::WebContents* source,
161    const content::OpenURLParams& params) {
162  if (!extension_options_guest_delegate_)
163    return NULL;
164
165  // Don't allow external URLs with the CURRENT_TAB disposition be opened in
166  // this guest view, change the disposition to NEW_FOREGROUND_TAB.
167  if ((!params.url.SchemeIs(extensions::kExtensionScheme) ||
168       params.url.host() != options_page_.host()) &&
169      params.disposition == CURRENT_TAB) {
170    return extension_options_guest_delegate_->OpenURLInNewTab(
171        content::OpenURLParams(params.url,
172                               params.referrer,
173                               params.frame_tree_node_id,
174                               NEW_FOREGROUND_TAB,
175                               params.transition,
176                               params.is_renderer_initiated));
177  }
178  return extension_options_guest_delegate_->OpenURLInNewTab(params);
179}
180
181void ExtensionOptionsGuest::CloseContents(content::WebContents* source) {
182  DispatchEventToEmbedder(new extensions::GuestViewBase::Event(
183      extension_options_internal::OnClose::kEventName,
184      make_scoped_ptr(new base::DictionaryValue())));
185}
186
187bool ExtensionOptionsGuest::HandleContextMenu(
188    const content::ContextMenuParams& params) {
189  if (!extension_options_guest_delegate_)
190    return false;
191
192  return extension_options_guest_delegate_->HandleContextMenu(params);
193}
194
195bool ExtensionOptionsGuest::ShouldCreateWebContents(
196    content::WebContents* web_contents,
197    int route_id,
198    WindowContainerType window_container_type,
199    const base::string16& frame_name,
200    const GURL& target_url,
201    const std::string& partition_id,
202    content::SessionStorageNamespace* session_storage_namespace) {
203  // This method handles opening links from within the guest. Since this guest
204  // view is used for displaying embedded extension options, we want any
205  // external links to be opened in a new tab, not in a new guest view.
206  // Therefore we just open the URL in a new tab, and since we aren't handling
207  // the new web contents, we return false.
208  // TODO(ericzeng): Open the tab in the background if the click was a
209  //   ctrl-click or middle mouse button click
210  if (extension_options_guest_delegate_) {
211    extension_options_guest_delegate_->OpenURLInNewTab(
212        content::OpenURLParams(target_url,
213                               content::Referrer(),
214                               NEW_FOREGROUND_TAB,
215                               ui::PAGE_TRANSITION_LINK,
216                               false));
217  }
218  return false;
219}
220
221bool ExtensionOptionsGuest::OnMessageReceived(const IPC::Message& message) {
222  bool handled = true;
223  IPC_BEGIN_MESSAGE_MAP(ExtensionOptionsGuest, message)
224    IPC_MESSAGE_HANDLER(ExtensionHostMsg_Request, OnRequest)
225    IPC_MESSAGE_UNHANDLED(handled = false)
226  IPC_END_MESSAGE_MAP()
227  return handled;
228}
229
230void ExtensionOptionsGuest::OnRequest(
231    const ExtensionHostMsg_Request_Params& params) {
232  extension_function_dispatcher_->Dispatch(params,
233                                           web_contents()->GetRenderViewHost());
234}
235
236void ExtensionOptionsGuest::SetUpAutoSize() {
237  // Read the autosize parameters passed in from the embedder.
238  bool auto_size_enabled = false;
239  attach_params()->GetBoolean(extensionoptions::kAttributeAutoSize,
240                              &auto_size_enabled);
241
242  int max_height = 0;
243  int max_width = 0;
244  attach_params()->GetInteger(extensionoptions::kAttributeMaxHeight,
245                              &max_height);
246  attach_params()->GetInteger(extensionoptions::kAttributeMaxWidth, &max_width);
247
248  int min_height = 0;
249  int min_width = 0;
250  attach_params()->GetInteger(extensionoptions::kAttributeMinHeight,
251                              &min_height);
252  attach_params()->GetInteger(extensionoptions::kAttributeMinWidth, &min_width);
253
254  // Call SetAutoSize to apply all the appropriate validation and clipping of
255  // values.
256  SetAutoSize(auto_size_enabled,
257              gfx::Size(min_width, min_height),
258              gfx::Size(max_width, max_height));
259}
260
261}  // namespace extensions
262