Bug 757823 - Make Account Provisioner order-form tab handle links properly. r+ui-r=bwinton, a=Standard8.
authorMike Conley <mconley@mozilla.com>
Mon, 28 May 2012 17:03:37 -0400
changeset 11349 e893db8a2ea501bd69b388429e48d99780f18be9
parent 11348 da6af1c55a328769a5cda979820c8f63bcd2a217
child 11350 f875a2e013396434c9dd94ffe9e437280f2e4e90
push id513
push usermconley@mozilla.com
push dateMon, 28 May 2012 21:06:09 +0000
treeherdercomm-beta@ff96b286f877 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersStandard8
bugs757823
Bug 757823 - Make Account Provisioner order-form tab handle links properly. r+ui-r=bwinton, a=Standard8.
mail/components/newmailaccount/content/accountProvisionerTab.js
mail/test/mozmill/newmailaccount/html/registration.html
mail/test/mozmill/newmailaccount/html/target.html
mail/test/mozmill/newmailaccount/test-newmailaccount.js
mail/test/mozmill/shared-modules/test-content-tab-helpers.js
--- a/mail/components/newmailaccount/content/accountProvisionerTab.js
+++ b/mail/components/newmailaccount/content/accountProvisionerTab.js
@@ -27,16 +27,17 @@ let accountProvisionerTabType = Object.c
 
 /**
  * Here, we're overriding openTab - first we call the openTab of contentTab
  * (for the context of this accountProvisionerTab "aTab") and then passing
  * special arguments "realName", "email" and "searchEngine" from the caller
  * of openTab, and passing those to our _setMonitoring function.
  */
 accountProvisionerTabType.openTab = function(aTab, aArgs) {
+  aArgs.clickHandler = "accountProvisionerTabType.clickHandler(event);";
   specialTabs.contentTabType.openTab.call(this, aTab, aArgs);
 
   this._setMonitoring(aTab.browser, aArgs.realName, aArgs.email,
                       aArgs.searchEngine);
 }
 
 /**
  * We're overriding closeTab - first, we call the closeTab of contentTab,
@@ -71,8 +72,32 @@ accountProvisionerTabType._setMonitoring
   });
 
   // Register our observer
   Services.obs.addObserver(this._observer, "http-on-examine-response",
                            false);
 
   this._log.info("httpRequestObserver wired up.");
 }
+
+/**
+ * Click handler for the Account Provisioner tab that allows all links
+ * to open within the current content tab, except for those which have
+ * their targets set to _blank - these links open in the default browser.
+ */
+accountProvisionerTabType.clickHandler = function(aEvent) {
+  // Don't handle events that: a) aren't trusted, b) have already been
+  // handled or c) aren't left-click.
+  if (!aEvent.isTrusted || aEvent.defaultPrevented || aEvent.button)
+    return true;
+
+  let href = hRefForClickEvent(aEvent, true);
+
+  // Check to see if we're set to open the link externally...
+  if (aEvent.target.hasAttribute("target")) {
+    if (aEvent.target.target == "_blank") {
+      aEvent.preventDefault();
+      openLinkExternally(href);
+    }
+  }
+
+  return false;
+}
--- a/mail/test/mozmill/newmailaccount/html/registration.html
+++ b/mail/test/mozmill/newmailaccount/html/registration.html
@@ -1,21 +1,25 @@
 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
   <head>
     <meta http-equiv="content-type" content="text/html; charset=UTF-8">
     <title>Fake registration page</title>
   </head>
   <body>
-  
     <div class="title">Local version</div>
     <div class="content">
       <form action="config.xml" method="GET">
          <p>
          First name: <input value="Green" id="first" name="firstname" type="text"><br>
          Last name: <input value="Llama" id="last" name="lastname" type="text"><br>
          Email: <input value="da.green.llama@bar.com" id="email" name="email" type="text"><br>
          <input value="Send" type="submit">
          </p>
       </form>
+      <a id="external" href="target.html" target="_blank">Should open externally</a>
+      <a id="internal" href="target.html">Should open internally</a>
+      <p id="newtab" onclick="window.open('target.html');">
+        Should open in a new content tab.
+      </p>
     </div>
   </body>
 </html>
