summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorfeldstein@chromium.org <feldstein@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2010-04-01 18:29:29 +0000
committerfeldstein@chromium.org <feldstein@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2010-04-01 18:29:29 +0000
commit2c7f63df1c2077445509cf05295558713593e84d (patch)
tree3c63d8cd6b7d3a5a5abfae768c470414523dec04
parentc665eb327f8f3486c454a95782ffdad4e79fde2c (diff)
downloadchromium_src-2c7f63df1c2077445509cf05295558713593e84d.zip
chromium_src-2c7f63df1c2077445509cf05295558713593e84d.tar.gz
chromium_src-2c7f63df1c2077445509cf05295558713593e84d.tar.bz2
Enable inspection of extension popups
When developer mode is enabled from the extensions page, you can right click on browser actions or page actions to inspect the popup. It will stay open until you close the Inspector (or open another popup, or close the main window, etc). BUG=24477 TEST=Inspect the popup Review URL: http://codereview.chromium.org/1606001 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@43363 0039d316-1c4b-4281-b951-d872f2087c98
-rw-r--r--chrome/browser/cocoa/autocomplete_text_field_cell.h5
-rw-r--r--chrome/browser/cocoa/autocomplete_text_field_cell.mm11
-rw-r--r--chrome/browser/cocoa/extensions/browser_action_button.mm4
-rw-r--r--chrome/browser/cocoa/extensions/browser_actions_controller.mm3
-rw-r--r--chrome/browser/cocoa/extensions/extension_action_context_menu.h17
-rw-r--r--chrome/browser/cocoa/extensions/extension_action_context_menu.mm126
-rw-r--r--chrome/browser/cocoa/extensions/extension_popup_controller.h18
-rw-r--r--chrome/browser/cocoa/extensions/extension_popup_controller.mm74
-rw-r--r--chrome/browser/cocoa/extensions/extension_popup_controller_unittest.mm3
-rw-r--r--chrome/browser/cocoa/location_bar_view_mac.mm7
10 files changed, 252 insertions, 16 deletions
diff --git a/chrome/browser/cocoa/autocomplete_text_field_cell.h b/chrome/browser/cocoa/autocomplete_text_field_cell.h
index a57a2ad..b92692c 100644
--- a/chrome/browser/cocoa/autocomplete_text_field_cell.h
+++ b/chrome/browser/cocoa/autocomplete_text_field_cell.h
@@ -113,6 +113,11 @@ class ExtensionAction;
// page actions. Use |-layedOutIcons:| instead in that case.
- (NSRect)pageActionFrameForIndex:(size_t)index inFrame:(NSRect)cellFrame;
+// Similar to |pageActionFrameForIndex:inFrame| but accepts an
+// ExtensionAction for when the index is not known.
+- (NSRect)pageActionFrameForExtensionAction:(ExtensionAction*)action
+ inFrame:(NSRect)cellFrame;
+
// Find the icon under the event. |nil| if |theEvent| is not over
// anything.
- (AutocompleteTextFieldIcon*)iconForEvent:(NSEvent*)theEvent
diff --git a/chrome/browser/cocoa/autocomplete_text_field_cell.mm b/chrome/browser/cocoa/autocomplete_text_field_cell.mm
index 3c29f8b..7c0c27f 100644
--- a/chrome/browser/cocoa/autocomplete_text_field_cell.mm
+++ b/chrome/browser/cocoa/autocomplete_text_field_cell.mm
@@ -367,6 +367,17 @@ void DrawImageInRect(NSImage* image, NSView* view, const NSRect& rect) {
return NSZeroRect;
}
+- (NSRect)pageActionFrameForExtensionAction:(ExtensionAction*)action
+ inFrame:(NSRect)cellFrame {
+ const size_t pageActionCount = [self pageActionCount];
+ size_t pos = 0;
+ while (pos < pageActionCount &&
+ action != page_action_views_->ViewAt(pos)->page_action())
+ ++pos;
+ return (pos == pageActionCount) ? NSZeroRect :
+ [self pageActionFrameForIndex:pos inFrame:cellFrame];
+}
+
- (void)drawHintWithFrame:(NSRect)cellFrame inView:(NSView*)controlView {
DCHECK(hintString_);
diff --git a/chrome/browser/cocoa/extensions/browser_action_button.mm b/chrome/browser/cocoa/extensions/browser_action_button.mm
index a6b8da5..863b56b 100644
--- a/chrome/browser/cocoa/extensions/browser_action_button.mm
+++ b/chrome/browser/cocoa/extensions/browser_action_button.mm
@@ -140,7 +140,9 @@ class ExtensionImageTrackerBridge : public NotificationObserver,
[self setShowsBorderOnlyWhileMouseInside:YES];
[self setMenu:[[[ExtensionActionContextMenu alloc]
- initWithExtension:extension profile:profile] autorelease]];
+ initWithExtension:extension
+ profile:profile
+ extensionAction:extension->browser_action()] autorelease]];
tabId_ = tabId;
extension_ = extension;
diff --git a/chrome/browser/cocoa/extensions/browser_actions_controller.mm b/chrome/browser/cocoa/extensions/browser_actions_controller.mm
index 90600dc..db65ec0 100644
--- a/chrome/browser/cocoa/extensions/browser_actions_controller.mm
+++ b/chrome/browser/cocoa/extensions/browser_actions_controller.mm
@@ -702,7 +702,8 @@ class ExtensionsServiceObserverBridge : public NotificationObserver,
[ExtensionPopupController showURL:popupUrl
inBrowser:browser_
anchoredAt:arrowPoint
- arrowLocation:kTopRight];
+ arrowLocation:kTopRight
+ devMode:NO];
} else {
ExtensionBrowserEventRouter::GetInstance()->BrowserActionExecuted(
profile_, action->extension_id(), browser_);
diff --git a/chrome/browser/cocoa/extensions/extension_action_context_menu.h b/chrome/browser/cocoa/extensions/extension_action_context_menu.h
index 2d3f698..13fede3 100644
--- a/chrome/browser/cocoa/extensions/extension_action_context_menu.h
+++ b/chrome/browser/cocoa/extensions/extension_action_context_menu.h
@@ -6,11 +6,14 @@
#define CHROME_BROWSER_COCOA_EXTENSIONS_EXTENSION_ACTION_CONTEXT_MENU_H_
#include "base/scoped_ptr.h"
+#include "base/scoped_nsobject.h"
#import <Cocoa/Cocoa.h>
class AsyncUninstaller;
+class DevmodeObserver;
class Extension;
+class ExtensionAction;
class Profile;
// A context menu used by the Browser and Page Action components that appears
@@ -20,17 +23,29 @@ class Profile;
// The extension that this menu belongs to. Weak.
Extension* extension_;
+ // The extension action this menu belongs to. Weak.
+ ExtensionAction* action_;
+
// The browser profile of the window that contains this extension. Weak.
Profile* profile_;
+ // The inspector menu item. Need to keep this around to add and remove it.
+ scoped_nsobject<NSMenuItem> inspectorItem_;
+
+ scoped_ptr<DevmodeObserver> observer_;
+
// Used to load the extension icon asynchronously on the I/O thread then show
// the uninstall confirmation dialog.
scoped_ptr<AsyncUninstaller> uninstaller_;
}
// Initializes and returns a context menu for the given extension and profile.
-- (id)initWithExtension:(Extension*)extension profile:(Profile*)profile;
+- (id)initWithExtension:(Extension*)extension
+ profile:(Profile*)profile
+ extensionAction:(ExtensionAction*)action;
+// Show or hide the inspector menu item.
+- (void)updateInspectorItem;
@end
typedef ExtensionActionContextMenu ExtensionActionContextMenuMac;
diff --git a/chrome/browser/cocoa/extensions/extension_action_context_menu.mm b/chrome/browser/cocoa/extensions/extension_action_context_menu.mm
index eca2e8e..a7283e6 100644
--- a/chrome/browser/cocoa/extensions/extension_action_context_menu.mm
+++ b/chrome/browser/cocoa/extensions/extension_action_context_menu.mm
@@ -8,11 +8,25 @@
#include "base/sys_string_conversions.h"
#include "base/task.h"
#include "chrome/browser/browser_list.h"
+#import "chrome/browser/cocoa/autocomplete_text_field_cell.h"
+#include "chrome/browser/cocoa/browser_window_cocoa.h"
+#include "chrome/browser/cocoa/browser_window_controller.h"
+#include "chrome/browser/cocoa/extensions/browser_actions_controller.h"
+#include "chrome/browser/cocoa/extensions/extension_popup_controller.h"
+#include "chrome/browser/cocoa/info_bubble_view.h"
+#include "chrome/browser/cocoa/toolbar_controller.h"
#include "chrome/browser/extensions/extension_install_ui.h"
#include "chrome/browser/extensions/extensions_service.h"
+#include "chrome/browser/extensions/extension_tabs_module.h"
+#include "chrome/browser/pref_service.h"
#include "chrome/browser/profile.h"
#include "chrome/common/extensions/extension.h"
+#include "chrome/common/extensions/extension_action.h"
#include "chrome/common/extensions/extension_constants.h"
+#include "chrome/common/notification_details.h"
+#include "chrome/common/notification_observer.h"
+#include "chrome/common/notification_type.h"
+#include "chrome/common/pref_names.h"
#include "chrome/common/url_constants.h"
#include "grit/generated_resources.h"
@@ -51,6 +65,21 @@ class AsyncUninstaller : public ExtensionInstallUI::Delegate {
DISALLOW_COPY_AND_ASSIGN(AsyncUninstaller);
};
+class DevmodeObserver : public NotificationObserver {
+ public:
+ DevmodeObserver(ExtensionActionContextMenu* menu)
+ : menu_(menu) {}
+
+ void Observe(NotificationType type,
+ const NotificationSource& source,
+ const NotificationDetails& details) {
+ DCHECK(type == NotificationType::PREF_CHANGED);
+ [menu_ updateInspectorItem];
+ }
+ private:
+ ExtensionActionContextMenu* menu_;
+};
+
@interface ExtensionActionContextMenu(Private)
// Callback for the context menu items.
- (void)dispatch:(id)menuItem;
@@ -66,12 +95,27 @@ enum {
kExtensionContextOptions = 2,
kExtensionContextDisable = 3,
kExtensionContextUninstall = 4,
- kExtensionContextManage = 6
+ kExtensionContextManage = 6,
+ kExtensionContextInspect = 7
};
+
+int CurrentTabId() {
+ Browser* browser = BrowserList::GetLastActive();
+ if(!browser)
+ return -1;
+ TabContents* contents = browser->GetSelectedTabContents();
+ if (!contents)
+ return -1;
+ return ExtensionTabUtil::GetTabId(contents);
+}
+
} // namespace
-- (id)initWithExtension:(Extension*)extension profile:(Profile*)profile {
+- (id)initWithExtension:(Extension*)extension
+ profile:(Profile*)profile
+ extensionAction:(ExtensionAction*)action{
if ((self = [super initWithTitle:@""])) {
+ action_ = action;
extension_ = extension;
profile_ = profile;
@@ -108,11 +152,38 @@ enum {
}
}
+ NSString* inspectorTitle =
+ l10n_util::GetNSStringWithFixup(IDS_EXTENSION_ACTION_INSPECT_POPUP);
+ inspectorItem_.reset([[NSMenuItem alloc] initWithTitle:inspectorTitle
+ action:@selector(dispatch:)
+ keyEquivalent:@""]);
+ [inspectorItem_.get() setTarget:self];
+ [inspectorItem_.get() setTag:kExtensionContextInspect];
+
+ PrefService* service = profile_->GetPrefs();
+ if (service) {
+ observer_.reset(new DevmodeObserver(self));
+ service->AddPrefObserver(prefs::kExtensionsUIDeveloperMode,
+ observer_.get());
+ }
+ [self updateInspectorItem];
return self;
}
return nil;
}
+- (void)updateInspectorItem {
+ PrefService* service = profile_->GetPrefs();
+ bool devmode = service->GetBoolean(prefs::kExtensionsUIDeveloperMode);
+ if (devmode) {
+ if ([self indexOfItem:inspectorItem_.get()] == -1)
+ [self addItem:inspectorItem_.get()];
+ } else {
+ if ([self indexOfItem:inspectorItem_.get()] != -1)
+ [self removeItem:inspectorItem_.get()];
+ }
+}
+
- (void)dispatch:(id)menuItem {
Browser* browser = BrowserList::FindBrowserWithProfile(profile_);
if (!browser)
@@ -148,10 +219,61 @@ enum {
NEW_FOREGROUND_TAB, PageTransition::LINK);
break;
}
+ case kExtensionContextInspect: {
+ NSPoint popupPoint;
+ BrowserWindowCocoa* window =
+ static_cast<BrowserWindowCocoa*>(browser->window());
+ LocationBar* locationBar = window->GetLocationBar();
+ AutocompleteTextField* field =
+ (AutocompleteTextField*)locationBar->location_entry()->
+ GetNativeView();
+ AutocompleteTextFieldCell* fieldCell = [field autocompleteTextFieldCell];
+ NSRect popupRect =
+ [fieldCell pageActionFrameForExtensionAction:action_
+ inFrame:[field bounds]];
+ if (!NSEqualRects(popupRect, NSZeroRect)) {
+ popupRect = [[field superview] convertRect:popupRect toView:nil];
+ popupPoint = popupRect.origin;
+ NSRect fieldFrame = [field bounds];
+ fieldFrame = [field convertRect:fieldFrame toView:nil];
+ popupPoint.x += fieldFrame.origin.x + popupRect.size.width / 2;
+ } else {
+ ToolbarController* toolbarController =
+ [window->cocoa_controller() toolbarController];
+ BrowserActionsController* controller =
+ [toolbarController browserActionsController];
+ popupPoint = [controller popupPointForBrowserAction:extension_];
+ }
+ int tabId = CurrentTabId();
+ GURL url = action_->GetPopupUrl(tabId);
+ DCHECK(url.is_valid());
+ [ExtensionPopupController showURL:url
+ inBrowser:BrowserList::GetLastActive()
+ anchoredAt:popupPoint
+ arrowLocation:kTopRight
+ devMode:YES];
+ break;
+ }
default:
NOTREACHED();
break;
}
}
+- (BOOL)validateMenuItem:(NSMenuItem *)menuItem {
+ if([menuItem isEqualTo: inspectorItem_.get()]) {
+ return (action_->HasPopup(CurrentTabId()));
+ }
+ return YES;
+}
+
+- (void)dealloc {
+ PrefService* service = profile_->GetPrefs();
+ if (service) {
+ service->RemovePrefObserver(prefs::kExtensionsUIDeveloperMode,
+ observer_.get());
+ }
+ [super dealloc];
+}
+
@end
diff --git a/chrome/browser/cocoa/extensions/extension_popup_controller.h b/chrome/browser/cocoa/extensions/extension_popup_controller.h
index 61d020a..efb5c6d 100644
--- a/chrome/browser/cocoa/extensions/extension_popup_controller.h
+++ b/chrome/browser/cocoa/extensions/extension_popup_controller.h
@@ -13,9 +13,12 @@
#include "chrome/browser/cocoa/info_bubble_view.h"
#include "googleurl/src/gurl.h"
+
class Browser;
+class DevtoolsNotificationBridge;
class ExtensionHost;
@class InfoBubbleWindow;
+class NotificationRegistrar;
// This controller manages a single browser action popup that can appear once a
// user has clicked on a browser action button. It instantiates the extension
@@ -42,6 +45,12 @@ class ExtensionHost;
// The extension host object.
scoped_ptr<ExtensionHost> host_;
+
+ scoped_ptr<NotificationRegistrar> registrar_;
+ scoped_ptr<DevtoolsNotificationBridge> notificationBridge_;
+
+ // Whether the popup has a devtools window attached to it.
+ bool beingInspected_;
}
// Returns the ExtensionHost object associated with this popup.
@@ -55,10 +64,14 @@ class ExtensionHost;
// center of the browser action button.
// The actual display of the popup is delayed until the page contents finish
// loading in order to minimize UI flashing and resizing.
+// Passing YES to |devMode| will launch the webkit inspector for the popup,
+// and prevent the popup from closing when focus is lost. It will be closed
+// after the inspector is closed, or another popup is opened.
+ (ExtensionPopupController*)showURL:(GURL)url
inBrowser:(Browser*)browser
anchoredAt:(NSPoint)anchoredAt
- arrowLocation:(BubbleArrowLocation)arrowLocation;
+ arrowLocation:(BubbleArrowLocation)arrowLocation
+ devMode:(BOOL)devMode;
// Returns the controller used to display the popup being shown. If no popup is
// currently open, then nil is returned. Static because only one extension popup
@@ -67,6 +80,9 @@ class ExtensionHost;
// Whether the popup is in the process of closing (via Core Animation).
- (BOOL)isClosing;
+
+// Show the dev tools attached to the popup.
+- (void)showDevTools;
@end
@interface ExtensionPopupController(TestingAPI)
diff --git a/chrome/browser/cocoa/extensions/extension_popup_controller.mm b/chrome/browser/cocoa/extensions/extension_popup_controller.mm
index 592f6e9..0e656b8 100644
--- a/chrome/browser/cocoa/extensions/extension_popup_controller.mm
+++ b/chrome/browser/cocoa/extensions/extension_popup_controller.mm
@@ -10,9 +10,11 @@
#import "chrome/browser/cocoa/browser_window_cocoa.h"
#import "chrome/browser/cocoa/extension_view_mac.h"
#import "chrome/browser/cocoa/info_bubble_window.h"
+#include "chrome/browser/debugger/devtools_manager.h"
#include "chrome/browser/extensions/extension_host.h"
#include "chrome/browser/extensions/extension_process_manager.h"
#include "chrome/browser/profile.h"
+#include "chrome/common/notification_registrar.h"
#include "chrome/common/notification_service.h"
namespace {
@@ -30,13 +32,50 @@ CGFloat Clamp(CGFloat value, CGFloat min, CGFloat max) {
} // namespace
+class DevtoolsNotificationBridge : public NotificationObserver {
+ public:
+ explicit DevtoolsNotificationBridge(ExtensionPopupController* controller)
+ : controller_(controller) {}
+
+ void Observe(NotificationType type,
+ const NotificationSource& source,
+ const NotificationDetails& details) {
+ switch (type.value) {
+ case NotificationType::EXTENSION_HOST_DID_STOP_LOADING: {
+ if (Details<ExtensionHost>([controller_ extensionHost]) ==
+ details)
+ [controller_ showDevTools];
+ break;
+ }
+ case NotificationType::DEVTOOLS_WINDOW_CLOSING: {
+ RenderViewHost* rvh =
+ [controller_ extensionHost]->render_view_host();
+ if (Details<RenderViewHost>(rvh) == details)
+ // Allow the devtools to finish detaching before we close the popup
+ [controller_ performSelector:@selector(close)
+ withObject:nil
+ afterDelay:0.0];
+ break;
+ }
+ default: {
+ NOTREACHED() << "Received unexpected notification";
+ break;
+ }
+ };
+ }
+
+ private:
+ ExtensionPopupController* controller_;
+};
+
@interface ExtensionPopupController(Private)
// Callers should be using the public static method for initialization.
// NOTE: This takes ownership of |host|.
- (id)initWithHost:(ExtensionHost*)host
parentWindow:(NSWindow*)parentWindow
anchoredAt:(NSPoint)anchoredAt
- arrowLocation:(BubbleArrowLocation)arrowLocation;
+ arrowLocation:(BubbleArrowLocation)arrowLocation
+ devMode:(BOOL)devMode;
// Called when the extension's hosted NSView has been resized.
- (void)extensionViewFrameChanged;
@@ -47,7 +86,9 @@ CGFloat Clamp(CGFloat value, CGFloat min, CGFloat max) {
- (id)initWithHost:(ExtensionHost*)host
parentWindow:(NSWindow*)parentWindow
anchoredAt:(NSPoint)anchoredAt
- arrowLocation:(BubbleArrowLocation)arrowLocation {
+ arrowLocation:(BubbleArrowLocation)arrowLocation
+ devMode:(BOOL)devMode {
+
parentWindow_ = parentWindow;
anchor_ = [parentWindow convertBaseToScreen:anchoredAt];
host_.reset(host);
@@ -86,10 +127,28 @@ CGFloat Clamp(CGFloat value, CGFloat min, CGFloat max) {
[window setDelegate:self];
[window setContentView:view];
self = [super initWithWindow:window];
-
+ if (devMode) {
+ beingInspected_ = true;
+ // Listen for the the devtools window closing.
+ notificationBridge_.reset(new DevtoolsNotificationBridge(self));
+ registrar_.reset(new NotificationRegistrar);
+ registrar_->Add(notificationBridge_.get(),
+ NotificationType::DEVTOOLS_WINDOW_CLOSING,
+ Source<Profile>(host->profile()));
+ registrar_->Add(notificationBridge_.get(),
+ NotificationType::EXTENSION_HOST_DID_STOP_LOADING,
+ Source<Profile>(host->profile()));
+ } else {
+ beingInspected_ = false;
+ }
return self;
}
+- (void)showDevTools {
+ DevToolsManager::GetInstance()->OpenDevToolsWindow(
+ host_->render_view_host());
+}
+
- (void)dealloc {
[[NSNotificationCenter defaultCenter] removeObserver:self];
[super dealloc];
@@ -110,7 +169,7 @@ CGFloat Clamp(CGFloat value, CGFloat min, CGFloat max) {
DCHECK_EQ([notification object], window);
// If the window isn't visible, it is already closed, and this notification
// has been sent as part of the closing operation, so no need to close.
- if ([window isVisible]) {
+ if ([window isVisible] && !beingInspected_) {
[self close];
}
}
@@ -131,7 +190,8 @@ CGFloat Clamp(CGFloat value, CGFloat min, CGFloat max) {
+ (ExtensionPopupController*)showURL:(GURL)url
inBrowser:(Browser*)browser
anchoredAt:(NSPoint)anchoredAt
- arrowLocation:(BubbleArrowLocation)arrowLocation {
+ arrowLocation:(BubbleArrowLocation)arrowLocation
+ devMode:(BOOL)devMode {
DCHECK([NSThread isMainThread]);
DCHECK(browser);
if (!browser)
@@ -164,8 +224,8 @@ CGFloat Clamp(CGFloat value, CGFloat min, CGFloat max) {
initWithHost:host
parentWindow:browser->window()->GetNativeHandle()
anchoredAt:anchoredAt
- arrowLocation:arrowLocation];
-
+ arrowLocation:arrowLocation
+ devMode:devMode];
return gPopup;
}
diff --git a/chrome/browser/cocoa/extensions/extension_popup_controller_unittest.mm b/chrome/browser/cocoa/extensions/extension_popup_controller_unittest.mm
index 16f2fcf..c6335dd 100644
--- a/chrome/browser/cocoa/extensions/extension_popup_controller_unittest.mm
+++ b/chrome/browser/cocoa/extensions/extension_popup_controller_unittest.mm
@@ -66,7 +66,8 @@ class ExtensionPopupControllerTest : public CocoaTest {
[ExtensionPopupController showURL:GURL("http://google.com")
inBrowser:browser_.get()
anchoredAt:NSZeroPoint
- arrowLocation:kTopRight];
+ arrowLocation:kTopRight
+ devMode:NO];
}
virtual void TearDown() {
profile_->ShutdownExtensionProfile();
diff --git a/chrome/browser/cocoa/location_bar_view_mac.mm b/chrome/browser/cocoa/location_bar_view_mac.mm
index 7e5cef4..c9584a0 100644
--- a/chrome/browser/cocoa/location_bar_view_mac.mm
+++ b/chrome/browser/cocoa/location_bar_view_mac.mm
@@ -665,7 +665,8 @@ void LocationBarViewMac::PageActionImageView::OnMousePressed(NSRect bounds) {
[ExtensionPopupController showURL:page_action_->GetPopupUrl(current_tab_id_)
inBrowser:BrowserList::GetLastActive()
anchoredAt:arrowPoint
- arrowLocation:kTopRight];
+ arrowLocation:kTopRight
+ devMode:NO];
} else {
ExtensionBrowserEventRouter::GetInstance()->PageActionExecuted(
profile_, page_action_->extension_id(), page_action_->id(),
@@ -777,7 +778,9 @@ NSMenu* LocationBarViewMac::PageActionImageView::GetMenu() {
if (!extension)
return nil;
return [[[ExtensionActionContextMenu alloc]
- initWithExtension:extension profile:profile_] autorelease];
+ initWithExtension:extension
+ profile:profile_
+ extensionAction:page_action_] autorelease];
}
void LocationBarViewMac::PageActionImageView::Observe(