Bug 846635 - Use asynchronous getCharsetForURI in getShortcutOrURI. r=mano
authorRaymond Lee <raymond@raysquare.com>
Tue, 25 Jun 2013 09:26:22 +0800
changeset 137013 19845a51b863e6b8b05b6db42a952d63fa063b3f
parent 137012 821104668b010c6e5207e278bf723490df678a90
child 137014 caf93078fbdb22ad59723f4606099143c5f158df
push id30348
push userryanvm@gmail.com
push dateMon, 01 Jul 2013 13:27:45 +0000
treeherdermozilla-inbound@0f1e1dbd0b6e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmano
bugs846635
milestone25.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 846635 - Use asynchronous getCharsetForURI in getShortcutOrURI. r=mano
browser/base/content/browser.js
browser/base/content/openLocation.js
browser/base/content/tabbrowser.xml
browser/base/content/test/browser_contentAreaClick.js
browser/base/content/test/browser_getshortcutoruri.js
browser/base/content/urlbarBindings.xml
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -4,16 +4,19 @@
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 let Ci = Components.interfaces;
 let Cu = Components.utils;
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource:///modules/RecentWindow.jsm");
 
+XPCOMUtils.defineLazyModuleGetter(this, "Task",
+                                  "resource://gre/modules/Task.jsm");
+
 const nsIWebNavigation = Ci.nsIWebNavigation;
 
 var gCharsetMenu = null;
 var gLastBrowserCharset = null;
 var gPrevCharset = null;
 var gProxyFavIcon = null;
 var gLastValidURLStr = "";
 var gInPrintPreviewMode = false;
@@ -1920,98 +1923,99 @@ function loadURI(uri, referrer, postData
   if (allowThirdPartyFixup)
     flags |= nsIWebNavigation.LOAD_FLAGS_ALLOW_THIRD_PARTY_FIXUP;
 
   try {
     gBrowser.loadURIWithFlags(uri, flags, referrer, null, postData);
   } catch (e) {}
 }
 
