Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(279)

Side by Side Diff: chrome/browser/extensions/extension_popup_api.cc

Issue 6334101: Removal of chrome.experimental.popup set of APIs (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src/
Patch Set: '' Created 9 years, 10 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
(Empty)
1 // Copyright (c) 2011 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/extension_popup_api.h"
6
7 #include <string>
8
9 #include "base/json/json_writer.h"
10 #include "base/string_util.h"
11 #include "base/stringprintf.h"
12 #include "base/values.h"
13 #include "chrome/browser/extensions/extension_event_router.h"
14 #include "chrome/browser/extensions/extension_host.h"
15 #include "chrome/browser/extensions/extension_web_ui.h"
16 #include "chrome/browser/profiles/profile.h"
17 #include "chrome/browser/renderer_host/render_view_host.h"
18 #include "chrome/browser/renderer_host/render_view_host_delegate.h"
19 #include "chrome/browser/renderer_host/render_widget_host_view.h"
20 #include "chrome/browser/ui/browser.h"
21 #include "chrome/browser/ui/browser_window.h"
22 #include "chrome/browser/ui/window_sizer.h"
23 #include "chrome/common/extensions/extension.h"
24 #include "chrome/common/notification_details.h"
25 #include "chrome/common/notification_service.h"
26 #include "chrome/common/notification_source.h"
27 #include "chrome/common/notification_type.h"
28 #include "chrome/common/url_constants.h"
29 #include "ui/gfx/point.h"
30
31 #if defined(TOOLKIT_VIEWS)
32 #include "chrome/browser/ui/views/bubble_border.h"
33 #include "chrome/browser/ui/views/extensions/extension_popup.h"
34 #include "views/view.h"
35 #include "views/focus/focus_manager.h"
36 #endif // TOOLKIT_VIEWS
37
38 namespace extension_popup_module_events {
39
40 const char kOnPopupClosed[] = "experimental.popup.onClosed.%d";
41
42 } // namespace extension_popup_module_events
43
44 namespace {
45
46 // Errors.
47 const char kBadAnchorArgument[] = "Invalid anchor argument.";
48 const char kInvalidURLError[] = "Invalid URL.";
49 const char kNotAnExtension[] = "Not an extension view.";
50 const char kPopupsDisallowed[] =
51 "Popups are only supported from tab-contents views.";
52
53 // Keys.
54 const char kWidthKey[] = "width";
55 const char kHeightKey[] = "height";
56 const char kTopKey[] = "top";
57 const char kLeftKey[] = "left";
58 const char kGiveFocusKey[] = "giveFocus";
59 const char kDomAnchorKey[] = "domAnchor";
60 const char kBorderStyleKey[] = "borderStyle";
61 const char kMaxSizeKey[] = "maxSize";
62
63 // chrome enumeration values
64 const char kRectangleChrome[] = "rectangle";
65
66 #if defined(TOOLKIT_VIEWS)
67 // Returns an updated arrow location, conditioned on the type of intersection
68 // between the popup window, and the screen. |location| is the current position
69 // of the arrow on the popup. |intersection| is the rect representing the
70 // intersection between the popup view and its working screen. |popup_rect|
71 // is the rect of the popup window in screen space coordinates.
72 // The returned location will be horizontally or vertically inverted based on
73 // if the popup has been clipped horizontally or vertically.
74 BubbleBorder::ArrowLocation ToggleArrowLocation(
75 BubbleBorder::ArrowLocation location, const gfx::Rect& intersection,
76 const gfx::Rect& popup_rect) {
77 // If the popup has been clipped horizontally, flip the right-left position
78 // of the arrow.
79 if (intersection.right() != popup_rect.right() ||
80 intersection.x() != popup_rect.x()) {
81 location = BubbleBorder::horizontal_mirror(location);
82 }
83
84 // If the popup has been clipped vertically, flip the bottom-top position
85 // of the arrow.
86 if (intersection.y() != popup_rect.y() ||
87 intersection.bottom() != popup_rect.bottom()) {
88 location = BubbleBorder::vertical_mirror(location);
89 }
90
91 return location;
92 }
93 #endif // TOOLKIT_VIEWS
94
95 }; // namespace
96
97 #if defined(TOOLKIT_VIEWS)
98 // ExtensionPopupHost objects implement the environment necessary to host
99 // an ExtensionPopup views for the popup api. Its main job is to handle
100 // its lifetime and to fire the popup-closed event when the popup is closed.
101 // Because the close-on-focus-lost behavior is different from page action
102 // and browser action, it also manages its own focus change listening. The
103 // difference in close-on-focus-lost is that in the page action and browser
104 // action cases, the popup closes when the focus leaves the popup or any of its
105 // children. In this case, the popup closes when the focus leaves the popups
106 // containing view or any of *its* children.
107 class ExtensionPopupHost : public ExtensionPopup::Observer,
108 public views::WidgetFocusChangeListener,
109 public base::RefCounted<ExtensionPopupHost>,
110 public NotificationObserver {
111 public:
112 // Pass |max_popup_size| to specify the maximal size to which the popup
113 // will expand. A width or height of 0 will result in the popup making use
114 // of the default max width or height, respectively: ExtensionPopup:kMaxWidth,
115 // and ExtensionPopup::kMaxHeight.
116 explicit ExtensionPopupHost(ExtensionFunctionDispatcher* dispatcher,
117 const gfx::Size& max_popup_size)
118 : dispatcher_(dispatcher), popup_(NULL), max_popup_size_(max_popup_size) {
119 AddRef(); // Balanced in DispatchPopupClosedEvent().
120 views::FocusManager::GetWidgetFocusManager()->AddFocusChangeListener(this);
121 }
122
123 ~ExtensionPopupHost() {
124 views::FocusManager::GetWidgetFocusManager()->
125 RemoveFocusChangeListener(this);
126 }
127
128 void set_popup(ExtensionPopup* popup) {
129 popup_ = popup;
130
131 // Now that a popup has been assigned, listen for subsequent popups being
132 // created in the same extension - we want to disallow more than one
133 // concurrently displayed popup windows.
134 registrar_.Add(
135 this,
136 NotificationType::EXTENSION_HOST_CREATED,
137 Source<ExtensionProcessManager>(
138 dispatcher_->profile()->GetExtensionProcessManager()));
139
140 registrar_.Add(
141 this,
142 NotificationType::RENDER_VIEW_HOST_WILL_CLOSE_RENDER_VIEW,
143 Source<RenderViewHost>(dispatcher_->render_view_host()));
144
145 registrar_.Add(
146 this,
147 NotificationType::EXTENSION_FUNCTION_DISPATCHER_DESTROYED,
148 Source<Profile>(dispatcher_->profile()));
149 }
150
151 // Overridden from ExtensionPopup::Observer
152 virtual void ExtensionPopupIsClosing(ExtensionPopup* popup) {
153 // Unregister the automation resource routing registered upon host
154 // creation.
155 AutomationResourceRoutingDelegate* router =
156 GetRoutingFromDispatcher(dispatcher_);
157 if (router)
158 router->UnregisterRenderViewHost(popup_->host()->render_view_host());
159 }
160
161 virtual void ExtensionPopupClosed(void* popup_token) {
162 if (popup_ == popup_token) {
163 popup_ = NULL;
164 DispatchPopupClosedEvent();
165 }
166 }
167
168 virtual void ExtensionHostCreated(ExtensionHost* host) {
169 // Pop-up views should share the same automation routing configuration as
170 // their hosting views, so register the RenderViewHost of the pop-up with
171 // the AutomationResourceRoutingDelegate interface of the dispatcher.
172 AutomationResourceRoutingDelegate* router =
173 GetRoutingFromDispatcher(dispatcher_);
174 if (router)
175 router->RegisterRenderViewHost(host->render_view_host());
176
177 // Extension hosts created for popup contents exist in the same tab
178 // contents as the ExtensionFunctionDispatcher that requested the popup.
179 // For example, '_blank' link navigation should be routed through the tab
180 // contents that requested the popup.
181 if (dispatcher_ && dispatcher_->delegate()) {
182 host->set_associated_tab_contents(
183 dispatcher_->delegate()->associated_tab_contents());
184 }
185 }
186
187 virtual void ExtensionPopupCreated(ExtensionPopup* popup) {
188 // The popup has been created, but not yet displayed, so install the max
189 // size overrides before the first positioning.
190 if (max_popup_size_.width())
191 popup->set_max_width(max_popup_size_.width());
192
193 if (max_popup_size_.height())
194 popup->set_max_height(max_popup_size_.height());
195 }
196
197 virtual void ExtensionPopupResized(ExtensionPopup* popup) {
198 // Reposition the location of the arrow on the popup so that the popup
199 // better fits on the working monitor.
200 gfx::Rect popup_rect = popup->GetOuterBounds();
201 if (popup_rect.IsEmpty())
202 return;
203
204 scoped_ptr<WindowSizer::MonitorInfoProvider> monitor_provider(
205 WindowSizer::CreateDefaultMonitorInfoProvider());
206 gfx::Rect monitor_bounds(
207 monitor_provider->GetMonitorWorkAreaMatching(popup_rect));
208 gfx::Rect intersection = monitor_bounds.Intersect(popup_rect);
209
210 // If the popup is totally out of the bounds of the monitor, then toggling
211 // the arrow location will not result in an un-clipped window.
212 if (intersection.IsEmpty())
213 return;
214
215 if (!intersection.Equals(popup_rect)) {
216 // The popup was clipped by the monitor. Toggle the arrow position
217 // to see if that improves visibility. Note: The assignment and
218 // re-assignment of the arrow-position will not trigger an intermittent
219 // display.
220 BubbleBorder::ArrowLocation previous_location = popup->arrow_position();
221 BubbleBorder::ArrowLocation flipped_location = ToggleArrowLocation(
222 previous_location, intersection, popup_rect);
223 popup->SetArrowPosition(flipped_location);
224
225 // Double check that toggling the position actually improved the
226 // situation - the popup will be contained entirely in its working monitor
227 // bounds.
228 gfx::Rect flipped_bounds = popup->GetOuterBounds();
229 gfx::Rect updated_monitor_bounds =
230 monitor_provider->GetMonitorWorkAreaMatching(flipped_bounds);
231 if (!updated_monitor_bounds.Contains(flipped_bounds))
232 popup->SetArrowPosition(previous_location);
233 }
234 }
235
236 // Overridden from views::WidgetFocusChangeListener
237 virtual void NativeFocusWillChange(gfx::NativeView focused_before,
238 gfx::NativeView focused_now) {
239 // If the popup doesn't exist, then do nothing.
240 if (!popup_)
241 return;
242
243 // If no view is to be focused, then Chrome was deactivated, so hide the
244 // popup.
245 if (focused_now) {
246 // On XP, the focus change handler may be invoked when the delegate has
247 // already been revoked.
248 // TODO(twiz@chromium.org): Resolve the trigger of this behaviour.
249 if (!dispatcher_ || !dispatcher_->delegate())
250 return;
251
252 gfx::NativeView host_view =
253 dispatcher_->delegate()->GetNativeViewOfHost();
254
255 // If the widget hosting the popup contains the newly focused view, then
256 // don't dismiss the pop-up.
257 ExtensionView* view = popup_->host()->view();
258 if (view) {
259 views::Widget* popup_root_widget = view->GetWidget();
260 if (popup_root_widget &&
261 popup_root_widget->ContainsNativeView(focused_now))
262 return;
263 }
264
265 // If the widget or RenderWidgetHostView hosting the extension that
266 // launched the pop-up is receiving focus, then don't dismiss the popup.
267 views::Widget* host_widget =
268 views::Widget::GetWidgetFromNativeView(host_view);
269 if (host_widget && host_widget->ContainsNativeView(focused_now))
270 return;
271
272 RenderWidgetHostView* render_host_view =
273 RenderWidgetHostView::GetRenderWidgetHostViewFromNativeView(
274 host_view);
275 if (render_host_view &&
276 render_host_view->ContainsNativeView(focused_now))
277 return;
278 }
279
280 // We are careful here to let the current event loop unwind before
281 // causing the popup to be closed.
282 MessageLoop::current()->PostTask(FROM_HERE, NewRunnableMethod(popup_,
283 &ExtensionPopup::Close));
284 }
285
286 // Overridden from NotificationObserver
287 virtual void Observe(NotificationType type,
288 const NotificationSource& source,
289 const NotificationDetails& details) {
290 if (NotificationType::EXTENSION_HOST_CREATED == type) {
291 Details<ExtensionHost> details_host(details);
292 // Disallow multiple pop-ups from the same extension, by closing
293 // the presently opened popup during construction of any new popups.
294 if (ViewType::EXTENSION_POPUP == details_host->GetRenderViewType() &&
295 popup_->host()->extension() == details_host->extension() &&
296 Details<ExtensionHost>(popup_->host()) != details) {
297 popup_->Close();
298 }
299 } else if (NotificationType::RENDER_VIEW_HOST_WILL_CLOSE_RENDER_VIEW ==
300 type) {
301 if (Source<RenderViewHost>(dispatcher_->render_view_host()) == source) {
302 // If the parent render view is about to be closed, signal closure
303 // of the popup.
304 popup_->Close();
305 }
306 } else if (NotificationType::EXTENSION_FUNCTION_DISPATCHER_DESTROYED ==
307 type) {
308 // Popups should not outlive the dispatchers that launched them.
309 // Normally, long-lived popups will be dismissed in response to the
310 // RENDER_VIEW_WILL_CLOSE_BY_RENDER_VIEW_HOST message. Unfortunately,
311 // if the hosting view invokes window.close(), there is no communication
312 // back to the browser until the entire view has been torn down, at which
313 // time the dispatcher will be invoked.
314 // Note: The onClosed event will not be fired, but because the hosting
315 // view has already been torn down, it is already too late to process it.
316 // TODO(twiz): Add a communication path between the renderer and browser
317 // for RenderView closure notifications initiatied within the renderer.
318 if (Details<ExtensionFunctionDispatcher>(dispatcher_) == details) {
319 dispatcher_ = NULL;
320 popup_->Close();
321 }
322 }
323 }
324
325 private:
326 // Returns the AutomationResourceRoutingDelegate interface for |dispatcher|.
327 static AutomationResourceRoutingDelegate*
328 GetRoutingFromDispatcher(ExtensionFunctionDispatcher* dispatcher) {
329 if (!dispatcher)
330 return NULL;
331
332 RenderViewHost* render_view_host = dispatcher->render_view_host();
333 RenderViewHostDelegate* delegate =
334 render_view_host ? render_view_host->delegate() : NULL;
335
336 return delegate ? delegate->GetAutomationResourceRoutingDelegate() : NULL;
337 }
338
339 void DispatchPopupClosedEvent() {
340 if (dispatcher_) {
341 PopupEventRouter::OnPopupClosed(
342 dispatcher_->profile(),
343 dispatcher_->render_view_host()->routing_id());
344 dispatcher_ = NULL;
345 }
346 Release(); // Balanced in ctor.
347 }
348
349 // A pointer to the dispatcher that handled the request that opened this
350 // popup view.
351 ExtensionFunctionDispatcher* dispatcher_;
352
353 // A pointer to the popup.
354 ExtensionPopup* popup_;
355
356 // The maximal size to which the popup is permitted to expand.
357 gfx::Size max_popup_size_;
358
359 NotificationRegistrar registrar_;
360
361 DISALLOW_COPY_AND_ASSIGN(ExtensionPopupHost);
362 };
363 #endif // TOOLKIT_VIEWS
364
365 PopupShowFunction::PopupShowFunction()
366 #if defined (TOOLKIT_VIEWS)
367 : popup_(NULL)
368 #endif
369 {}
370
371 void PopupShowFunction::Run() {
372 #if defined(TOOLKIT_VIEWS)
373 if (!RunImpl()) {
374 SendResponse(false);
375 } else {
376 // If the contents of the popup are already available, then immediately
377 // send the response. Otherwise wait for the EXTENSION_POPUP_VIEW_READY
378 // notification.
379 if (popup_->host() && popup_->host()->document_element_available()) {
380 SendResponse(true);
381 } else {
382 AddRef();
383 registrar_.Add(this, NotificationType::EXTENSION_POPUP_VIEW_READY,
384 NotificationService::AllSources());
385 registrar_.Add(this, NotificationType::EXTENSION_HOST_DESTROYED,
386 NotificationService::AllSources());
387 }
388 }
389 #else
390 SendResponse(false);
391 #endif
392 }
393
394 bool PopupShowFunction::RunImpl() {
395 // Popups may only be displayed from TAB_CONTENTS and EXTENSION_INFOBAR.
396 ViewType::Type view_type =
397 dispatcher()->render_view_host()->delegate()->GetRenderViewType();
398 if (ViewType::TAB_CONTENTS != view_type &&
399 ViewType::EXTENSION_INFOBAR != view_type) {
400 error_ = kPopupsDisallowed;
401 return false;
402 }
403
404 std::string url_string;
405 EXTENSION_FUNCTION_VALIDATE(args_->GetString(0, &url_string));
406
407 DictionaryValue* show_details = NULL;
408 EXTENSION_FUNCTION_VALIDATE(args_->GetDictionary(1, &show_details));
409
410 DictionaryValue* dom_anchor = NULL;
411 EXTENSION_FUNCTION_VALIDATE(show_details->GetDictionary(kDomAnchorKey,
412 &dom_anchor));
413
414 int dom_top, dom_left;
415 EXTENSION_FUNCTION_VALIDATE(dom_anchor->GetInteger(kTopKey,
416 &dom_top));
417 EXTENSION_FUNCTION_VALIDATE(dom_anchor->GetInteger(kLeftKey,
418 &dom_left));
419
420 int dom_width, dom_height;
421 EXTENSION_FUNCTION_VALIDATE(dom_anchor->GetInteger(kWidthKey,
422 &dom_width));
423 EXTENSION_FUNCTION_VALIDATE(dom_anchor->GetInteger(kHeightKey,
424 &dom_height));
425 EXTENSION_FUNCTION_VALIDATE(dom_top >= 0 && dom_left >= 0 &&
426 dom_width >= 0 && dom_height >= 0);
427
428 // The default behaviour is to give the focus to the pop-up window.
429 bool give_focus = true;
430 if (show_details->HasKey(kGiveFocusKey)) {
431 EXTENSION_FUNCTION_VALIDATE(show_details->GetBoolean(kGiveFocusKey,
432 &give_focus));
433 }
434
435 int max_width = 0;
436 int max_height = 0;
437 if (show_details->HasKey(kMaxSizeKey)) {
438 DictionaryValue* max_size = NULL;
439 EXTENSION_FUNCTION_VALIDATE(show_details->GetDictionary(kMaxSizeKey,
440 &max_size));
441
442 if (max_size->HasKey(kWidthKey))
443 EXTENSION_FUNCTION_VALIDATE(max_size->GetInteger(kWidthKey, &max_width));
444
445 if (max_size->HasKey(kHeightKey))
446 EXTENSION_FUNCTION_VALIDATE(max_size->GetInteger(kHeightKey,
447 &max_height));
448 }
449
450 #if defined(TOOLKIT_VIEWS)
451 // The default behaviour is to provide the bubble-chrome to the popup.
452 ExtensionPopup::PopupChrome chrome = ExtensionPopup::BUBBLE_CHROME;
453 if (show_details->HasKey(kBorderStyleKey)) {
454 std::string chrome_string;
455 EXTENSION_FUNCTION_VALIDATE(show_details->GetString(kBorderStyleKey,
456 &chrome_string));
457 if (chrome_string == kRectangleChrome)
458 chrome = ExtensionPopup::RECTANGLE_CHROME;
459 }
460 #endif
461
462 GURL url = dispatcher()->url().Resolve(url_string);
463 if (!url.is_valid()) {
464 error_ = kInvalidURLError;
465 return false;
466 }
467
468 // Disallow non-extension requests, or requests outside of the requesting
469 // extension view's extension.
470 const std::string& extension_id = url.host();
471 if (extension_id != GetExtension()->id() ||
472 !url.SchemeIs(chrome::kExtensionScheme)) {
473 error_ = kInvalidURLError;
474 return false;
475 }
476
477 gfx::Point origin(dom_left, dom_top);
478 if (!dispatcher()->render_view_host()->view()) {
479 error_ = kNotAnExtension;
480 return false;
481 }
482
483 gfx::Rect content_bounds =
484 dispatcher()->render_view_host()->view()->GetViewBounds();
485 origin.Offset(content_bounds.x(), content_bounds.y());
486 gfx::Rect rect(origin.x(), origin.y(), dom_width, dom_height);
487
488 // Get the correct native window to pass to ExtensionPopup.
489 // ExtensionFunctionDispatcher::Delegate may provide a custom implementation
490 // of this.
491 gfx::NativeWindow window =
492 dispatcher()->delegate()->GetCustomFrameNativeWindow();
493 if (!window)
494 window = GetCurrentBrowser()->window()->GetNativeHandle();
495
496 #if defined(TOOLKIT_VIEWS)
497 BubbleBorder::ArrowLocation arrow_location = BubbleBorder::TOP_LEFT;
498
499 // ExtensionPopupHost manages it's own lifetime.
500 ExtensionPopupHost* popup_host =
501 new ExtensionPopupHost(dispatcher(), gfx::Size(max_width, max_height));
502 popup_ = ExtensionPopup::Show(url,
503 GetCurrentBrowser(),
504 dispatcher()->profile(),
505 window,
506 rect,
507 arrow_location,
508 give_focus,
509 false, // inspect_with_devtools
510 chrome,
511 popup_host); // ExtensionPopup::Observer
512
513 // popup_host will handle focus change listening and close the popup when
514 // focus leaves the containing views hierarchy.
515 popup_->set_close_on_lost_focus(false);
516 popup_host->set_popup(popup_);
517 #endif // defined(TOOLKIT_VIEWS)
518
519 return true;
520 }
521
522 void PopupShowFunction::Observe(NotificationType type,
523 const NotificationSource& source,
524 const NotificationDetails& details) {
525 #if defined(TOOLKIT_VIEWS)
526 DCHECK(type == NotificationType::EXTENSION_POPUP_VIEW_READY ||
527 type == NotificationType::EXTENSION_HOST_DESTROYED);
528 DCHECK(popup_ != NULL);
529
530 // Wait for notification that the popup view is ready (and onload has been
531 // called), before completing the API call.
532 if (popup_ && type == NotificationType::EXTENSION_POPUP_VIEW_READY &&
533 Details<ExtensionHost>(popup_->host()) == details) {
534 SendResponse(true);
535 Release(); // Balanced in Run().
536 } else if (popup_ && type == NotificationType::EXTENSION_HOST_DESTROYED &&
537 Details<ExtensionHost>(popup_->host()) == details) {
538 // If the host was destroyed, then report failure, and release the remaining
539 // reference.
540 SendResponse(false);
541 Release(); // Balanced in Run().
542 }
543 #endif // defined(TOOLKIT_VIEWS)
544 }
545
546 // static
547 void PopupEventRouter::OnPopupClosed(Profile* profile,
548 int routing_id) {
549 std::string full_event_name = base::StringPrintf(
550 extension_popup_module_events::kOnPopupClosed,
551 routing_id);
552
553 profile->GetExtensionEventRouter()->DispatchEventToRenderers(
554 full_event_name, base::JSONWriter::kEmptyArray, profile, GURL());
555 }
OLDNEW
« no previous file with comments | « chrome/browser/extensions/extension_popup_api.h ('k') | chrome/browser/extensions/extension_popup_apitest.cc » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698