OLD | NEW |
| (Empty) |
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/ui/gtk/extensions/extension_popup_gtk.h" | |
6 | |
7 #include <gtk/gtk.h> | |
8 | |
9 #include <algorithm> | |
10 | |
11 #include "base/bind.h" | |
12 #include "base/bind_helpers.h" | |
13 #include "base/callback.h" | |
14 #include "base/i18n/rtl.h" | |
15 #include "base/message_loop/message_loop.h" | |
16 #include "chrome/browser/chrome_notification_types.h" | |
17 #include "chrome/browser/devtools/devtools_window.h" | |
18 #include "chrome/browser/extensions/extension_view_host.h" | |
19 #include "chrome/browser/extensions/extension_view_host_factory.h" | |
20 #include "chrome/browser/profiles/profile.h" | |
21 #include "chrome/browser/ui/browser.h" | |
22 #include "chrome/browser/ui/browser_window.h" | |
23 #include "chrome/browser/ui/gtk/gtk_theme_service.h" | |
24 #include "content/public/browser/devtools_agent_host.h" | |
25 #include "content/public/browser/devtools_manager.h" | |
26 #include "content/public/browser/notification_details.h" | |
27 #include "content/public/browser/notification_source.h" | |
28 #include "content/public/browser/render_view_host.h" | |
29 #include "content/public/browser/render_widget_host_view.h" | |
30 #include "url/gurl.h" | |
31 | |
32 using content::BrowserContext; | |
33 using content::RenderViewHost; | |
34 | |
35 ExtensionPopupGtk* ExtensionPopupGtk::current_extension_popup_ = NULL; | |
36 | |
37 // The minimum/maximum dimensions of the extension popup. | |
38 // The minimum is just a little larger than the size of a browser action button. | |
39 // The maximum is an arbitrary number that should be smaller than most screens. | |
40 const int ExtensionPopupGtk::kMinWidth = 25; | |
41 const int ExtensionPopupGtk::kMinHeight = 25; | |
42 const int ExtensionPopupGtk::kMaxWidth = 800; | |
43 const int ExtensionPopupGtk::kMaxHeight = 600; | |
44 | |
45 ExtensionPopupGtk::ExtensionPopupGtk(Browser* browser, | |
46 extensions::ExtensionViewHost* host, | |
47 GtkWidget* anchor, | |
48 ShowAction show_action) | |
49 : browser_(browser), | |
50 bubble_(NULL), | |
51 host_(host), | |
52 anchor_(anchor), | |
53 devtools_callback_(base::Bind( | |
54 &ExtensionPopupGtk::OnDevToolsStateChanged, base::Unretained(this))), | |
55 weak_factory_(this) { | |
56 host_->view()->SetContainer(this); | |
57 being_inspected_ = show_action == SHOW_AND_INSPECT; | |
58 | |
59 // If the host had somehow finished loading, then we'd miss the notification | |
60 // and not show. This seems to happen in single-process mode. | |
61 if (host->did_stop_loading()) { | |
62 ShowPopup(); | |
63 } else { | |
64 registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_HOST_DID_STOP_LOADING, | |
65 content::Source<BrowserContext>(host->browser_context())); | |
66 } | |
67 | |
68 registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_HOST_VIEW_SHOULD_CLOSE, | |
69 content::Source<BrowserContext>(host->browser_context())); | |
70 content::DevToolsManager::GetInstance()->AddAgentStateCallback( | |
71 devtools_callback_); | |
72 } | |
73 | |
74 ExtensionPopupGtk::~ExtensionPopupGtk() { | |
75 content::DevToolsManager::GetInstance()->RemoveAgentStateCallback( | |
76 devtools_callback_); | |
77 } | |
78 | |
79 // static | |
80 void ExtensionPopupGtk::Show(const GURL& url, Browser* browser, | |
81 GtkWidget* anchor, ShowAction show_action) { | |
82 extensions::ExtensionViewHost* host = | |
83 extensions::ExtensionViewHostFactory::CreatePopupHost(url, browser); | |
84 // This object will delete itself when the bubble is closed. | |
85 new ExtensionPopupGtk(browser, host, anchor, show_action); | |
86 } | |
87 | |
88 void ExtensionPopupGtk::Observe(int type, | |
89 const content::NotificationSource& source, | |
90 const content::NotificationDetails& details) { | |
91 switch (type) { | |
92 case chrome::NOTIFICATION_EXTENSION_HOST_DID_STOP_LOADING: | |
93 if (content::Details<extensions::ExtensionHost>(host_.get()) == details) | |
94 ShowPopup(); | |
95 break; | |
96 case chrome::NOTIFICATION_EXTENSION_HOST_VIEW_SHOULD_CLOSE: | |
97 if (content::Details<extensions::ExtensionHost>(host_.get()) == details) | |
98 DestroyPopup(); | |
99 break; | |
100 default: | |
101 NOTREACHED() << "Received unexpected notification"; | |
102 } | |
103 } | |
104 | |
105 void ExtensionPopupGtk::OnDevToolsStateChanged( | |
106 content::DevToolsAgentHost* agent_host, bool attached) { | |
107 // Make sure it's the devtools window that is inspecting our popup. | |
108 if (host_->render_view_host() != agent_host->GetRenderViewHost()) | |
109 return; | |
110 if (attached) { | |
111 // Make sure that the popup won't go away when the inspector is activated. | |
112 if (bubble_) | |
113 bubble_->StopGrabbingInput(); | |
114 | |
115 being_inspected_ = true; | |
116 } else { | |
117 // If the devtools window is closing, we post a task to ourselves to | |
118 // close the popup. This gives the devtools window a chance to finish | |
119 // detaching from the inspected RenderViewHost. | |
120 base::MessageLoop::current()->PostTask( | |
121 FROM_HERE, | |
122 base::Bind(&ExtensionPopupGtk::DestroyPopupWithoutResult, | |
123 weak_factory_.GetWeakPtr())); | |
124 } | |
125 } | |
126 | |
127 void ExtensionPopupGtk::BubbleClosing(BubbleGtk* bubble, | |
128 bool closed_by_escape) { | |
129 current_extension_popup_ = NULL; | |
130 delete this; | |
131 } | |
132 | |
133 void ExtensionPopupGtk::OnExtensionSizeChanged( | |
134 ExtensionViewGtk* view, | |
135 const gfx::Size& new_size) { | |
136 int width = std::max(kMinWidth, std::min(kMaxWidth, new_size.width())); | |
137 int height = std::max(kMinHeight, std::min(kMaxHeight, new_size.height())); | |
138 | |
139 view->render_view_host()->GetView()->SetSize(gfx::Size(width, height)); | |
140 gtk_widget_set_size_request(view->native_view(), width, height); | |
141 } | |
142 | |
143 bool ExtensionPopupGtk::DestroyPopup() { | |
144 if (!bubble_) { | |
145 NOTREACHED(); | |
146 return false; | |
147 } | |
148 | |
149 bubble_->Close(); | |
150 return true; | |
151 } | |
152 | |
153 void ExtensionPopupGtk::ShowPopup() { | |
154 if (bubble_) { | |
155 NOTREACHED(); | |
156 return; | |
157 } | |
158 | |
159 if (being_inspected_) | |
160 DevToolsWindow::OpenDevToolsWindow(host_->render_view_host()); | |
161 | |
162 // Only one instance should be showing at a time. Get rid of the old one, if | |
163 // any. Typically, |current_extension_popup_| will be NULL, but it can be | |
164 // non-NULL if a browser action button is clicked while another extension | |
165 // popup's extension host is still loading. | |
166 if (current_extension_popup_) | |
167 current_extension_popup_->DestroyPopup(); | |
168 current_extension_popup_ = this; | |
169 | |
170 GtkWidget* border_box = gtk_alignment_new(0, 0, 1.0, 1.0); | |
171 // This border is necessary so the bubble's corners do not get cut off by the | |
172 // render view. | |
173 gtk_container_set_border_width(GTK_CONTAINER(border_box), 2); | |
174 gtk_container_add(GTK_CONTAINER(border_box), host_->view()->native_view()); | |
175 | |
176 // We'll be in the upper-right corner of the window for LTR languages, so we | |
177 // want to put the arrow at the upper-right corner of the bubble to match the | |
178 // page and app menus. | |
179 bubble_ = BubbleGtk::Show(anchor_, | |
180 NULL, | |
181 border_box, | |
182 BubbleGtk::ANCHOR_TOP_RIGHT, | |
183 being_inspected_ ? 0 : | |
184 BubbleGtk::POPUP_WINDOW | BubbleGtk::GRAB_INPUT, | |
185 GtkThemeService::GetFrom(browser_->profile()), | |
186 this); | |
187 } | |
188 | |
189 void ExtensionPopupGtk::DestroyPopupWithoutResult() { | |
190 DestroyPopup(); | |
191 } | |
192 | |
193 gfx::Rect ExtensionPopupGtk::GetViewBounds() { | |
194 GtkAllocation allocation; | |
195 gtk_widget_get_allocation(host_->view()->native_view(), &allocation); | |
196 return gfx::Rect(allocation); | |
197 } | |
OLD | NEW |