-function getShortcutOrURI(aURL, aPostDataRef, aMayInheritPrincipal) {
-  // Initialize outparam to false
-  if (aMayInheritPrincipal)
-    aMayInheritPrincipal.value = false;
-
-  var shortcutURL = null;
-  var keyword = aURL;
-  var param = "";
-
-  var offset = aURL.indexOf(" ");
-  if (offset > 0) {
-    keyword = aURL.substr(0, offset);
-    param = aURL.substr(offset + 1);
-  }
-
-  if (!aPostDataRef)
-    aPostDataRef = {};
-
-  var engine = Services.search.getEngineByAlias(keyword);
-  if (engine) {
-    var submission = engine.getSubmission(param);
-    aPostDataRef.value = submission.postData;
-    return submission.uri.spec;
-  }
-
-  [shortcutURL, aPostDataRef.value] =
-    PlacesUtils.getURLAndPostDataForKeyword(keyword);
-
-  if (!shortcutURL)
-    return aURL;
-
-  var postData = "";
-  if (aPostDataRef.value)
-    postData = unescape(aPostDataRef.value);
-
-  if (/%s/i.test(shortcutURL) || /%s/i.test(postData)) {
-    var charset = "";
-    const re = /^(.*)\&mozcharset=([a-zA-Z][_\-a-zA-Z0-9]+)\s*$/;
-    var matches = shortcutURL.match(re);
-    if (matches)
-      [, shortcutURL, charset] = matches;
-    else {
-      // Try to get the saved character-set.
-      try {
-        // makeURI throws if URI is invalid.
-        // Will return an empty string if character-set is not found.
-        charset = PlacesUtils.history.getCharsetForURI(makeURI(shortcutURL));
-      } catch (e) {}
-    }
-
-    // encodeURIComponent produces UTF-8, and cannot be used for other charsets.
-    // escape() works in those cases, but it doesn't uri-encode +, @, and /.
-    // Therefore we need to manually replace these ASCII characters by their
-    // encodeURIComponent result, to match the behavior of nsEscape() with
-    // url_XPAlphas
-    var encodedParam = "";
-    if (charset && charset != "UTF-8")
-      encodedParam = escape(convertFromUnicode(charset, param)).
-                     replace(/[+@\/]+/g, encodeURIComponent);
-    else // Default charset is UTF-8
-      encodedParam = encodeURIComponent(param);
-
-    shortcutURL = shortcutURL.replace(/%s/g, encodedParam).replace(/%S/g, param);
-
-    if (/%s/i.test(postData)) // POST keyword
-      aPostDataRef.value = getPostDataStream(postData, param, encodedParam,
-                                             "application/x-www-form-urlencoded");
-  }
-  else if (param) {
-    // This keyword doesn't take a parameter, but one was provided. Just return
-    // the original URL.
-    aPostDataRef.value = null;
-
-    return aURL;
-  }
-
-  // This URL came from a bookmark, so it's safe to let it inherit the current
-  // document's principal.
-  if (aMayInheritPrincipal)
-    aMayInheritPrincipal.value = true;
-
-  return shortcutURL;
+function getShortcutOrURIAndPostData(aURL) {
+  return Task.spawn(function() {
+    let mayInheritPrincipal = false;
+    let postData = null;
+    let shortcutURL = null;
+    let keyword = aURL;
+    let param = "";
+
+    let offset = aURL.indexOf(" ");
+    if (offset > 0) {
+      keyword = aURL.substr(0, offset);
+      param = aURL.substr(offset + 1);
+    }
+
+    let engine = Services.search.getEngineByAlias(keyword);
+    if (engine) {
+      let submission = engine.getSubmission(param);
+      postData = submission.postData;
+      throw new Task.Result({ postData: submission.postData,
+                              url: submission.uri.spec,
+                              mayInheritPrincipal: mayInheritPrincipal });
+    }
+
+    [shortcutURL, postData] =
+      PlacesUtils.getURLAndPostDataForKeyword(keyword);
+
+    if (!shortcutURL)
+      throw new Task.Result({ postData: postData, url: aURL,
+                              mayInheritPrincipal: mayInheritPrincipal });
+
+    let escapedPostData = "";
+    if (postData)
+      escapedPostData = unescape(postData);
+
+    if (/%s/i.test(shortcutURL) || /%s/i.test(escapedPostData)) {
+      let charset = "";
+      const re = /^(.*)\&mozcharset=([a-zA-Z][_\-a-zA-Z0-9]+)\s*$/;
+      let matches = shortcutURL.match(re);
+      if (matches)
+        [, shortcutURL, charset] = matches;
+      else {
+        // Try to get the saved character-set.
+        try {
+          // makeURI throws if URI is invalid.
+          // Will return an empty string if character-set is not found.
+          charset = yield PlacesUtils.getCharsetForURI(makeURI(shortcutURL));
+        } catch (e) {}
+      }
+
+      // encodeURIComponent produces UTF-8, and cannot be used for other charsets.
+      // escape() works in those cases, but it doesn't uri-encode +, @, and /.
+      // Therefore we need to manually replace these ASCII characters by their
+      // encodeURIComponent result, to match the behavior of nsEscape() with
+      // url_XPAlphas
+      let encodedParam = "";
+      if (charset && charset != "UTF-8")
+        encodedParam = escape(convertFromUnicode(charset, param)).
+                       replace(/[+@\/]+/g, encodeURIComponent);
+      else // Default charset is UTF-8
+        encodedParam = encodeURIComponent(param);
+
+      shortcutURL = shortcutURL.replace(/%s/g, encodedParam).replace(/%S/g, param);
+
+      if (/%s/i.test(escapedPostData)) // POST keyword
+        postData = getPostDataStream(escapedPostData, param, encodedParam,
+                                               "application/x-www-form-urlencoded");
+    }
+    else if (param) {
+      // This keyword doesn't take a parameter, but one was provided. Just return
+      // the original URL.
+      postData = null;
+
+      throw new Task.Result({ postData: postData, url: aURL,
+                              mayInheritPrincipal: mayInheritPrincipal });
+    }
+
+    // This URL came from a bookmark, so it's safe to let it inherit the current
+    // document's principal.
+    mayInheritPrincipal = true;
+
+    throw new Task.Result({ postData: postData, url: shortcutURL,
+                            mayInheritPrincipal: mayInheritPrincipal });
+  });
 }
 
 function getPostDataStream(aStringData, aKeyword, aEncKeyword, aType) {
   var dataStream = Cc["@mozilla.org/io/string-input-stream;1"].
                    createInstance(Ci.nsIStringInputStream);
   aStringData = aStringData.replace(/%s/g, aEncKeyword).replace(/%S/g, aKeyword);
   dataStream.data = aStringData;
 
@@ -2838,42 +2842,44 @@ var newTabButtonObserver = {
 
   onDragExit: function (aEvent)
   {
   },
 
   onDrop: function (aEvent)
   {
     let url = browserDragAndDrop.drop(aEvent, { });
-    var postData = {};
-    url = getShortcutOrURI(url, postData);
-    if (url) {
-      // allow third-party services to fixup this URL
-      openNewTabWith(url, null, postData.value, aEvent, true);
-    }
+    Task.spawn(function() {
+      let data = yield getShortcutOrURIAndPostData(url);
+      if (data.url) {
+        // allow third-party services to fixup this URL
+        openNewTabWith(data.url, null, data.postData, aEvent, true);
+      }
+    });
   }
 }
 
 var newWindowButtonObserver = {
   onDragOver: function (aEvent)
   {
     browserDragAndDrop.dragOver(aEvent);
   },
   onDragExit: function (aEvent)
   {
   },
   onDrop: function (aEvent)
   {
     let url = browserDragAndDrop.drop(aEvent, { });
-    var postData = {};
-    url = getShortcutOrURI(url, postData);
-    if (url) {
-      // allow third-party services to fixup this URL
-      openNewWindowWith(url, null, postData.value, true);
-    }
+    Task.spawn(function() {
+      let data = yield getShortcutOrURIAndPostData(url);
+      if (data.url) {
+        // allow third-party services to fixup this URL
+        openNewWindowWith(data.url, null, data.postData, true);
+      }
+    });
   }
 }
 
 const DOMLinkHandler = {
   handleEvent: function (event) {
     switch (event.type) {
       case "DOMLinkAdded":
         this.onLinkAdded(event);
@@ -5234,46 +5240,62 @@ function middleMousePaste(event) {
   let clipboard = readFromClipboard();
   if (!clipboard)
     return;
 
   // Strip embedded newlines and surrounding whitespace, to match the URL
   // bar's behavior (stripsurroundingwhitespace)
   clipboard = clipboard.replace(/\s*\n\s*/g, "");
 
-  let mayInheritPrincipal = { value: false };
-  let url = getShortcutOrURI(clipboard, mayInheritPrincipal);
-  try {
-    makeURI(url);
-  } catch (ex) {
-    // Not a valid URI.
-    return;
+  // if it's not the current tab, we don't need to do anything because the 
+  // browser doesn't exist.
+  let where = whereToOpenLink(event, true, false);
+  let lastLocationChange;
+  if (where == "current") {
+    lastLocationChange = gBrowser.selectedBrowser.lastLocationChange;
   }
 
-  try {
-    addToUrlbarHistory(url);
-  } catch (ex) {
-    // Things may go wrong when adding url to session history,
-    // but don't let that interfere with the loading of the url.
-    Cu.reportError(ex);
-  }
-
-  openUILink(url, event,
-             { ignoreButton: true,
-               disallowInheritPrincipal: !mayInheritPrincipal.value });
+  Task.spawn(function() {
+    let data = yield getShortcutOrURIAndPostData(clipboard);
+    try {
+      makeURI(data.url);
+    } catch (ex) {
+      // Not a valid URI.
+      return;
+    }
+
+    try {
+      addToUrlbarHistory(data.url);
+    } catch (ex) {
+      // Things may go wrong when adding url to session history,
+      // but don't let that interfere with the loading of the url.
+      Cu.reportError(ex);
+    }
+
+    if (where != "current" ||
+        lastLocationChange == gBrowser.selectedBrowser.lastLocationChange) {
+      openUILink(data.url, event,
+                 { ignoreButton: true,
+                   disallowInheritPrincipal: !data.mayInheritPrincipal });
+    }
+  });
 
   event.stopPropagation();
 }
 
 function handleDroppedLink(event, url, name)
 {
-  let postData = { };
-  let uri = getShortcutOrURI(url, postData);
-  if (uri)
-    loadURI(uri, null, postData.value, false);
+  let lastLocationChange = gBrowser.selectedBrowser.lastLocationChange;
+
+  Task.spawn(function() {
+    let data = yield getShortcutOrURIAndPostData(url);
+    if (data.url &&
+        lastLocationChange == gBrowser.selectedBrowser.lastLocationChange)
+      loadURI(data.url, null, data.postData, false);
+  });
 
   // Keep the event from being handled by the dragDrop listeners
   // built-in to gecko if they happen to be above us.
   event.preventDefault();
 };
 
 function MultiplexHandler(event)
 { try {
@@ -5386,17 +5408,16 @@ function charsetLoadListener() {
     if (!gCharsetMenu)
       gCharsetMenu = Cc['@mozilla.org/rdf/datasource;1?name=charset-menu'].getService(Ci.nsICurrentCharsetListener);
     gCharsetMenu.SetCurrentCharset(charset);
     gPrevCharset = gLastBrowserCharset;
     gLastBrowserCharset = charset;
   }
 }
 
-
 var gPageStyleMenu = {
 
   _getAllStyleSheets: function (frameset) {
     var styleSheetsArray = Array.slice(frameset.document.styleSheets);
     for (let i = 0; i < frameset.frames.length; i++) {
       let frameSheets = this._getAllStyleSheets(frameset.frames[i]);
       styleSheetsArray = styleSheetsArray.concat(frameSheets);
     }
--- a/browser/base/content/openLocation.js
+++ b/browser/base/content/openLocation.js
@@ -11,16 +11,17 @@ let openLocationModule = {};
 try {
   pref = Components.classes["@mozilla.org/preferences-service;1"]
                    .getService(Components.interfaces.nsIPrefBranch);
 } catch (ex) {
   // not critical, remain silent
 }
 
 Components.utils.import("resource:///modules/openLocationLastURL.jsm", openLocationModule);
+Components.utils.import("resource://gre/modules/Task.jsm");
 let gOpenLocationLastURL = new openLocationModule.OpenLocationLastURL(window.opener);
 
 function onLoad()
 {
   dialog.input         = document.getElementById("dialog.input");
   dialog.open          = document.documentElement.getButton("accept");
   dialog.openWhereList = document.getElementById("openWhereList");
   dialog.openTopWindow = document.getElementById("currentWindow");
@@ -56,54 +57,62 @@ function onLoad()
 
 function doEnabling()
 {
     dialog.open.disabled = !dialog.input.value;
 }
 
 function open()
 {
-  var url;
-  var postData = {};
-  var mayInheritPrincipal = {value: false};
-  if (browser)
-    url = browser.getShortcutOrURI(dialog.input.value, postData, mayInheritPrincipal);
-  else
-    url = dialog.input.value;
+  Task.spawn(function() {
+    let url;
+    let postData = null;
+    let mayInheritPrincipal = false;
+
+    if (browser) {
+      let data = yield browser.getShortcutOrURIAndPostData(dialog.input.value);
+      url = data.url;
+      postData = data.postData;
+      mayInheritPrincipal = data.mayInheritPrincipal;
+    } else {
+      url = dialog.input.value;
+    }
 
-  try {
-    // Whichever target we use for the load, we allow third-party services to
-    // fixup the URI
-    switch (dialog.openWhereList.value) {
-      case "0":
-        var webNav = Components.interfaces.nsIWebNavigation;
-        var flags = webNav.LOAD_FLAGS_ALLOW_THIRD_PARTY_FIXUP;
-        if (!mayInheritPrincipal.value)
-          flags |= webNav.LOAD_FLAGS_DISALLOW_INHERIT_OWNER;
-        browser.gBrowser.loadURIWithFlags(url, flags, null, null, postData.value);
-        break;
-      case "1":
-        window.opener.delayedOpenWindow(getBrowserURL(), "all,dialog=no",
-                                        url, postData.value, null, null, true);
-        break;
-      case "3":
-        browser.delayedOpenTab(url, null, null, postData.value, true);
-        break;
+    try {
+      // Whichever target we use for the load, we allow third-party services to
+      // fixup the URI
+      switch (dialog.openWhereList.value) {
+        case "0":
+          var webNav = Components.interfaces.nsIWebNavigation;
+          var flags = webNav.LOAD_FLAGS_ALLOW_THIRD_PARTY_FIXUP;
+          if (!mayInheritPrincipal)
+            flags |= webNav.LOAD_FLAGS_DISALLOW_INHERIT_OWNER;
+          browser.gBrowser.loadURIWithFlags(url, flags, null, null, postData);
+          break;
+        case "1":
+          window.opener.delayedOpenWindow(getBrowserURL(), "all,dialog=no",
+                                          url, postData, null, null, true);
+          break;
+        case "3":
+          browser.delayedOpenTab(url, null, null, postData, true);
+          break;
+      }
     }
-  }
-  catch(exception) {
-  }
+    catch(exception) {
+    }
 
-  if (pref) {
-    gOpenLocationLastURL.value = dialog.input.value;
-    pref.setIntPref("general.open_location.last_window_choice", dialog.openWhereList.value);
-  }
+    if (pref) {
+      gOpenLocationLastURL.value = dialog.input.value;
+      pref.setIntPref("general.open_location.last_window_choice", dialog.openWhereList.value);
+    }
 
-  // Delay closing slightly to avoid timing bug on Linux.
-  window.close();
+    // Delay closing slightly to avoid timing bug on Linux.
+    window.close();
+  });
+
   return false;
 }
 
 function createInstance(contractid, iidName)
 {
   var iid = Components.interfaces[iidName];
   return Components.classes[contractid].createInstance(iid);
 }
--- a/browser/base/content/tabbrowser.xml
+++ b/browser/base/content/tabbrowser.xml
@@ -674,18 +674,20 @@
               }
 
               if (!this.mBlank) {
                 this._callProgressListeners("onLocationChange",
                                             [aWebProgress, aRequest, aLocation,
                                              aFlags]);
               }
 
-              if (topLevel)
+              if (topLevel) {
                 this.mBrowser.lastURI = aLocation;
+                this.mBrowser.lastLocationChange = Date.now();
+              }
             },
 
             onStatusChange: function (aWebProgress, aRequest, aStatus, aMessage) {
               if (this.mBlank)
                 return;
 
               this._callProgressListeners("onStatusChange",
                                           [aWebProgress, aRequest, aStatus, aMessage]);
--- a/browser/base/content/test/browser_contentAreaClick.js
+++ b/browser/base/content/test/browser_contentAreaClick.js
@@ -162,17 +162,17 @@ let gTests = [
 // Array of method names that will be replaced in the new window.
 let gReplacedMethods = [
   "middleMousePaste",
   "urlSecurityCheck",
   "loadURI",
   "gatherTextUnder",
   "saveURL",
   "openLinkIn",
-  "getShortcutOrURI",
+  "getShortcutOrURIAndPostData",
 ];
 
 // Reference to the new window.
 let gTestWin = null;
 
 // List of methods invoked by a specific call to contentAreaClick.
 let gInvokedMethods = [];
 
@@ -227,18 +227,18 @@ let gClickHandler = {
     executeSoon(runNextTest);
   }
 }
 
 // Wraps around the methods' replacement mock function.
 function wrapperMethod(aInvokedMethods, aMethodName) {
   return function () {
     aInvokedMethods.push(aMethodName);
-    // At least getShortcutOrURI requires to return url that is the first param.
-    return arguments[0];
+    // At least getShortcutOrURIAndPostData requires to return url
+    return (aMethodName == "getShortcutOrURIAndPostData") ? arguments.url : arguments[0];
   }
 }
 
 function setupTestBrowserWindow() {
   // Steal click events and don't propagate them.
   gTestWin.addEventListener("click", gClickHandler, true);
 
   // Replace methods.
--- a/browser/base/content/test/browser_getshortcutoruri.js
+++ b/browser/base/content/test/browser_getshortcutoruri.js
@@ -80,43 +80,43 @@ var testData = [
   // UTF-8 default
   [new bmKeywordData("bmget-escaping", "http://bmget/?esc=%s&raw=%S", null, "+/@"),
    new keywordResult("http://bmget/?esc=%2B%2F%40&raw=+/@", null)],
   // Explicitly-defined ISO-8859-1
   [new bmKeywordData("bmget-escaping2", "http://bmget/?esc=%s&raw=%S&mozcharset=ISO-8859-1", null, "+/@"),
    new keywordResult("http://bmget/?esc=%2B%2F%40&raw=+/@", null)],
 
   // Test using a non-bmKeywordData object, to test the behavior of
-  // getShortcutOrURI for non-keywords (setupKeywords only adds keywords for
+  // getShortcutOrURIAndPostData for non-keywords (setupKeywords only adds keywords for
   // bmKeywordData objects)
   [{keyword: "http://gavinsharp.com"},
    new keywordResult(null, null, true)]
 ];
 
 function test() {
+  waitForExplicitFinish();
+
   setupKeywords();
 
-  for each (var item in testData) {
-    var [data, result] = item;
+  Task.spawn(function() {
+    for each (var item in testData) {
+      let [data, result] = item;
 
-    var postData = {};
-    var query = data.keyword;
-    if (data.searchWord)
-      query += " " + data.searchWord;
-    var mayInheritPrincipal = {};
-    var url = getShortcutOrURI(query, postData, mayInheritPrincipal);
-
-    // null result.url means we should expect the same query we sent in
-    var expected = result.url || query;
-    is(url, expected, "got correct URL for " + data.keyword);
-    is(getPostDataString(postData.value), result.postData, "got correct postData for " + data.keyword);
-    is(mayInheritPrincipal.value, !result.isUnsafe, "got correct mayInheritPrincipal for " + data.keyword);
-  }
-
-  cleanupKeywords();
+      let query = data.keyword;
+      if (data.searchWord)
+        query += " " + data.searchWord;
+      let returnedData = yield getShortcutOrURIAndPostData(query);
+      // null result.url means we should expect the same query we sent in
+      let expected = result.url || query;
+      is(returnedData.url, expected, "got correct URL for " + data.keyword);
+      is(getPostDataString(returnedData.postData), result.postData, "got correct postData for " + data.keyword);
+      is(returnedData.mayInheritPrincipal, !result.isUnsafe, "got correct mayInheritPrincipal for " + data.keyword);
+    }
+    cleanupKeywords();
+  }).then(finish);
 }
 
 var gBMFolder = null;
 var gAddedEngines = [];
 function setupKeywords() {
   gBMFolder = Application.bookmarks.menu.addFolder("keyword-test");
   for each (var item in testData) {
     var data = item[0];
--- a/browser/base/content/urlbarBindings.xml
+++ b/browser/base/content/urlbarBindings.xml
@@ -253,165 +253,175 @@
           if (aTriggeringEvent instanceof MouseEvent && aTriggeringEvent.button == 2)
             return; // Do nothing for right clicks
 
           var url = this.value;
           var mayInheritPrincipal = false;
           var postData = null;
 
           var action = this._parseActionUrl(url);
-          if (action) {
-            url = action.param;
-            if (this.hasAttribute("actiontype")) {
-              if (action.type == "switchtab") {
-                this.handleRevert();
-                let prevTab = gBrowser.selectedTab;
-                if (switchToTabHavingURI(url) &&
-                    isTabEmpty(prevTab))
-                  gBrowser.removeTab(prevTab);
+          let lastLocationChange = gBrowser.selectedBrowser.lastLocationChange;
+          Task.spawn(function() {
+            let matchLastLocationChange = true;
+            if (action) {
+              url = action.param;
+              if (this.hasAttribute("actiontype")) {
+                if (action.type == "switchtab") {
+                  this.handleRevert();
+                  let prevTab = gBrowser.selectedTab;
+                  if (switchToTabHavingURI(url) &&
+                      isTabEmpty(prevTab))
+                    gBrowser.removeTab(prevTab);
+                }
+                return;
               }
-              return;
             }
-          }
-          else {
-            [url, postData, mayInheritPrincipal] = this._canonizeURL(aTriggeringEvent);
-            if (!url)
-              return;
-          }
+            else {
+              [url, postData, mayInheritPrincipal] = yield this._canonizeURL(aTriggeringEvent);
+              matchLastLocationChange = (lastLocationChange ==
+                                         gBrowser.selectedBrowser.lastLocationChange);
+              if (!url)
+                return;
+            }
 
-          this.value = url;
-          gBrowser.userTypedValue = url;
-          try {
-            addToUrlbarHistory(url);
-          } catch (ex) {
-            // Things may go wrong when adding url to session history,
-            // but don't let that interfere with the loading of the url.
-            Cu.reportError(ex);
-          }
+            this.value = url;
+            gBrowser.userTypedValue = url;
+            try {
+              addToUrlbarHistory(url);
+            } catch (ex) {
+              // Things may go wrong when adding url to session history,
+              // but don't let that interfere with the loading of the url.
+              Cu.reportError(ex);
+            }
 
-          function loadCurrent() {
-            let flags = Ci.nsIWebNavigation.LOAD_FLAGS_ALLOW_THIRD_PARTY_FIXUP;
-            // Pass LOAD_FLAGS_DISALLOW_INHERIT_OWNER to prevent any loads from
-            // inheriting the currently loaded document's principal, unless this
-            // URL is marked as safe to inherit (e.g. came from a bookmark
-            // keyword).
-            if (!mayInheritPrincipal)
-              flags |= Ci.nsIWebNavigation.LOAD_FLAGS_DISALLOW_INHERIT_OWNER;
-            // If the value wasn't typed, we know that we decoded the value as
-            // UTF-8 (see losslessDecodeURI)
-            if (!this.valueIsTyped)
-              flags |= Ci.nsIWebNavigation.LOAD_FLAGS_URI_IS_UTF8;
-            gBrowser.loadURIWithFlags(url, flags, null, null, postData);
-          }
+            function loadCurrent() {
+              let flags = Ci.nsIWebNavigation.LOAD_FLAGS_ALLOW_THIRD_PARTY_FIXUP;
+              // Pass LOAD_FLAGS_DISALLOW_INHERIT_OWNER to prevent any loads from
+              // inheriting the currently loaded document's principal, unless this
+              // URL is marked as safe to inherit (e.g. came from a bookmark
+              // keyword).
+              if (!mayInheritPrincipal)
+                flags |= Ci.nsIWebNavigation.LOAD_FLAGS_DISALLOW_INHERIT_OWNER;
+              // If the value wasn't typed, we know that we decoded the value as
+              // UTF-8 (see losslessDecodeURI)
+              if (!this.valueIsTyped)
+                flags |= Ci.nsIWebNavigation.LOAD_FLAGS_URI_IS_UTF8;
+              gBrowser.loadURIWithFlags(url, flags, null, null, postData);
+            }
 
-          // Focus the content area before triggering loads, since if the load
-          // occurs in a new tab, we want focus to be restored to the content
-          // area when the current tab is re-selected.
-          gBrowser.selectedBrowser.focus();
+            // Focus the content area before triggering loads, since if the load
+            // occurs in a new tab, we want focus to be restored to the content
+            // area when the current tab is re-selected.
+            gBrowser.selectedBrowser.focus();
 
-          let isMouseEvent = aTriggeringEvent instanceof MouseEvent;
-          let altEnter = !isMouseEvent && aTriggeringEvent && aTriggeringEvent.altKey;
+            let isMouseEvent = aTriggeringEvent instanceof MouseEvent;
+            let altEnter = !isMouseEvent && aTriggeringEvent && aTriggeringEvent.altKey;
 
-          if (altEnter) {
-            // XXX This was added a long time ago, and I'm not sure why it is
-            // necessary. Alt+Enter's default action might cause a system beep,
-            // or something like that?
-            aTriggeringEvent.preventDefault();
-            aTriggeringEvent.stopPropagation();
-          }
+            if (altEnter) {
+              // XXX This was added a long time ago, and I'm not sure why it is
+              // necessary. Alt+Enter's default action might cause a system beep,
+              // or something like that?
+              aTriggeringEvent.preventDefault();
+              aTriggeringEvent.stopPropagation();
+            }
 
-          // If the current tab is empty, ignore Alt+Enter (just reuse this tab)
-          altEnter = altEnter && !isTabEmpty(gBrowser.selectedTab);
+            // If the current tab is empty, ignore Alt+Enter (just reuse this tab)
+            altEnter = altEnter && !isTabEmpty(gBrowser.selectedTab);
+
+            if (isMouseEvent || altEnter) {
+              // Use the standard UI link behaviors for clicks or Alt+Enter
+              let where = "tab";
+              if (isMouseEvent)
+                where = whereToOpenLink(aTriggeringEvent, false, false);
 
-          if (isMouseEvent || altEnter) {
-            // Use the standard UI link behaviors for clicks or Alt+Enter
-            let where = "tab";
-            if (isMouseEvent)
-              where = whereToOpenLink(aTriggeringEvent, false, false);
-
-            if (where == "current") {
-              loadCurrent();
+              if (where == "current") {
+                if (matchLastLocationChange) {
+                  loadCurrent();
+                }
+              } else {
+                this.handleRevert();
+                let params = { allowThirdPartyFixup: true,
+                               postData: postData,
+                               initiatingDoc: document };
+                if (!this.valueIsTyped)
+                  params.isUTF8 = true;
+                openUILinkIn(url, where, params);
+              }
             } else {
-              this.handleRevert();
-              let params = { allowThirdPartyFixup: true,
-                             postData: postData,
-                             initiatingDoc: document };
-              if (!this.valueIsTyped)
-                params.isUTF8 = true;
-              openUILinkIn(url, where, params);
+              if (matchLastLocationChange) {
+                loadCurrent();
+              }
             }
-          } else {
-            loadCurrent();
-          }
+          }.bind(this));
         ]]></body>
       </method>
 
       <method name="_canonizeURL">
         <parameter name="aTriggeringEvent"/>
         <body><![CDATA[
-          var url = this.value;
-          if (!url)
-            return ["", null, false];
+          return Task.spawn(function() {
+            var url = this.value;
+            if (!url)
+              throw new Task.Result(["", null, false]);
 
-          // Only add the suffix when the URL bar value isn't already "URL-like",
-          // and only if we get a keyboard event, to match user expectations.
-          if (/^\s*[^.:\/\s]+(?:\/.*|\s*)$/i.test(url) &&
-              (aTriggeringEvent instanceof KeyEvent)) {
+            // Only add the suffix when the URL bar value isn't already "URL-like",
+            // and only if we get a keyboard event, to match user expectations.
+            if (/^\s*[^.:\/\s]+(?:\/.*|\s*)$/i.test(url) &&
+                (aTriggeringEvent instanceof KeyEvent)) {
 #ifdef XP_MACOSX
-            let accel = aTriggeringEvent.metaKey;
+              let accel = aTriggeringEvent.metaKey;
 #else
-            let accel = aTriggeringEvent.ctrlKey;
+              let accel = aTriggeringEvent.ctrlKey;
 #endif
-            let shift = aTriggeringEvent.shiftKey;
+              let shift = aTriggeringEvent.shiftKey;
 
-            let suffix = "";
+              let suffix = "";
 
-            switch (true) {
-              case (accel && shift):
-                suffix = ".org/";
-                break;
-              case (shift):
-                suffix = ".net/";
-                break;
-              case (accel):
-                try {
-                  suffix = gPrefService.getCharPref("browser.fixup.alternate.suffix");
-                  if (suffix.charAt(suffix.length - 1) != "/")
-                    suffix += "/";
-                } catch(e) {
-                  suffix = ".com/";
+              switch (true) {
+                case (accel && shift):
+                  suffix = ".org/";
+                  break;
+                case (shift):
+                  suffix = ".net/";
+                  break;
+                case (accel):
+                  try {
+                    suffix = gPrefService.getCharPref("browser.fixup.alternate.suffix");
+                    if (suffix.charAt(suffix.length - 1) != "/")
+                      suffix += "/";
+                  } catch(e) {
+                    suffix = ".com/";
+                  }
+                  break;
+              }
+
+              if (suffix) {
+                // trim leading/trailing spaces (bug 233205)
+                url = url.trim();
+
+                // Tack www. and suffix on.  If user has appended directories, insert
+                // suffix before them (bug 279035).  Be careful not to get two slashes.
+
+                let firstSlash = url.indexOf("/");
+
+                if (firstSlash >= 0) {
+                  url = url.substring(0, firstSlash) + suffix +
+                        url.substring(firstSlash + 1);
+                } else {
+                  url = url + suffix;
                 }
-                break;
+
+                url = "http://www." + url;
+              }
             }
 
-            if (suffix) {
-              // trim leading/trailing spaces (bug 233205)
-              url = url.trim();
-
-              // Tack www. and suffix on.  If user has appended directories, insert
-              // suffix before them (bug 279035).  Be careful not to get two slashes.
-
-              let firstSlash = url.indexOf("/");
+            let data = yield getShortcutOrURIAndPostData(url);
 
-              if (firstSlash >= 0) {
-                url = url.substring(0, firstSlash) + suffix +
-                      url.substring(firstSlash + 1);
-              } else {
-                url = url + suffix;
-              }
-
-              url = "http://www." + url;
-            }
-          }
-
-          var postData = {};
-          var mayInheritPrincipal = { value: false };
-          url = getShortcutOrURI(url, postData, mayInheritPrincipal);
-
-          return [url, postData.value, mayInheritPrincipal.value];
+            throw new Task.Result([data.url, data.postData, data.mayInheritPrincipal]);
+          }.bind(this));
         ]]></body>
       </method>
 
       <field name="_contentIsCropped">false</field>
 
       <method name="_initURLTooltip">
         <body><![CDATA[
           if (this.focused || !this._contentIsCropped)