Make UI or API driven extension options navigations refocus any existing options page.

This is already the way that the options pages embedded in chrome://extensions
work. Options pages not embedded (those declared via "options_page" or with
{"open_in_tab": true} in "options_ui") currently always open in a new tab - now
it will refocus instead.

This will affect any caller of ExtensionTabUtil::OpenOptionsPage - called from
UI like the toolbar button's context menu, and from the
chrome.browser.openOptionsPage API call.

An important side-effect of all of this is that the old behavior is buggy when
a devtools window is focused (see crash report), and this fixes it. This was
impossible to trigger until we added the chrome.browser.openOptionsPage() - now
quite likely that a devtools window is focused.

BUG=430054, 463341
R=rockot@chromium.org

Review URL: https://codereview.chromium.org/1005673002

Cr-Commit-Position: refs/heads/master@{#320426}
diff --git a/chrome/browser/extensions/extension_tab_util.cc b/chrome/browser/extensions/extension_tab_util.cc
index d5feb70..706f41e 100644
--- a/chrome/browser/extensions/extension_tab_util.cc
+++ b/chrome/browser/extensions/extension_tab_util.cc
@@ -590,35 +590,26 @@
     browser = displayer->browser();
   }
 
-  if (!OptionsPageInfo::ShouldOpenInTab(extension)) {
-    // If we should embed the options page for this extension, open
-    // chrome://extensions in a new tab and show the extension options in an
-    // embedded popup.
-    chrome::NavigateParams params(chrome::GetSingletonTabNavigateParams(
-        browser, GURL(chrome::kChromeUIExtensionsURL)));
-    params.path_behavior = chrome::NavigateParams::IGNORE_AND_NAVIGATE;
-
+  GURL url_to_navigate;
+  if (OptionsPageInfo::ShouldOpenInTab(extension)) {
+    // Options page tab is simply e.g. chrome-extension://.../options.html.
+    url_to_navigate = OptionsPageInfo::GetOptionsPage(extension);
+  } else {
+    // Options page tab is Extension settings pointed at that Extension's ID,
+    // e.g. chrome://extensions?options=...
+    url_to_navigate = GURL(chrome::kChromeUIExtensionsURL);
     GURL::Replacements replacements;
     std::string query =
         base::StringPrintf("options=%s", extension->id().c_str());
     replacements.SetQueryStr(query);
-    params.url = params.url.ReplaceComponents(replacements);
-
-    chrome::ShowSingletonTabOverwritingNTP(browser, params);
-  } else {
-    // Otherwise open a new tab with the extension's options page
-    content::OpenURLParams params(OptionsPageInfo::GetOptionsPage(extension),
-                                  content::Referrer(),
-                                  SINGLETON_TAB,
-                                  ui::PAGE_TRANSITION_LINK,
-                                  false);
-    browser->OpenURL(params);
-    browser->window()->Show();
-    WebContents* web_contents =
-        browser->tab_strip_model()->GetActiveWebContents();
-    web_contents->GetDelegate()->ActivateContents(web_contents);
+    url_to_navigate = url_to_navigate.ReplaceComponents(replacements);
   }
 
+  chrome::NavigateParams params(
+      chrome::GetSingletonTabNavigateParams(browser, url_to_navigate));
+  params.path_behavior = chrome::NavigateParams::IGNORE_AND_NAVIGATE;
+  params.url = url_to_navigate;
+  chrome::ShowSingletonTabOverwritingNTP(browser, params);
   return true;
 }
 
diff --git a/chrome/test/data/extensions/api_test/runtime/open_options_page/manifest.json b/chrome/test/data/extensions/api_test/runtime/open_options_page/manifest.json
index ea13b2b..81f6f54 100644
--- a/chrome/test/data/extensions/api_test/runtime/open_options_page/manifest.json
+++ b/chrome/test/data/extensions/api_test/runtime/open_options_page/manifest.json
@@ -8,5 +8,6 @@
   },
   "options_ui": {
     "page": "options.html"
-  }
+  },
+  "permissions": ["tabs"]
 }
diff --git a/chrome/test/data/extensions/api_test/runtime/open_options_page/test.js b/chrome/test/data/extensions/api_test/runtime/open_options_page/test.js
index 471da12..049167a8 100644
--- a/chrome/test/data/extensions/api_test/runtime/open_options_page/test.js
+++ b/chrome/test/data/extensions/api_test/runtime/open_options_page/test.js
@@ -5,14 +5,64 @@
 // browser_tests --gtest_filter=ExtensionApiTest.OpenOptionsPage
 
 var assertEq = chrome.test.assertEq;
+var assertTrue = chrome.test.assertTrue;
+var listenOnce = chrome.test.listenOnce;
+var pass = chrome.test.callbackPass;
 
-function test() {
-  chrome.test.listenOnce(chrome.runtime.onMessage, function(m, sender) {
-    assertEq('success', m);
-    assertEq(chrome.runtime.id, sender.id);
-    assertEq(chrome.runtime.getURL('options.html'), sender.url);
-  });
-  chrome.runtime.openOptionsPage();
+var optionsTabUrl = 'chrome://extensions/?options=' + chrome.runtime.id;
+
+// Finds the Tab for an options page, or null if no options page is open.
+// Asserts that there is at most 1 options page open.
+// Result is passed to |callback|.
+function findOptionsTab(callback) {
+  chrome.tabs.query({url: optionsTabUrl}, pass(function(tabs) {
+    assertTrue(tabs.length <= 1);
+    callback(tabs.length == 0 ? null : tabs[0]);
+  }));
 }
 
-chrome.test.runTests([test]);
+// Tests opening a new options page.
+function testNewOptionsPage() {
+  findOptionsTab(function(tab) {
+    assertEq(null, tab);
+    listenOnce(chrome.runtime.onMessage, function(m, sender) {
+      assertEq('success', m);
+      assertEq(chrome.runtime.id, sender.id);
+      assertEq(chrome.runtime.getURL('options.html'), sender.url);
+    });
+    chrome.runtime.openOptionsPage();
+  });
+}
+
+// Gets the active tab, or null if no tab is active. Asserts that there is at
+// most 1 active tab. Result is passed to |callback|.
+function getActiveTab(callback) {
+  chrome.tabs.query({active: true}, pass(function(tabs) {
+    assertTrue(tabs.length <= 1);
+    callback(tabs.length == 0 ? null : tabs[0]);
+  }));
+}
+
+// Tests refocusing an existing page.
+function testRefocusExistingOptionsPage() {
+  var testUrl = 'chrome://chrome/';
+
+  // There will already be an options page open from the last test. Find it,
+  // focus away from it, then make sure openOptionsPage() refocuses it.
+  findOptionsTab(function(optionsTab) {
+    assertTrue(optionsTab != null);
+    chrome.tabs.create({url: testUrl}, pass(function(tab) {
+      // Make sure the new tab is active.
+      getActiveTab(function(activeTab) {
+        assertEq(testUrl, activeTab.url);
+        // Open options page should refocus it.
+        chrome.runtime.openOptionsPage();
+        getActiveTab(function(activeTab) {
+          assertEq(optionsTabUrl, activeTab.url);
+        });
+      });
+    }));
+  });
+}
+
+chrome.test.runTests([testNewOptionsPage, testRefocusExistingOptionsPage]);