new file mode 100644
--- /dev/null
+++ b/mail/test/mozmill/newmailaccount/html/target.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
+  <head>
+    <meta http-equiv="content-type" content="text/html; charset=UTF-8">
+    <title>Well, how do you do!</title>
+  </head>
+  <body>
+    <h1>Testing, testing, 1..2..3..</h1>
+  </body>
+</html>
--- a/mail/test/mozmill/newmailaccount/test-newmailaccount.js
+++ b/mail/test/mozmill/newmailaccount/test-newmailaccount.js
@@ -607,29 +607,17 @@ function subtest_shows_error_on_empty_su
 
 /**
  * Tests that if a provider returns broken or erroneous XML back
  * to the user after account registration, that we log the error
  * in the error console.
  */
 function test_throws_console_error_on_corrupt_XML() {
   // Open the provisioner - once opened, let subtest_get_an_account run.
-  plan_for_modal_dialog("AccountCreation",
-                        subtest_throws_console_error_on_corrupt_XML);
-  open_provisioner_window();
-  wait_for_modal_dialog("AccountCreation");
-
-  // Once we're here, subtest_get_an_account has completed, and we're waiting
-  // for a content tab to load for the account order form.
-
-  // Make sure the page is loaded.
-  wait_for_content_tab_load(undefined, function (aURL) {
-    return aURL.host == "localhost";
-  });
-
+  get_to_order_form("corrupt@corrupt.nul");
   let tab = mc.tabmail.currentTabInfo;
 
   // Record how many accounts we start with.
   gNumAccounts = nAccounts();
 
   gConsoleListener.reset();
   gConsoleListener.listenFor("Problem interpreting provider XML:");
 
@@ -637,44 +625,18 @@ function test_throws_console_error_on_co
 
   // Click the OK button to order the account.
   let btn = tab.browser.contentWindow.document.querySelector("input[value=Send]");
   mc.click(new elib.Elem(btn));
 
   gConsoleListener.wait();
 
   Services.console.unregisterListener(gConsoleListener);
-}
 
