summaryrefslogtreecommitdiffstats
path: root/chrome/browser/cocoa/blocked_popup_container_controller.mm
blob: 528f82a4c9997c586089dcf5d31fccdc5e7771dd (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
// Copyright (c) 2009 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.

#import "chrome/browser/cocoa/blocked_popup_container_controller.h"

#include "app/l10n_util_mac.h"
#include "base/nsimage_cache_mac.h"
#include "base/sys_string_conversions.h"
#import "chrome/browser/cocoa/bubble_view.h"
#include "chrome/browser/tab_contents/tab_contents.h"
#include "chrome/browser/tab_contents/tab_contents_view.h"
#include "grit/generated_resources.h"

// A C++ bridge class that manages the interaction between the C++ interface
// and the Objective-C view controller that implements the popup blocker.
class BlockedPopupContainerViewBridge : public BlockedPopupContainerView {
 public:
  BlockedPopupContainerViewBridge(BlockedPopupContainerController* controller);
  virtual ~BlockedPopupContainerViewBridge();

  // Overrides from BlockedPopupContainerView
  virtual void SetPosition();
  virtual void ShowView();
  virtual void UpdateLabel();
  virtual void HideView();
  virtual void Destroy();

 private:
  BlockedPopupContainerController* controller_;  // Weak, owns us.
};

@interface BlockedPopupContainerController(Private)
- (void)initPopupView;
- (NSView*)containingView;
@end

@implementation BlockedPopupContainerController

// Initialize with the given popup container. Creates the C++ bridge object
// used to represent the "view".
- (id)initWithContainer:(BlockedPopupContainer*)container {
  if ((self = [super init])) {
    container_ = container;
    bridge_.reset(new BlockedPopupContainerViewBridge(self));
    [self initPopupView];
  }
  return self;
}

- (void)dealloc {
  [view_ removeFromSuperview];
  [[NSNotificationCenter defaultCenter] removeObserver:self];
  [super dealloc];
}

- (IBAction)closePopup:(id)sender {
  container_->set_dismissed();
  container_->CloseAll();
}

// Create and initialize the popup view and its label, close box, etc.
- (void)initPopupView {
  static const float kWidth = 200.0;
  static const float kHeight = 20.0;
  static const float kCloseBoxSize = 16.0;
  static const float kCloseBoxPaddingY = 2.0;
  static const float kLabelPaddingX = 5.0;

  NSWindow* window = [[self containingView] window];

  // Create it below the parent's bottom edge so we can animate it into place.
  NSRect startFrame = NSMakeRect(0.0, -kHeight, kWidth, kHeight);
  view_.reset([[BubbleView alloc] initWithFrame:startFrame
                                  themeProvider:window]);
  [view_ setAutoresizingMask:NSViewMinXMargin | NSViewMaxYMargin];
  [view_ setCornerFlags:kRoundedTopLeftCorner | kRoundedTopRightCorner];

  // Create the text label and position it. We'll resize it later when the
  // label gets updated. The view owns the label, we only hold a weak reference.
  NSRect labelFrame = NSMakeRect(kLabelPaddingX,
                                 0,
                                 startFrame.size.width - kCloseBoxSize,
                                 startFrame.size.height);
  popupButton_ = [[[NSPopUpButton alloc] initWithFrame:labelFrame] autorelease];
  [popupButton_ setAutoresizingMask:NSViewWidthSizable];
  [popupButton_ setBordered:NO];
  [popupButton_ setBezelStyle:NSTexturedRoundedBezelStyle];
  [popupButton_ setPullsDown:YES];
  // TODO(pinkerton): this doesn't work, not sure why.
  [popupButton_ setPreferredEdge:NSMaxYEdge];
  // TODO(pinkerton): no matter what, the arrows always draw in the middle
  // of the button. We can turn off the arrows entirely, but then will the
  // user ever know to click it?
  [[popupButton_ cell] setArrowPosition:NSPopUpNoArrow];
  [[popupButton_ cell] setAltersStateOfSelectedItem:NO];
  // If we don't add this, no title will ever display.
  [popupButton_ addItemWithTitle:@""];
  [view_ addSubview:popupButton_];

  // Register for notifications that the menu is about to display so we can
  // fill it in lazily
  [[NSNotificationCenter defaultCenter]
      addObserver:self
         selector:@selector(showMenu:)
             name:NSPopUpButtonCellWillPopUpNotification
           object:[popupButton_ cell]];

  // Create the close box and position at the left of the view.
  NSRect closeFrame = NSMakeRect(startFrame.size.width - kCloseBoxSize,
                                 kCloseBoxPaddingY,
                                 kCloseBoxSize,
                                 kCloseBoxSize);
  closeButton_.reset([[HoverCloseButton alloc] initWithFrame:closeFrame]);
  [closeButton_ setAutoresizingMask:NSViewMinXMargin];
  [closeButton_ setButtonType:NSMomentaryChangeButton];
  [closeButton_ setImage:nsimage_cache::ImageNamed(@"close_bar.pdf")];
  [closeButton_
      setAlternateImage:nsimage_cache::ImageNamed(@"close_bar_p.pdf")];
  [closeButton_ setBordered:NO];
  [closeButton_ setTarget:self];
  [closeButton_ setAction:@selector(closePopup:)];
  [view_ addSubview:closeButton_];
}

// Returns the C++ brige object.
- (BlockedPopupContainerView*)bridge {
  return bridge_.get();
}

// Returns the parent of the RWHVMac. We insert our popup blocker as a sibling
// so that it stays around as the RWHVMac is created/destroyed during
// navigation.
- (NSView*)containingView {
  NSView* view = nil;
  if (container_)
    view = container_->GetConstrainingContents(NULL)->view()->GetNativeView();
  return view;
}

- (void)show {
  const float kLeftPadding = 20;  // Leave room for the scrollbar.

  // No need to do anything if it's already on screen.
  if ([view_ superview]) return;

  // Position the view at the bottom right corner, always leaving room for the
  // scrollbar. This is what window does. It also doesn't care about covering
  // up the horizontal scrollbar.
  NSView* parent = [self containingView];
  NSRect frame = [view_ frame];
  frame.origin.x = [parent frame].size.width - frame.size.width - kLeftPadding;
  [view_ setFrame:frame];

  // Add the view and animate it sliding up into view.
  [parent addSubview:view_.get()];
  frame.origin.y = 0;
  [[view_ animator] setFrame:frame];
}

- (void)hide {
  [view_ removeFromSuperview];
}

// Resize the view based on the new label contents. The autoresize mask will
// take care of resizing everything else.
- (void)resizeWithLabel:(NSString*)label {
  // It would be nice to teach BubbleView to honor -sizeToFit, but it can't
  // really handle the subviews that get added.  So just measure the string
  // and pad for the views.
  NSDictionary* attributes =
      [NSDictionary dictionaryWithObjectsAndKeys:
        NSFontAttributeName, [view_ font],
        nil];
  NSSize stringSize = [label sizeWithAttributes:attributes];
  // Keep the right edge in the same place.
  NSRect frame = [view_ frame];
  CGFloat originalWidth = frame.size.width;
  frame.size.width =
      stringSize.width + [closeButton_ frame].size.width +
      [popupButton_ frame].origin.x + kBubbleViewTextPositionX;
  frame.origin.x -= frame.size.width - originalWidth;
  [view_ setFrame:frame];
}

- (void)update {
  size_t blockedNotices = container_->GetBlockedNoticeCount();
  size_t blockedItems = container_->GetBlockedPopupCount() + blockedNotices;
  NSString* label = nil;
  if (!blockedItems) {
    label = l10n_util::GetNSString(IDS_POPUPS_UNBLOCKED);
  } else if (!blockedNotices) {
    label = l10n_util::GetNSStringF(IDS_POPUPS_BLOCKED_COUNT,
                                    UintToString16(blockedItems));
  } else {
    label = l10n_util::GetNSStringF(IDS_BLOCKED_NOTICE_COUNT,
                                    UintToString16(blockedItems));
  }
  [self resizeWithLabel:label];
  [view_ setContent:label];
}

// Called when the user selects an item from the popup menu. The tag, if below
// |kImpossibleNumberOfPopups| will be the index into the container's popup
// array. In that case, we should display the popup. Otherwise, the tag is
// either a host we should toggle whitelisting on or a notice (for which we do
// nothing as yet).
// |sender| is the NSMenuItem that was chosen.
- (void)menuAction:(id)sender {
  size_t tag = static_cast<size_t>([sender tag]);

  // Is this a click on a popup?
  if (tag < BlockedPopupContainer::kImpossibleNumberOfPopups) {
    container_->LaunchPopupAtIndex(tag);
    return;
  }

  tag -= BlockedPopupContainer::kImpossibleNumberOfPopups;

  // Is this a click on a host?
  size_t hostCount = container_->GetPopupHostCount();
  if (tag < hostCount) {
    container_->ToggleWhitelistingForHost(tag);
    return;
  }

  tag -= hostCount;

  // Nothing to do for now for notices.
}

namespace {
void GetURLAndTitleForPopup(
    const BlockedPopupContainer* container,
    size_t index,
    string16* url,
    string16* title) {
  DCHECK(url);
  DCHECK(title);
  TabContents* tab_contents = container->GetTabContentsAt(index);
  const GURL& tab_contents_url = tab_contents->GetURL().GetOrigin();
  *url = UTF8ToUTF16(tab_contents_url.possibly_invalid_spec());
  *title = tab_contents->GetTitle();
}
}  // namespace

// Build a new popup menu from scratch. The menu contains the blocked popups
// (tags being the popup's index), followed by the list of hosts from which
// the popups were blocked (tags being |kImpossibleNumberOfPopups| + host
// index). The hosts are used to toggle whitelisting for a site.
- (NSMenu*)buildMenu {
  NSMenu* menu = [[[NSMenu alloc] init] autorelease];

  // For pop-down menus, the first item is what is displayed while tracking the
  // menu and it remains there if nothing is selected. Set it to the
  // current title.
  NSString* currentTitle = [popupButton_ title];
  scoped_nsobject<NSMenuItem> dummy(
      [[NSMenuItem alloc] initWithTitle:currentTitle
                                 action:nil
                          keyEquivalent:@""]);
  [menu addItem:dummy.get()];

  // Add the list of blocked popups titles to the menu. We set the array index
  // as the tag for use in the menu action rather than relying on the menu item
  // index.
  const size_t count = container_->GetBlockedPopupCount();
  for (size_t i = 0; i < count; ++i) {
    string16 url, title;
    GetURLAndTitleForPopup(container_, i, &url, &title);
    NSString* titleStr =
        l10n_util::GetNSStringF(IDS_POPUP_TITLE_FORMAT, url, title);
    scoped_nsobject<NSMenuItem> item(
        [[NSMenuItem alloc] initWithTitle:titleStr
                                   action:@selector(menuAction:)
                            keyEquivalent:@""]);
    [item setTag:i];
    [item setTarget:self];
    [menu addItem:item.get()];
  }

  // Add the list of hosts. We begin tagging these at
  // |kImpossibleNumberOfPopups|. If whitelisting has already been enabled
  // for a site, mark it with a checkmark.
  std::vector<std::string> hosts(container_->GetHosts());
  if (!hosts.empty() && count)
    [menu addItem:[NSMenuItem separatorItem]];
  size_t first_host = BlockedPopupContainer::kImpossibleNumberOfPopups;
  for (size_t i = 0; i < hosts.size(); ++i) {
    NSString* titleStr =
        l10n_util::GetNSStringF(IDS_POPUP_HOST_FORMAT, UTF8ToUTF16(hosts[i]));
    scoped_nsobject<NSMenuItem> item(
        [[NSMenuItem alloc] initWithTitle:titleStr
                                   action:@selector(menuAction:)
                            keyEquivalent:@""]);
    if (container_->IsHostWhitelisted(i))
      [item setState:NSOnState];
    [item setTag:first_host + i];
    [item setTarget:self];
    [menu addItem:item.get()];
  }

  // Add the list of notices. We begin tagging these at
  // |kImpossibleNumberOfPopups + hosts.size()|.
  size_t notice_count = container_->GetBlockedNoticeCount();
  if (notice_count && (!hosts.empty() || count))
    [menu addItem:[NSMenuItem separatorItem]];
  size_t first_notice = first_host + hosts.size();
  for (size_t i = 0; i < notice_count; ++i) {
    std::string host;
    string16 reason;
    container_->GetHostAndReasonForNotice(i, &host, &reason);
    NSString* titleStr =
        l10n_util::GetNSStringF(IDS_NOTICE_TITLE_FORMAT, UTF8ToUTF16(host));
    scoped_nsobject<NSMenuItem> item(
        [[NSMenuItem alloc] initWithTitle:titleStr
                                   action:@selector(menuAction:)
                            keyEquivalent:@""]);
    [item setTag:first_notice + i];
    [item setTarget:self];
    [menu addItem:item.get()];
  }

  return menu;
}

// Called when the popup button is about to display the menu, giving us a
// chance to fill in the contents.
- (void)showMenu:(NSNotification*)notify {
  NSMenu* menu = [self buildMenu];
  [[notify object] setMenu:menu];
}

// Only used for testing.
- (BubbleView*)view {
  return view_.get();
}

// Only used for testing.
- (void)setContainer:(BlockedPopupContainer*)container {
  container_ = container;
}

@end

//---------------------------------------------------------------------------

BlockedPopupContainerView* BlockedPopupContainerView::Create(
    BlockedPopupContainer* container) {
  // We "leak" |blocker| for now, we'll release it when the bridge class
  // gets a Destroy() message.
  BlockedPopupContainerController* blocker =
      [[BlockedPopupContainerController alloc] initWithContainer:container];
  return [blocker bridge];
}

BlockedPopupContainerViewBridge::BlockedPopupContainerViewBridge(
    BlockedPopupContainerController* controller) {
  controller_ = controller;
}

BlockedPopupContainerViewBridge::~BlockedPopupContainerViewBridge() {
}

void BlockedPopupContainerViewBridge::SetPosition() {
  // Doesn't ever get called, also a no-op on GTK.
  NOTIMPLEMENTED();
}

void BlockedPopupContainerViewBridge::ShowView() {
  [controller_ show];
}

void BlockedPopupContainerViewBridge::UpdateLabel() {
  [controller_ update];
}

void BlockedPopupContainerViewBridge::HideView() {
  [controller_ hide];
}

void BlockedPopupContainerViewBridge::Destroy() {
  [controller_ autorelease];
}