summaryrefslogtreecommitdiffstats
path: root/chrome/browser
diff options
context:
space:
mode:
authorerikchen <erikchen@chromium.org>2014-12-11 16:17:38 -0800
committerCommit bot <commit-bot@chromium.org>2014-12-12 00:18:30 +0000
commit600f7962455cb8479168eb2c6678eda05365460a (patch)
tree0485cd68bf693af4ef9e925ee1e594da813d6463 /chrome/browser
parentd01af9d9c924bcca8e965a89eb21e6299b2eb3e0 (diff)
downloadchromium_src-600f7962455cb8479168eb2c6678eda05365460a.zip
chromium_src-600f7962455cb8479168eb2c6678eda05365460a.tar.gz
chromium_src-600f7962455cb8479168eb2c6678eda05365460a.tar.bz2
Reland 1: "mac: Allow Chrome to hand off its active URL to other devices."
The original CL used instance variables in a class extension and automatic generation of ivars for synthesized properties, features only available on 64-bit builds. The Mac Memory bots are still compiling Chromium in 32-bits. This reland removes the usage of those features. > This CL adds the class HandoffManager, which is responsible for interfacing > with Apple's Handoff APIs. It takes a GURL, and exposes that GURL to Handoff. > > This CL adds the class ActiveWebContentsObserver, which is responsible for > listening to changes to the active browser, the active tab, and the visible > URL. It notifies its delegate when any of this state might have changed. > > AppControllerMac is the delegate of ActiveWebContentsObserver, as well as the > owner of the HandoffManager. When it receives a delegate callback, it passes an > updated GURL to the HandoffManager. There is some minimal logic in > AppControllerMac that prevents URLs from incognito windows from being passed to > the HandoffManager. > > BUG=431051, 438823 > Committed: https://crrev.com/708abc5b0abb5e0916d779bf6d1342fd472a2aa1 > Cr-Commit-Position: refs/heads/master@{#307846} BUG=431051, 438823 TBR=sky, erikwright, mmenke, avi Review URL: https://codereview.chromium.org/794853004 Cr-Commit-Position: refs/heads/master@{#308005}
Diffstat (limited to 'chrome/browser')
-rw-r--r--chrome/browser/app_controller_mac.h9
-rw-r--r--chrome/browser/app_controller_mac.mm82
-rw-r--r--chrome/browser/app_controller_mac_browsertest.mm143
-rw-r--r--chrome/browser/ui/cocoa/handoff_active_url_observer.cc124
-rw-r--r--chrome/browser/ui/cocoa/handoff_active_url_observer.h83
-rw-r--r--chrome/browser/ui/cocoa/handoff_active_url_observer_bridge.h47
-rw-r--r--chrome/browser/ui/cocoa/handoff_active_url_observer_bridge.mm21
-rw-r--r--chrome/browser/ui/cocoa/handoff_active_url_observer_delegate.h27
8 files changed, 534 insertions, 2 deletions
diff --git a/chrome/browser/app_controller_mac.h b/chrome/browser/app_controller_mac.h
index 6eb522c..a3f6481 100644
--- a/chrome/browser/app_controller_mac.h
+++ b/chrome/browser/app_controller_mac.h
@@ -22,6 +22,8 @@ class AppControllerProfileObserver;
class BookmarkMenuBridge;
class CommandUpdater;
class GURL;
+class HandoffActiveURLObserverBridge;
+@class HandoffManager;
class HistoryMenuBridge;
class Profile;
@class ProfileMenuController;
@@ -97,6 +99,13 @@ class WorkAreaWatcherObserver;
// Displays a notification when quitting while apps are running.
scoped_refptr<QuitWithAppsController> quitWithAppsController_;
+
+ // Responsible for maintaining all state related to the Handoff feature.
+ base::scoped_nsobject<HandoffManager> handoffManager_;
+
+ // Observes changes to the active URL.
+ scoped_ptr<HandoffActiveURLObserverBridge>
+ handoff_active_url_observer_bridge_;
}
@property(readonly, nonatomic) BOOL startupComplete;
diff --git a/chrome/browser/app_controller_mac.mm b/chrome/browser/app_controller_mac.mm
index 2e2dfe5..604f217 100644
--- a/chrome/browser/app_controller_mac.mm
+++ b/chrome/browser/app_controller_mac.mm
@@ -63,6 +63,7 @@
#import "chrome/browser/ui/cocoa/confirm_quit.h"
#import "chrome/browser/ui/cocoa/confirm_quit_panel_controller.h"
#import "chrome/browser/ui/cocoa/encoding_menu_controller_delegate_mac.h"
+#include "chrome/browser/ui/cocoa/handoff_active_url_observer_bridge.h"
#import "chrome/browser/ui/cocoa/history_menu_bridge.h"
#include "chrome/browser/ui/cocoa/last_active_browser_cocoa.h"
#import "chrome/browser/ui/cocoa/profiles/profile_menu_controller.h"
@@ -83,6 +84,7 @@
#include "chrome/common/url_constants.h"
#include "chrome/grit/chromium_strings.h"
#include "chrome/grit/generated_resources.h"
+#include "components/handoff/handoff_manager.h"
#include "components/handoff/handoff_utility.h"
#include "components/signin/core/browser/signin_manager.h"
#include "components/signin/core/common/profile_management_switches.h"
@@ -209,9 +211,10 @@ bool IsProfileSignedOut(Profile* profile) {
return cache.ProfileIsSigninRequiredAtIndex(profile_index);
}
-} // anonymous namespace
+} // namespace
+
+@interface AppController () <HandoffActiveURLObserverBridgeDelegate>
-@interface AppController (Private)
- (void)initMenuState;
- (void)initProfileMenu;
- (void)updateConfirmToQuitPrefMenuItem:(NSMenuItem*)item;
@@ -240,6 +243,25 @@ bool IsProfileSignedOut(Profile* profile) {
// this method is called, and that tab is the NTP, then this method closes the
// NTP after all the |urls| have been opened.
- (void)openUrlsReplacingNTP:(const std::vector<GURL>&)urls;
+
+// Whether instances of this class should use the Handoff feature.
+- (BOOL)shouldUseHandoff;
+
+// This method passes |handoffURL| to |handoffManager_|.
+- (void)passURLToHandoffManager:(const GURL&)handoffURL;
+
+// Lazily creates the Handoff Manager. Updates the state of the Handoff
+// Manager. This method is idempotent. This should be called:
+// - During initialization.
+// - When the current tab navigates to a new URL.
+// - When the active browser changes.
+// - When the active browser's active tab switches.
+// |webContents| should be the new, active WebContents.
+- (void)updateHandoffManager:(content::WebContents*)webContents;
+
+// Given |webContents|, extracts a GURL to be used for Handoff. This may return
+// the empty GURL.
+- (GURL)handoffURLFromWebContents:(content::WebContents*)webContents;
@end
class AppControllerProfileObserver : public ProfileInfoCacheObserver {
@@ -776,6 +798,12 @@ class AppControllerProfileObserver : public ProfileInfoCacheObserver {
startupComplete_ = YES;
+ Browser* browser =
+ FindLastActiveWithHostDesktopType(chrome::HOST_DESKTOP_TYPE_NATIVE);
+ content::WebContents* activeWebContents = nullptr;
+ if (browser)
+ activeWebContents = browser->tab_strip_model()->GetActiveWebContents();
+ [self updateHandoffManager:activeWebContents];
[self openStartupUrls];
PrefService* localState = g_browser_process->local_state();
@@ -786,6 +814,9 @@ class AppControllerProfileObserver : public ProfileInfoCacheObserver {
base::Bind(&chrome::BrowserCommandController::UpdateOpenFileState,
menuState_.get()));
}
+
+ handoff_active_url_observer_bridge_.reset(
+ new HandoffActiveURLObserverBridge(self));
}
// This is called after profiles have been loaded and preferences registered.
@@ -1638,6 +1669,53 @@ class AppControllerProfileObserver : public ProfileInfoCacheObserver {
error:(NSError*)error {
}
+#pragma mark - Handoff Manager
+
+- (BOOL)shouldUseHandoff {
+ return base::mac::IsOSYosemiteOrLater();
+}
+
+- (void)passURLToHandoffManager:(const GURL&)handoffURL {
+ [handoffManager_ updateActiveURL:handoffURL];
+}
+
+- (void)updateHandoffManager:(content::WebContents*)webContents {
+ if (![self shouldUseHandoff])
+ return;
+
+ if (!handoffManager_)
+ handoffManager_.reset([[HandoffManager alloc] init]);
+
+ GURL handoffURL = [self handoffURLFromWebContents:webContents];
+ [self passURLToHandoffManager:handoffURL];
+}
+
+- (GURL)handoffURLFromWebContents:(content::WebContents*)webContents {
+ if (!webContents)
+ return GURL();
+
+ Profile* profile =
+ Profile::FromBrowserContext(webContents->GetBrowserContext());
+ if (!profile)
+ return GURL();
+
+ // Handoff is not allowed from an incognito profile. To err on the safe side,
+ // also disallow Handoff from a guest profile.
+ if (profile->GetProfileType() != Profile::REGULAR_PROFILE)
+ return GURL();
+
+ if (!webContents)
+ return GURL();
+
+ return webContents->GetVisibleURL();
+}
+
+#pragma mark - HandoffActiveURLObserverBridgeDelegate
+
+- (void)handoffActiveURLChanged:(content::WebContents*)webContents {
+ [self updateHandoffManager:webContents];
+}
+
@end // @implementation AppController
//---------------------------------------------------------------------------
diff --git a/chrome/browser/app_controller_mac_browsertest.mm b/chrome/browser/app_controller_mac_browsertest.mm
index a136a12..0400188 100644
--- a/chrome/browser/app_controller_mac_browsertest.mm
+++ b/chrome/browser/app_controller_mac_browsertest.mm
@@ -35,9 +35,11 @@
#include "chrome/common/pref_names.h"
#include "chrome/common/url_constants.h"
#include "chrome/test/base/in_process_browser_test.h"
+#include "chrome/test/base/ui_test_utils.h"
#include "components/bookmarks/test/bookmark_test_helpers.h"
#include "components/signin/core/common/profile_management_switches.h"
#include "content/public/browser/web_contents.h"
+#include "content/public/test/browser_test_utils.h"
#include "content/public/test/test_navigation_observer.h"
#include "extensions/browser/app_window/app_window_registry.h"
#include "extensions/common/extension.h"
@@ -481,3 +483,144 @@ IN_PROC_BROWSER_TEST_F(AppControllerMainMenuBrowserTest,
}
} // namespace
+
+//--------------------------AppControllerHandoffBrowserTest---------------------
+
+static GURL g_handoff_url;
+
+@interface AppController (BrowserTest)
+- (BOOL)new_shouldUseHandoff;
+- (void)new_passURLToHandoffManager:(const GURL&)handoffURL;
+@end
+
+@implementation AppController (BrowserTest)
+- (BOOL)new_shouldUseHandoff {
+ return YES;
+}
+
+- (void)new_passURLToHandoffManager:(const GURL&)handoffURL {
+ g_handoff_url = handoffURL;
+}
+@end
+
+namespace {
+
+class AppControllerHandoffBrowserTest : public InProcessBrowserTest {
+ protected:
+ AppControllerHandoffBrowserTest() {}
+
+ // Exchanges the implementations of the two selectors on the class
+ // AppController.
+ void ExchangeSelectors(SEL originalMethod, SEL newMethod) {
+ Class appControllerClass = NSClassFromString(@"AppController");
+
+ ASSERT_TRUE(appControllerClass != nil);
+
+ Method original =
+ class_getInstanceMethod(appControllerClass, originalMethod);
+ Method destination = class_getInstanceMethod(appControllerClass, newMethod);
+
+ ASSERT_TRUE(original != NULL);
+ ASSERT_TRUE(destination != NULL);
+
+ method_exchangeImplementations(original, destination);
+ }
+
+ // Swizzle Handoff related implementations.
+ void SetUpInProcessBrowserTestFixture() override {
+ // Handoff is only available on OSX 10.10+. This swizzle makes the logic
+ // run on all OSX versions.
+ SEL originalMethod = @selector(shouldUseHandoff);
+ SEL newMethod = @selector(new_shouldUseHandoff);
+ ExchangeSelectors(originalMethod, newMethod);
+
+ // This swizzle intercepts the URL that would be sent to the Handoff
+ // Manager, and instead puts it into a variable accessible to this test.
+ originalMethod = @selector(passURLToHandoffManager:);
+ newMethod = @selector(new_passURLToHandoffManager:);
+ ExchangeSelectors(originalMethod, newMethod);
+ }
+
+ // Closes the tab, and waits for the close to finish.
+ void CloseTab(Browser* browser, int index) {
+ content::WebContentsDestroyedWatcher destroyed_watcher(
+ browser->tab_strip_model()->GetWebContentsAt(index));
+ browser->tab_strip_model()->CloseWebContentsAt(
+ index, TabStripModel::CLOSE_CREATE_HISTORICAL_TAB);
+ destroyed_watcher.Wait();
+ }
+};
+
+// Tests that as a user switches between tabs, navigates within a tab, and
+// switches between browser windows, the correct URL is being passed to the
+// Handoff.
+IN_PROC_BROWSER_TEST_F(AppControllerHandoffBrowserTest, TestHandoffURLs) {
+ ASSERT_TRUE(embedded_test_server()->InitializeAndWaitUntilReady());
+ EXPECT_EQ(g_handoff_url, GURL(url::kAboutBlankURL));
+
+ // Test that navigating to a URL updates the handoff URL.
+ GURL test_url1 = embedded_test_server()->GetURL("/title1.html");
+ ui_test_utils::NavigateToURL(browser(), test_url1);
+ EXPECT_EQ(g_handoff_url, test_url1);
+
+ // Test that opening a new tab updates the handoff URL.
+ GURL test_url2 = embedded_test_server()->GetURL("/title2.html");
+ chrome::NavigateParams params(browser(), test_url2, ui::PAGE_TRANSITION_LINK);
+ params.disposition = NEW_FOREGROUND_TAB;
+ ui_test_utils::NavigateToURL(&params);
+ EXPECT_EQ(g_handoff_url, test_url2);
+
+ // Test that switching tabs updates the handoff URL.
+ browser()->tab_strip_model()->ActivateTabAt(0, true);
+ EXPECT_EQ(g_handoff_url, test_url1);
+
+ // Test that closing the current tab updates the handoff URL.
+ CloseTab(browser(), 0);
+ EXPECT_EQ(g_handoff_url, test_url2);
+
+ // Test that opening a new browser window updates the handoff URL.
+ GURL test_url3 = embedded_test_server()->GetURL("/title3.html");
+ ui_test_utils::NavigateToURLWithDisposition(
+ browser(), GURL(test_url3), NEW_WINDOW,
+ ui_test_utils::BROWSER_TEST_WAIT_FOR_BROWSER);
+ EXPECT_EQ(g_handoff_url, test_url3);
+
+ // Check that there are exactly 2 browsers.
+ BrowserList* active_browser_list =
+ BrowserList::GetInstance(chrome::GetActiveDesktop());
+ EXPECT_EQ(2u, active_browser_list->size());
+
+ // Close the one and only tab for the second browser window.
+ Browser* browser2 = active_browser_list->get(1);
+ CloseTab(browser2, 0);
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(g_handoff_url, test_url2);
+
+ // The URLs of incognito windows should not be passed to Handoff.
+ GURL test_url4 = embedded_test_server()->GetURL("/simple.html");
+ ui_test_utils::NavigateToURLWithDisposition(
+ browser(), GURL(test_url4), OFF_THE_RECORD,
+ ui_test_utils::BROWSER_TEST_WAIT_FOR_BROWSER);
+ EXPECT_EQ(g_handoff_url, GURL());
+
+ // Open a new tab in the incognito window.
+ EXPECT_EQ(2u, active_browser_list->size());
+ Browser* browser3 = active_browser_list->get(1);
+ ui_test_utils::NavigateToURLWithDisposition(
+ browser3, test_url4, NEW_FOREGROUND_TAB,
+ ui_test_utils::BROWSER_TEST_WAIT_FOR_TAB);
+ EXPECT_EQ(g_handoff_url, GURL());
+
+ // Navigate the current tab in the incognito window.
+ ui_test_utils::NavigateToURLWithDisposition(
+ browser3, test_url1, CURRENT_TAB,
+ ui_test_utils::BROWSER_TEST_WAIT_FOR_NAVIGATION);
+ EXPECT_EQ(g_handoff_url, GURL());
+
+ // Activate the original browser window.
+ Browser* browser1 = active_browser_list->get(0);
+ browser1->window()->Show();
+ EXPECT_EQ(g_handoff_url, test_url2);
+}
+
+} // namespace
diff --git a/chrome/browser/ui/cocoa/handoff_active_url_observer.cc b/chrome/browser/ui/cocoa/handoff_active_url_observer.cc
new file mode 100644
index 0000000..592a757
--- /dev/null
+++ b/chrome/browser/ui/cocoa/handoff_active_url_observer.cc
@@ -0,0 +1,124 @@
+// Copyright 2014 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.
+
+#include "chrome/browser/ui/cocoa/handoff_active_url_observer.h"
+
+#include "base/logging.h"
+#include "chrome/browser/ui/browser_finder.h"
+#include "chrome/browser/ui/browser_list.h"
+#include "chrome/browser/ui/cocoa/handoff_active_url_observer_delegate.h"
+#include "chrome/browser/ui/tabs/tab_strip_model.h"
+#include "content/public/browser/web_contents.h"
+
+HandoffActiveURLObserver::HandoffActiveURLObserver(
+ HandoffActiveURLObserverDelegate* delegate)
+ : delegate_(delegate),
+ active_tab_strip_model_(nullptr),
+ active_browser_(nullptr) {
+ DCHECK(delegate_);
+
+ active_browser_ = chrome::FindLastActiveWithHostDesktopType(
+ chrome::HOST_DESKTOP_TYPE_NATIVE);
+ BrowserList::AddObserver(this);
+ UpdateObservations();
+}
+
+HandoffActiveURLObserver::~HandoffActiveURLObserver() {
+ BrowserList::RemoveObserver(this);
+ StopObservingTabStripModel();
+ StopObservingWebContents();
+}
+
+void HandoffActiveURLObserver::OnBrowserSetLastActive(Browser* browser) {
+ active_browser_ = browser;
+ UpdateObservations();
+ delegate_->HandoffActiveURLChanged(GetActiveWebContents());
+}
+
+void HandoffActiveURLObserver::OnBrowserRemoved(Browser* removed_browser) {
+ if (active_browser_ != removed_browser)
+ return;
+
+ active_browser_ = chrome::FindLastActiveWithHostDesktopType(
+ chrome::HOST_DESKTOP_TYPE_NATIVE);
+ UpdateObservations();
+ delegate_->HandoffActiveURLChanged(GetActiveWebContents());
+}
+
+void HandoffActiveURLObserver::ActiveTabChanged(
+ content::WebContents* old_contents,
+ content::WebContents* new_contents,
+ int index,
+ int reason) {
+ StartObservingWebContents(new_contents);
+ delegate_->HandoffActiveURLChanged(new_contents);
+}
+
+void HandoffActiveURLObserver::TabStripModelDeleted() {
+ StopObservingTabStripModel();
+ StopObservingWebContents();
+}
+
+void HandoffActiveURLObserver::DidNavigateMainFrame(
+ const content::LoadCommittedDetails& details,
+ const content::FrameNavigateParams& params) {
+ delegate_->HandoffActiveURLChanged(web_contents());
+}
+
+void HandoffActiveURLObserver::WebContentsDestroyed() {
+ StopObservingWebContents();
+}
+
+void HandoffActiveURLObserver::UpdateObservations() {
+ if (!active_browser_) {
+ StopObservingTabStripModel();
+ StopObservingWebContents();
+ return;
+ }
+
+ TabStripModel* model = active_browser_->tab_strip_model();
+ StartObservingTabStripModel(model);
+
+ content::WebContents* web_contents = model->GetActiveWebContents();
+ if (web_contents)
+ StartObservingWebContents(web_contents);
+ else
+ StopObservingWebContents();
+}
+
+void HandoffActiveURLObserver::StartObservingTabStripModel(
+ TabStripModel* tab_strip_model) {
+ DCHECK(tab_strip_model);
+
+ if (active_tab_strip_model_ == tab_strip_model)
+ return;
+
+ StopObservingTabStripModel();
+ tab_strip_model->AddObserver(this);
+ active_tab_strip_model_ = tab_strip_model;
+}
+
+void HandoffActiveURLObserver::StopObservingTabStripModel() {
+ if (active_tab_strip_model_) {
+ active_tab_strip_model_->RemoveObserver(this);
+ active_tab_strip_model_ = nullptr;
+ }
+}
+
+void HandoffActiveURLObserver::StartObservingWebContents(
+ content::WebContents* web_contents) {
+ DCHECK(web_contents);
+ Observe(web_contents);
+}
+
+void HandoffActiveURLObserver::StopObservingWebContents() {
+ Observe(nullptr);
+}
+
+content::WebContents* HandoffActiveURLObserver::GetActiveWebContents() {
+ if (!active_browser_)
+ return nullptr;
+
+ return active_browser_->tab_strip_model()->GetActiveWebContents();
+}
diff --git a/chrome/browser/ui/cocoa/handoff_active_url_observer.h b/chrome/browser/ui/cocoa/handoff_active_url_observer.h
new file mode 100644
index 0000000..a46ed98
--- /dev/null
+++ b/chrome/browser/ui/cocoa/handoff_active_url_observer.h
@@ -0,0 +1,83 @@
+// Copyright 2014 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.
+
+#ifndef CHROME_BROWSER_UI_COCOA_HANDOFF_ACTIVE_URL_OBSERVER_H_
+#define CHROME_BROWSER_UI_COCOA_HANDOFF_ACTIVE_URL_OBSERVER_H_
+
+#include "chrome/browser/ui/browser_list_observer.h"
+#include "chrome/browser/ui/tabs/tab_strip_model_observer.h"
+#include "content/public/browser/web_contents_observer.h"
+
+namespace content {
+class WebContents;
+}
+
+class Browser;
+class HandoffActiveURLObserverDelegate;
+class TabStripModel;
+
+// This class observes changes to the "active URL". This is defined as the
+// visible URL of the WebContents of the selected tab of the most recently
+// focused browser window.
+class HandoffActiveURLObserver : public chrome::BrowserListObserver,
+ public TabStripModelObserver,
+ public content::WebContentsObserver {
+ public:
+ explicit HandoffActiveURLObserver(HandoffActiveURLObserverDelegate* delegate);
+ ~HandoffActiveURLObserver() override;
+
+ private:
+ // chrome::BrowserListObserver
+ void OnBrowserSetLastActive(Browser* browser) override;
+ void OnBrowserRemoved(Browser* browser) override;
+
+ // TabStripModelObserver
+ void ActiveTabChanged(content::WebContents* old_contents,
+ content::WebContents* new_contents,
+ int index,
+ int reason) override;
+ void TabStripModelDeleted() override;
+
+ // content::WebContentsObserver
+ void DidNavigateMainFrame(
+ const content::LoadCommittedDetails& details,
+ const content::FrameNavigateParams& params) override;
+ void WebContentsDestroyed() override;
+
+ // This method ensures that the instance is registered as an observer of the
+ // correct TabStripModel and WebContents for |active_browser_|.
+ void UpdateObservations();
+
+ // Makes this object start observing the TabStripModel, if it is not already
+ // doing so. This method is idempotent.
+ void StartObservingTabStripModel(TabStripModel* tab_strip_model);
+
+ // Makes this object stop observing the TabStripModel.
+ void StopObservingTabStripModel();
+
+ // Makes this object start observing the WebContents, if it is not already
+ // doing so. This method is idempotent.
+ void StartObservingWebContents(content::WebContents* web_contents);
+
+ // Makes this object stop observing the WebContents.
+ void StopObservingWebContents();
+
+ // Returns the active WebContents. May return nullptr.
+ content::WebContents* GetActiveWebContents();
+
+ // Instances of this class should be owned by their |delegate_|.
+ HandoffActiveURLObserverDelegate* delegate_;
+
+ // When this pointer is not nullptr, this object is registered as an observer
+ // of the TabStripModel.
+ TabStripModel* active_tab_strip_model_;
+
+ // This pointer is always up to date, and points to the most recently
+ // activated browser, or nullptr if no browsers exist.
+ Browser* active_browser_;
+
+ DISALLOW_COPY_AND_ASSIGN(HandoffActiveURLObserver);
+};
+
+#endif // CHROME_BROWSER_UI_COCOA_HANDOFF_ACTIVE_URL_OBSERVER_H_
diff --git a/chrome/browser/ui/cocoa/handoff_active_url_observer_bridge.h b/chrome/browser/ui/cocoa/handoff_active_url_observer_bridge.h
new file mode 100644
index 0000000..0d00961
--- /dev/null
+++ b/chrome/browser/ui/cocoa/handoff_active_url_observer_bridge.h
@@ -0,0 +1,47 @@
+// Copyright 2014 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.
+
+#ifndef CHROME_BROWSER_UI_COCOA_HANDOFF_ACTIVE_URL_OBSERVER_BRIDGE_H_
+#define CHROME_BROWSER_UI_COCOA_HANDOFF_ACTIVE_URL_OBSERVER_BRIDGE_H_
+
+#import <Cocoa/Cocoa.h>
+
+#include "base/macros.h"
+#include "base/memory/scoped_ptr.h"
+#include "chrome/browser/ui/cocoa/handoff_active_url_observer_delegate.h"
+
+namespace content {
+class WebContents;
+}
+
+class HandoffActiveURLObserver;
+
+// A protocol that allows ObjC objects to receive delegate callbacks from
+// HandoffActiveURLObserver.
+@protocol HandoffActiveURLObserverBridgeDelegate
+- (void)handoffActiveURLChanged:(content::WebContents*)webContents;
+@end
+
+// This class allows an ObjC object to receive the delegate callbacks from an
+// HandoffActiveURLObserver.
+class HandoffActiveURLObserverBridge : public HandoffActiveURLObserverDelegate {
+ public:
+ explicit HandoffActiveURLObserverBridge(
+ NSObject<HandoffActiveURLObserverBridgeDelegate>* delegate);
+
+ ~HandoffActiveURLObserverBridge() override;
+
+ private:
+ void HandoffActiveURLChanged(content::WebContents* web_contents) override;
+
+ // Instances of this class should be owned by their |delegate_|.
+ NSObject<HandoffActiveURLObserverBridgeDelegate>* delegate_;
+
+ // The C++ object that this class acts as a bridge for.
+ scoped_ptr<HandoffActiveURLObserver> observer_;
+
+ DISALLOW_COPY_AND_ASSIGN(HandoffActiveURLObserverBridge);
+};
+
+#endif // CHROME_BROWSER_UI_COCOA_HANDOFF_ACTIVE_URL_OBSERVER_BRIDGE_H_
diff --git a/chrome/browser/ui/cocoa/handoff_active_url_observer_bridge.mm b/chrome/browser/ui/cocoa/handoff_active_url_observer_bridge.mm
new file mode 100644
index 0000000..639ae93
--- /dev/null
+++ b/chrome/browser/ui/cocoa/handoff_active_url_observer_bridge.mm
@@ -0,0 +1,21 @@
+// Copyright 2014 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.
+
+#include "chrome/browser/ui/cocoa/handoff_active_url_observer_bridge.h"
+
+#include "chrome/browser/ui/cocoa/handoff_active_url_observer.h"
+
+HandoffActiveURLObserverBridge::HandoffActiveURLObserverBridge(
+ NSObject<HandoffActiveURLObserverBridgeDelegate>* delegate)
+ : delegate_(delegate) {
+ DCHECK(delegate_);
+ observer_.reset(new HandoffActiveURLObserver(this));
+}
+
+HandoffActiveURLObserverBridge::~HandoffActiveURLObserverBridge(){};
+
+void HandoffActiveURLObserverBridge::HandoffActiveURLChanged(
+ content::WebContents* web_contents) {
+ [delegate_ handoffActiveURLChanged:web_contents];
+}
diff --git a/chrome/browser/ui/cocoa/handoff_active_url_observer_delegate.h b/chrome/browser/ui/cocoa/handoff_active_url_observer_delegate.h
new file mode 100644
index 0000000..d0d0fff
--- /dev/null
+++ b/chrome/browser/ui/cocoa/handoff_active_url_observer_delegate.h
@@ -0,0 +1,27 @@
+// Copyright 2014 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.
+
+#ifndef CHROME_BROWSER_UI_COCOA_HANDOFF_ACTIVE_URL_OBSERVER_DELEGATE_H_
+#define CHROME_BROWSER_UI_COCOA_HANDOFF_ACTIVE_URL_OBSERVER_DELEGATE_H_
+
+namespace content {
+class WebContents;
+}
+
+// The delegate for a HandoffActiveURLObserver.
+class HandoffActiveURLObserverDelegate {
+ public:
+ // Called when:
+ // 1. The most recently focused browser changes.
+ // 2. The active tab of the browser changes.
+ // 3. After a navigation of the web contents of the active tab.
+ // |web_contents| is the WebContents whose VisibleURL is considered the
+ // "Active URL" of Chrome.
+ virtual void HandoffActiveURLChanged(content::WebContents* web_contents) = 0;
+
+ protected:
+ virtual ~HandoffActiveURLObserverDelegate(){};
+};
+
+#endif // CHROME_BROWSER_UI_COCOA_HANDOFF_ACTIVE_URL_OBSERVER_DELEGATE_H_