-/**
- * Subtest for test_throws_console_error_on_corrupt_XML.  This
- * function does a search for an email address, and then chooses
- * an address from a provider that returns corrupt XML.
- */
-function subtest_throws_console_error_on_corrupt_XML(w) {
-  wait_for_provider_list_loaded(w);
-  wait_for_search_ready(w);
-  let $ = w.window.$;
-
-  // Make sure that only the corrupt provider is checked.
-  $('input[type="checkbox"]').not('[value="corrupt"]').removeAttr("checked");
-
-  // Fill in some data
-  $("#name").val("Green Llama");
-  $("#searchSubmit").click();
-  wait_for_search_results(w);
-
-  // Click on the first address. This reveals the button with the price.
-  $(".address:first").click();
-  mc.waitFor(function () $("button.create:visible").length > 0);
-
-  plan_for_content_tab_load();
-
-  // Clicking this button should close the modal dialog.
-  $('button.create[address="corrupt@corrupt.nul"]').click();
+  mc.tabmail.closeTab(tab);
 }
 
 /**
  * Test that if the providerList is invalid or broken JSON, that
  * we "go offline" and display an error message.
  */
 function test_broken_provider_list_goes_offline() {
   let original = Services.prefs.getCharPref(kProviderListPref);
@@ -893,16 +855,130 @@ function test_other_lang_link_hides() {
   plan_for_modal_dialog("AccountCreation",
                         subtest_other_lang_link_hides);
   open_provisioner_window();
   wait_for_modal_dialog("AccountCreation");
   Services.prefs.setCharPref(kProviderListPref, original);
 }
 
 /**
- * Subtest for test_other_lang_link_hides that just waits for the provider list
- * to be loaded, and then ensures that the "show me providers in other
+ * Subtest for test_other_lang_link_hides that just waits for the provider
+ * list to be loaded, and then ensures that the "show me providers in other
  * languages" link is not visible.
  */
 function subtest_other_lang_link_hides(w) {
   wait_for_provider_list_loaded(w);
   wait_for_element_invisible(w, "otherLangDesc");
 }
+
+/**
+ * Quickly get us to the default order form (registration.html) and return
+ * when we're there.
+ */
+function get_to_order_form(aAddress) {
+  if (!aAddress)
+    aAddress = "green@example.com";
+
+  plan_for_modal_dialog("AccountCreation", function(aController) {
+    sub_get_to_order_form(aController, aAddress);
+  });
+  open_provisioner_window();
+  wait_for_modal_dialog("AccountCreation");
+
+  // Once we're here, subtest_get_an_account has completed, and we're waiting
+  // for a content tab to load for the account order form.
+
+  // Make sure the page is loaded.
+  wait_for_content_tab_load(undefined, function (aURL) {
+    return aURL.host == "localhost";
+  });
+}
+
+/**
+ * Fills in the Account Provisioner dialog to get us to the order form.
+ */
+function sub_get_to_order_form(aController, aAddress) {
+  wait_for_provider_list_loaded(aController);
+  wait_for_search_ready(aController);
+
+  // Fill in some data
+  let $ = aController.window.$;
+  $("#name").val("Joe Nobody");
+  $("#searchSubmit").click();
+  wait_for_search_results(aController);
+
+  // Click on the first address. This reveals the button with the price.
+  $(".address:first").click();
+  mc.waitFor(function () $("button.create:visible").length > 0);
+
+  // Pick the email address green@example.com
+  plan_for_content_tab_load();
+
+  // Clicking this button should close the modal dialog.
+  $('button.create[address="' + aAddress + '"]').click();
+}
+
+/**
+ * Test that clicking on links in the order form open in the same account
+ * provisioner tab.
+ */
+function test_internal_link_opening_behaviour() {
+  get_to_order_form();
+
+  // Open the provisioner - once opened, let subtest_get_an_account run...
+  let tab = mc.tabmail.currentTabInfo;
+  let doc = tab.browser.contentWindow.document;
+
+  // Click on the internal link.
+  mc.click(new elib.Elem(doc.getElementById("internal")));
+
+  // We should load the target page in the current tab browser.
+  wait_for_browser_load(tab.browser, function(aURL) {
+    return aURL.host == "localhost" && aURL.path == "/target.html";
+  });
+  // Now close the tab.
+  mc.tabmail.closeTab(tab);
+}
+
+/**
+ * Test that window.open in the order form opens in new content tabs.
+ */
+function test_window_open_link_opening_behaviour() {
+  get_to_order_form();
+
+  let tab = mc.tabmail.currentTabInfo;
+  let doc = tab.browser.contentWindow.document;
+
+  // First, click on the Javascript link - this should open in a new content
+  // tab and be focused.
+  let newTabLink = doc.getElementById("newtab");
+  open_content_tab_with_click(newTabLink, function(aURL) {
+    return aURL.host == "localhost" && aURL.path == "/target.html";
+  });
+
+  // Close the new tab.
+  let newTab = mc.tabmail.currentTabInfo;
+  mc.tabmail.closeTab(newTab);
+  mc.tabmail.closeTab(tab);
+}
+
+/**
+ * Test that links with target="_blank" open in the default browser.
+ */
+function test_external_link_opening_behaviour() {
+  get_to_order_form();
+
+  let tab = mc.tabmail.currentTabInfo;
+  let doc = tab.browser.contentWindow.document;
+
+  // Mock out the ExternalProtocolService.
+  gMockExtProtSvcReg.register();
+
+  let external = doc.getElementById("external");
+  let targetHref = external.href;
+  mc.click(new elib.Elem(external));
+
+  mc.waitFor(function () gMockExtProtSvc.urlLoaded(targetHref),
+             "Timed out waiting for the link " + targetHref + "to be " +
+             "opened in the default browser.");
+  gMockExtProtSvcReg.unregister();
+  mc.tabmail.closeTab(tab);
+}
--- a/mail/test/mozmill/shared-modules/test-content-tab-helpers.js
+++ b/mail/test/mozmill/shared-modules/test-content-tab-helpers.js
@@ -47,35 +47,42 @@ var utils = {};
 Cu.import('resource://mozmill/modules/utils.js', utils);
 Cu.import("resource://gre/modules/Services.jsm");
 
 const MODULE_NAME = 'content-tab-helpers';
 
 const RELATIVE_ROOT = '../shared-modules';
 
 // we need this for the main controller
-const MODULE_REQUIRES = ['folder-display-helpers', 'window-helpers'];
+const MODULE_REQUIRES = ['folder-display-helpers',
+                         'window-helpers',
+                         'mock-object-helpers'];
 
 const NORMAL_TIMEOUT = 6000;
 const FAST_TIMEOUT = 1000;
 const FAST_INTERVAL = 100;
+const EXT_PROTOCOL_SVC_CID = "@mozilla.org/uriloader/external-protocol-service;1";
 
 var folderDisplayHelper;
 var mc;
 var wh;
 
 // logHelper (and therefore folderDisplayHelper) exports
 var mark_failure;
+let gMockExtProtocolSvcReg;
 
 function setupModule() {
   folderDisplayHelper = collector.getModule('folder-display-helpers');
   mc = folderDisplayHelper.mc;
   mark_failure = folderDisplayHelper.mark_failure;
 
   wh = collector.getModule('window-helpers');
+  let moh = collector.getModule('mock-object-helpers');
+  gMockExtProtSvcReg = new moh.MockObjectReplacer(EXT_PROTOCOL_SVC_CID,
+                                                  MockExtProtConstructor);
 }
 
 function installInto(module) {
   setupModule();
 
   // Now copy helper functions
   module.open_content_tab_with_url = open_content_tab_with_url;
   module.open_content_tab_with_click = open_content_tab_with_click;
@@ -90,18 +97,63 @@ function installInto(module) {
   module.assert_content_tab_element_visible = assert_content_tab_element_visible;
   module.wait_for_content_tab_element_display_value = wait_for_content_tab_element_display_value;
   module.assert_content_tab_text_present = assert_content_tab_text_present;
   module.assert_content_tab_text_absent = assert_content_tab_text_absent;
   module.NotificationWatcher = NotificationWatcher;
   module.get_notification_bar_for_tab = get_notification_bar_for_tab;
   module.get_test_plugin = get_test_plugin;
   module.plugins_run_in_separate_processes = plugins_run_in_separate_processes;
+  module.gMockExtProtSvcReg = gMockExtProtSvcReg;
+  module.gMockExtProtSvc = gMockExtProtSvc;
 }
 
+/**
+ * gMockExtProtocolSvc allows us to capture (most if not all) attempts to
+ * open links in the default browser.
+ */
+let gMockExtProtSvc = {
+  _loadedURLs: [],
+  QueryInterface: XPCOMUtils.generateQI([Ci.nsIExternalProtocolService]),
+
+  externalProtocolHandlerExists: function(aProtocolScheme) {
+  },
+
+  getApplicationDescription: function(aScheme) {
+  },
+
+  getProtocolHandlerInfo: function(aProtocolScheme) {
+  },
+
+  getProtocolHandlerInfoFromOS: function(aProtocolScheme, aFound) {
+  },
+
+  isExposedProtocol: function(aProtocolScheme) {
+  },
+
+  loadURI: function(aURI, aWindowContext) {
+  },
+
+  loadUrl: function(aURL) {
+    this._loadedURLs.push(aURL.spec);
+  },
+
+  setProtocolHandlerDefaults: function(aHandlerInfo, aOSHandlerExists) {
+  },
+
+  urlLoaded: function(aURL) {
+    return this._loadedURLs.indexOf(aURL) != -1;
+  },
+}
+
+function MockExtProtConstructor() {
+  return gMockExtProtSvc;
+}
+
+
 /* Allows for planning / capture of notification events within
  * content tabs, for example: plugin crash notifications, theme
  * install notifications.
  */
 const ALERT_TIMEOUT = 10000;
 
 let NotificationWatcher = {
   planForNotification: function(aController) {