bug 610736 - Port a number of recent places changes, r=Neil
authorRobert Kaiser <kairo@kairo.at>
Thu, 18 Nov 2010 15:45:18 +0100
changeset 6715 65159580f815ef4aaf620777dd4a8849440c1f69
parent 6714 fdff425a73eeddd20fed47b2cf1508859d005421
child 6716 4c4f1bcda38299e183d7594634733dfcf222d2f6
push idunknown
push userunknown
push dateunknown
reviewersNeil
bugs610736
bug 610736 - Port a number of recent places changes, r=Neil
suite/browser/nsBrowserStatusHandler.js
suite/common/bookmarks/bm-panel.xul
suite/common/bookmarks/bookmarksManager.xul
suite/common/bookmarks/editBookmarkOverlay.js
suite/common/contentAreaClick.js
suite/common/history/history-panel.xul
suite/common/history/sidebarUtils.js
suite/common/jar.mn
suite/common/places/sidebarUtils.js
suite/common/src/PlacesUIUtils.jsm
suite/common/tests/browser/Makefile.in
suite/common/tests/browser/browser_markPageAsFollowedLink.js
suite/common/tests/browser/frameLeft.html
suite/common/tests/browser/frameRight.html
suite/common/tests/browser/framedPage.html
suite/locales/en-US/chrome/common/bookmarks/places.dtd
--- a/suite/browser/nsBrowserStatusHandler.js
+++ b/suite/browser/nsBrowserStatusHandler.js
@@ -97,47 +97,56 @@ nsBrowserStatusHandler.prototype =
     this.statusTextField = null;
     this.isImage         = null;
     this.securityButton  = null;
     this.evButton        = null;
     this.feedsButton     = null;
     this.feedsMenu       = null;
   },
 
+  // nsIXULBrowserWindow
   setJSStatus : function(status)
   {
     this.jsStatus = status;
     this.updateStatusField();
   },
 
+  // nsIXULBrowserWindow
   setJSDefaultStatus : function(status)
   {
     this.jsDefaultStatus = status;
     this.updateStatusField();
   },
 
   setDefaultStatus : function(status)
   {
     this.defaultStatus = status;
     this.updateStatusField();
   },
 
+  // nsIXULBrowserWindow
   setOverLink : function(link, context)
   {
     this.overLink = link;
     // clear out 'Done' (or other message) on first hover
     if (this.defaultStatus)
       this.defaultStatus = "";
     this.updateStatusField();
     if (link)
       this.statusTextField.setAttribute('crop', 'center');
     else
       this.statusTextField.setAttribute('crop', 'end');
   },
 
+  // nsIXULBrowserWindow
+  // Called before links are navigated to to allow us to retarget them if needed.
+  onBeforeLinkTraversal: function(originalTarget, linkURI, linkNode, isAppTab) {
+    return originalTarget;
+  },
+
   updateStatusField : function()
   {
     var text = this.overLink || this.status || this.jsStatus || this.jsDefaultStatus || this.defaultStatus;
 
     // check the current value so we don't trigger an attribute change
     // and cause needless (slow!) UI updates
     if (this.statusTextField.label != text)
       this.statusTextField.label = text;
--- a/suite/common/bookmarks/bm-panel.xul
+++ b/suite/common/bookmarks/bm-panel.xul
@@ -49,17 +49,17 @@
 <?xul-overlay href="chrome://communicator/content/utilityOverlay.xul"?>
 
 <!DOCTYPE page SYSTEM "chrome://communicator/locale/bookmarks/places.dtd">
 
 <page id="bookmarksPanel"
       xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
       xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
       onload="init();"
-      onunload="SidebarUtils.clearURLFromStatusBar();"
+      onunload="SidebarUtils.setMouseoverURL('');"
       elementtofocus="search-box">
 
   <script type="application/javascript"
           src="chrome://communicator/content/places/sidebarUtils.js"/>
   <script type="application/javascript"
           src="chrome://communicator/content/bookmarks/bm-panel.js"/>
 
   <commandset id="placesCommands"/>
@@ -78,16 +78,16 @@
 
   <tree id="bookmarks-view" class="sidebar-placesTree" type="places"
         flex="1"
         hidecolumnpicker="true"
         context="placesContext"
         onkeypress="SidebarUtils.handleTreeKeyPress(event);"
         onclick="SidebarUtils.handleTreeClick(this, event, true);"
         onmousemove="SidebarUtils.handleTreeMouseMove(event);"
-        onmouseout="SidebarUtils.clearURLFromStatusBar();">
+        onmouseout="SidebarUtils.setMouseoverURL('');">
     <treecols>
       <treecol id="title" flex="1" primary="true" hideheader="true"/>
     </treecols>
     <treechildren id="bookmarks-view-children" view="bookmarks-view"
                   class="sidebar-placesTreechildren" flex="1" tooltip="bhTooltip"/>
   </tree>
 </page>
--- a/suite/common/bookmarks/bookmarksManager.xul
+++ b/suite/common/bookmarks/bookmarksManager.xul
@@ -349,17 +349,17 @@
               collapse="before"
               persist="state collapsed">
       <grippy/>
     </splitter>
     <vbox id="contentView" flex="1">
       <toolbox id="searchModifiers" hidden="true">
         <toolbar id="organizerScopeBar" xpfe="false"
                  class="chromeclass-toolbar" align="center">
-          <label id="scopeBarTitle" value="&search.label;"/>
+          <label id="scopeBarTitle" value="&search.in.label;"/>
           <button id="scopeBarAll" class="small-margin"
                   type="radio" group="scopeBar"
                   oncommand="PlacesQueryBuilder.onScopeSelected(this);"
                   label="&search.scopeBookmarks.label;"
                   accesskey="&search.scopeBookmarks.accesskey;"/>
           <button id="scopeBarFolder" class="small-margin"
                   type="radio" group="scopeBar"
                   oncommand="PlacesQueryBuilder.onScopeSelected(this);"
--- a/suite/common/bookmarks/editBookmarkOverlay.js
+++ b/suite/common/bookmarks/editBookmarkOverlay.js
@@ -686,17 +686,22 @@ var gEditItemOverlay = {
       return;
 
     var namePicker = this._element("namePicker")
     var txns = [];
     const ptm = PlacesUIUtils.ptm;
 
     // Here we update either the item title or its cached static title
     var newTitle = this._element("userEnteredName").label;
-    if (this._getItemStaticTitle() != newTitle) {
+    if (!newTitle &&
+        PlacesUtils.bookmarks.getFolderIdForItem(this._itemId) == PlacesUtils.tagsFolderId) {
+      // We don't allow setting an empty title for a tag, restore the old one.
+      this._initNamePicker();
+    }
+    else if (this._getItemStaticTitle() != newTitle) {
       this._mayUpdateFirstEditField("namePicker");
       if (PlacesUtils.microsummaries.hasMicrosummary(this._itemId)) {
         // Note: this implicitly also takes care of the microsummary->static
         // title case, the removeMicorosummary method in the service will set
         // the item-title to the value of this annotation.
         //
         // XXXmano: use a transaction
         PlacesUtils.setAnnotationsForItem(this._itemId,
@@ -996,30 +1001,26 @@ var gEditItemOverlay = {
     else {
       expander.className = "expander-down";
       expander.setAttribute("tooltiptext",
                             expander.getAttribute("tooltiptextdown"));
       tagsSelectorRow.collapsed = true;
     }
   },
 
+  /**
+   * Splits "tagsField" element value, returning an array of valid tag strings.
+   *
+   * @return Array of tag strings found in the field value.
+   */
   _getTagsArrayFromTagField: function EIO__getTagsArrayFromTagField() {
-    // we don't require the leading space (after each comma)
-    var tags = this._element("tagsField").value.split(",");
-    for (var i=0; i < tags.length; i++) {
-      // remove trailing and leading spaces
-      tags[i] = tags[i].replace(/^\s+/, "").replace(/\s+$/, "");
-
-      // remove empty entries from the array.
-      if (tags[i] == "") {
-        tags.splice(i, 1);
-        i--;
-      }
-    }
-    return tags;
+    let tags = this._element("tagsField").value;
+    return tags.trim()
+               .split(/\s*,\s*/) // Split on commas and remove spaces.
+               .filter(function (tag) tag.length > 0); // Kill empty tags.
   },
 
   newFolder: function EIO_newFolder() {
     var ip = this._folderTree.insertionPoint;
 
     // default to the bookmarks menu folder
     if (!ip || ip.itemId == PlacesUIUtils.allBookmarksFolderId) {
         ip = new InsertionPoint(PlacesUtils.bookmarksMenuFolderId,
--- a/suite/common/contentAreaClick.js
+++ b/suite/common/contentAreaClick.js
@@ -1,9 +1,9 @@
-/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+// /* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* ***** BEGIN LICENSE BLOCK *****
  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
  *
  * The contents of this file are subject to the Mozilla Public License Version
  * 1.1 (the "License"); you may not use this file except in compliance with
  * the License. You may obtain a copy of the License at
  * http://www.mozilla.org/MPL/
  *
@@ -111,25 +111,34 @@
       }
       else {
         // if in mailnews block the link left click if we determine
         // that this URL is phishy (i.e. a potential email scam) 
         if ("gMessengerBundle" in this && event.button < 2 &&
             isPhishingURL(ceParams.linkNode, false, href))
           return false;
         handleLinkClick(event, href, ceParams.linkNode);
+
+        // Mark the page as a user followed link.  This is done so that history can
+        // distinguish automatic embed visits from user activated ones.  For example
+        // pages loaded in frames are embed visits and lost with the session, while
+        // visits across frames should be preserved.
+        try {
+          PlacesUIUtils.markPageAsFollowedLink(href);
+        } catch (ex) { /* Skip invalid URIs. */ }
       }
       return true;
     }
 
     if (pref && !isKeyCommand && event.button == 1 &&
         pref.getBoolPref("middlemouse.contentLoadURL") &&
         !pref.getBoolPref("general.autoScroll")) {
       middleMousePaste(event);
     }
+
     return true;
   }
 
   function openNewTabOrWindow(event, href, doc)
   {
     // should we open it in a new tab?
     if (pref && pref.getBoolPref("browser.tabs.opentabfor.middleclick")) {
       openNewTabWith(href, doc, event.shiftKey);
--- a/suite/common/history/history-panel.xul
+++ b/suite/common/history/history-panel.xul
@@ -48,21 +48,21 @@
 <!DOCTYPE page SYSTEM "chrome://communicator/locale/history/history.dtd">
 
 <!-- we need to keep id="history-panel" for upgrade and switching
      between versions of the browser -->
 
 <page id="history-panel" orient="vertical"
       xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
       onload="HistoryCommonInit();"
-      onunload="SidebarUtils.clearURLFromStatusBar();"
+      onunload="SidebarUtils.setMouseoverURL('');"
       elementtofocus="search-box">
 
   <script type="application/javascript"
-          src="chrome://communicator/content/history/sidebarUtils.js"/>
+          src="chrome://communicator/content/places/sidebarUtils.js"/>
 
   <commandset id="editMenuCommands"/>
   <commandset id="placesCommands"/>
 
   <keyset id="editMenuKeys">
     <key id="key_delete2"/>
   </keyset>
 
@@ -82,17 +82,17 @@
         class="sidebar-placesTree plain"
         flex="1"
         type="places"
         context="placesContext"
         onselect="historyOnSelect();"
         onkeypress="SidebarUtils.handleTreeKeyPress(event);"
         onclick="SidebarUtils.handleTreeClick(this, event, true);"
         onmousemove="SidebarUtils.handleTreeMouseMove(event);"
-        onmouseout="SidebarUtils.clearURLFromStatusBar();">
+        onmouseout="SidebarUtils.setMouseoverURL('');">
     <treecols context="">
       <treecol label="&col.title.label;" id="Name" flex="4"
                persist="width hidden ordinal sortActive sortDirection"/>
       <splitter class="tree-splitter"/>
       <treecol label="&col.url.label;" id="URL" flex="4" hidden="true"
                persist="width hidden ordinal sortActive sortDirection"/>
       <splitter class="tree-splitter"/>
       <treecol label="&col.lastvisit.label;" id="Date" flex="1" hidden="true"
deleted file mode 100644
--- a/suite/common/history/sidebarUtils.js
+++ /dev/null
@@ -1,124 +0,0 @@
-/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
-/* ***** BEGIN LICENSE BLOCK *****
- * Version: MPL 1.1/GPL 2.0/LGPL 2.1
- *
- * The contents of this file are subject to the Mozilla Public License Version
- * 1.1 (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- * http://www.mozilla.org/MPL/
- *
- * Software distributed under the License is distributed on an "AS IS" basis,
- * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
- * for the specific language governing rights and limitations under the
- * License.
- *
- * The Original Code is Mozilla Places Organizer.
- *
- * The Initial Developer of the Original Code is Google Inc.
- * Portions created by the Initial Developer are Copyright (C) 2005-2006
- * the Initial Developer. All Rights Reserved.
- *
- * Contributor(s):
- *   Dan Mills <thunder@mozilla.com> (Ported from history-panel.js)
- *   Marco Bonardo <mak77@supereva.it>
- *   Robert Kaiser <kairo@kairo.at>
- *
- * Alternatively, the contents of this file may be used under the terms of
- * either the GNU General Public License Version 2 or later (the "GPL"), or
- * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
- * in which case the provisions of the GPL or the LGPL are applicable instead
- * of those above. If you wish to allow use of your version of this file only
- * under the terms of either the GPL or the LGPL, and not to allow others to
- * use your version of this file under the terms of the MPL, indicate your
- * decision by deleting the provisions above and replace them with the notice
- * and other provisions required by the GPL or the LGPL. If you do not delete
- * the provisions above, a recipient may use your version of this file under
- * the terms of any one of the MPL, the GPL or the LGPL.
- *
- * ***** END LICENSE BLOCK ***** */
-
-var SidebarUtils = {
-  handleTreeClick: function SU_handleTreeClick(aTree, aEvent, aGutterSelect) {
-    // right-clicks are not handled here
-    if (aEvent.button == 2)
-      return;
-
-    var tbo = aTree.treeBoxObject;
-    var row = { }, col = { }, obj = { };
-    tbo.getCellAt(aEvent.clientX, aEvent.clientY, row, col, obj);
-
-    if (row.value == -1 || obj.value == "twisty")
-      return;
-
-    var mouseInGutter = false;
-    if (aGutterSelect) {
-      var x = { }, y = { }, w = { }, h = { };
-      tbo.getCoordsForCellItem(row.value, col.value, "image",
-                               x, y, w, h);
-      mouseInGutter = aEvent.clientX < x.value;
-    }
-
-    var openWhere = whereToOpenLink(aEvent, false, true);
-
-    var isContainer = tbo.view.isContainer(row.value);
-    var openInTabs = isContainer &&
-                     (openWhere == "tab" || openWhere == "tabshifted") &&
-                     PlacesUtils.hasChildURIs(tbo.view.nodeForTreeIndex(row.value));
-
-    if (aEvent.button == 0 && isContainer && !openInTabs) {
-      tbo.view.toggleOpenState(row.value);
-      return;
-    }
-    else if (!mouseInGutter && openInTabs &&
-            aEvent.originalTarget.localName == "treechildren") {
-      tbo.view.selection.select(row.value);
-      PlacesUIUtils.openContainerNodeInTabs(aTree.selectedNode, aEvent);
-    }
-    else if (!mouseInGutter && !isContainer &&
-             aEvent.originalTarget.localName == "treechildren") {
-      // Clear all other selection since we're loading a link now. We must
-      // do this *before* attempting to load the link since openURL uses
-      // selection as an indication of which link to load.
-      tbo.view.selection.select(row.value);
-      PlacesUIUtils.openNodeWithEvent(aTree.selectedNode, aEvent);
-    }
-  },
-
-  handleTreeKeyPress: function SU_handleTreeKeyPress(aEvent) {
-    if (aEvent.keyCode == KeyEvent.DOM_VK_RETURN)
-      PlacesUIUtils.openNodeWithEvent(aEvent.target.selectedNode, aEvent);
-  },
-
-  /**
-   * The following function displays the URL of a node that is being
-   * hovered over.
-   */
-  handleTreeMouseMove: function SU_handleTreeMouseMove(aEvent) {
-    if (aEvent.target.localName != "treechildren")
-      return;
-
-    var tree = aEvent.target.parentNode;
-    var tbo = tree.treeBoxObject;
-    var row = { }, col = { }, obj = { };
-    tbo.getCellAt(aEvent.clientX, aEvent.clientY, row, col, obj);
-
-    // row.value is -1 when the mouse is hovering an empty area within the tree.
-    // To avoid showing a URL from a previously hovered node,
-    // for a currently hovered non-url node, we must clear the URL from the
-    // status bar in these cases.
-    if (row.value != -1) {
-      var cell = tree.view.nodeForTreeIndex(row.value);
-      if (PlacesUtils.nodeIsURI(cell))
-        window.top.XULBrowserWindow.setOverLink(cell.uri, null);
-      else
-        this.clearURLFromStatusBar();
-    }
-    else
-      this.clearURLFromStatusBar();
-  },
-
-  clearURLFromStatusBar: function SU_clearURLFromStatusBar() {
-    if (window.top.XULBrowserWindow)
-      window.top.XULBrowserWindow.setOverLink("", null);
-  }
-};
--- a/suite/common/jar.mn
+++ b/suite/common/jar.mn
@@ -148,17 +148,16 @@ comm.jar:
    content/communicator/feeds/subscribe.xhtml                       (feeds/subscribe.xhtml)
    content/communicator/feeds/subscribe.xml                         (feeds/subscribe.xml)
    content/communicator/history/controller.js                       (history/controller.js)
    content/communicator/history/history.js                          (history/history.js)
    content/communicator/history/history.xul                         (history/history.xul)
    content/communicator/history/history-panel.xul                   (history/history-panel.xul)
    content/communicator/history/places.css                          (history/places.css)
    content/communicator/history/placesOverlay.xul                   (history/placesOverlay.xul)
-   content/communicator/history/sidebarUtils.js                     (history/sidebarUtils.js)
    content/communicator/history/tree.xml                            (history/tree.xml)
    content/communicator/history/treeView.js                         (history/treeView.js)
 *  content/communicator/history/utils.js                            (history/utils.js)
    content/communicator/permissions/cookieViewer.js                 (permissions/cookieViewer.js)
    content/communicator/permissions/cookieViewer.xul                (permissions/cookieViewer.xul)
    content/communicator/permissions/imageContextOverlay.xul         (permissions/imageContextOverlay.xul)
    content/communicator/permissions/permissionsManager.js           (permissions/permissionsManager.js)
    content/communicator/permissions/permissionsManager.xul          (permissions/permissionsManager.xul)
--- a/suite/common/places/sidebarUtils.js
+++ b/suite/common/places/sidebarUtils.js
@@ -106,26 +106,25 @@ var SidebarUtils = {
       return;
 
     var tree = aEvent.target.parentNode;
     var tbo = tree.treeBoxObject;
     var row = { }, col = { }, obj = { };
     tbo.getCellAt(aEvent.clientX, aEvent.clientY, row, col, obj);
 
     // row.value is -1 when the mouse is hovering an empty area within the tree.
-    // To avoid showing a URL from a previously hovered node,
-    // for a currently hovered non-url node, we must clear the URL from the
-    // status bar in these cases.
+    // To avoid showing a URL from a previously hovered node for a currently
+    // hovered non-url node, we must clear the moused-over URL in these cases.
     if (row.value != -1) {
-      var cell = tree.view.nodeForTreeIndex(row.value);
-      if (PlacesUtils.nodeIsURI(cell))
-        window.top.XULBrowserWindow.setOverLink(cell.uri, null);
+      var node = tree.view.nodeForTreeIndex(row.value);
+      if (PlacesUtils.nodeIsURI(node))
+        this.setMouseoverURL(node.uri);
       else
-        this.clearURLFromStatusBar();
+        this.setMouseoverURL("");
     }
     else
-      this.clearURLFromStatusBar();
+      this.setMouseoverURL("");
   },
 
-  clearURLFromStatusBar: function SU_clearURLFromStatusBar() {
-    window.top.XULBrowserWindow.setOverLink("", null);
+  setMouseoverURL: function SU_setMouseoverURL(aURL) {
+    window.top.XULBrowserWindow.setOverLink(aURL, null);
   }
 };
--- a/suite/common/src/PlacesUIUtils.jsm
+++ b/suite/common/src/PlacesUIUtils.jsm
@@ -716,17 +716,17 @@ var PlacesUIUtils = {
   },
 
   /**
    * By calling this before visiting an URL, any visit in frames will be
    * associated to a TRANSITION_FRAMED_LINK transition.
    * This is actually used to distinguish user-initiated visits in frames
    * so automatic visits can be correctly ignored.
    */
-  markPageAsFollowedLink: function PUIU_markPageAsUserClicked(aURL) {
+  markPageAsFollowedLink: function PUIU_markPageAsFollowedLink(aURL) {
     PlacesUtils.history.QueryInterface(Components.interfaces.nsIBrowserHistory)
                .markPageAsFollowedLink(this.createFixedURI(aURL));
   },
 
   /**
    * Allows opening of javascript/data URI only if the given node is
    * bookmarked (see bug 224521).
    * @param aURINode
--- a/suite/common/tests/browser/Makefile.in
+++ b/suite/common/tests/browser/Makefile.in
@@ -86,12 +86,16 @@ include $(topsrcdir)/config/rules.mk
 	browser_bug510890.js \
 	browser_514751.js \
 	browser_bug515006.js \
 	browser_522545.js \
 	browser_524745.js \
 	browser_526613.js \
 	browser_528776.js \
 	browser_isempty.js \
+	browser_markPageAsFollowedLink.js \
+	framedPage.html \
+	frameLeft.html \
+	frameRight.html \
 	$(NULL)
 
 libs::	$(_BROWSER_FILES)
 	$(INSTALL) $(foreach f,$^,"$f") $(MOZDEPTH)/_tests/testing/mochitest/browser/$(relativesrcdir)
new file mode 100644
--- /dev/null
+++ b/suite/common/tests/browser/browser_markPageAsFollowedLink.js
@@ -0,0 +1,89 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+/**
+ * Tests that visits across frames are correctly represented in the database.
+ */
+
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+
+const BASE_URL = "http://mochi.test:8888/browser/suite/common/tests/browser";
+const PAGE_URL = BASE_URL + "/framedPage.html";
+const LEFT_URL = BASE_URL + "/frameLeft.html";
+const RIGHT_URL = BASE_URL + "/frameRight.html";
+
+Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+
+let gTabLoaded = false;
+let gLeftFrameVisited = false;
+
+let observer = {
+  observe: function(aSubject, aTopic, aData)
+  {
+    let url = aSubject.QueryInterface(Ci.nsIURI).spec;
+    if (url == LEFT_URL ) {
+      is(getTransitionForUrl(url), PlacesUtils.history.TRANSITION_EMBED,
+         "Frames should get a EMBED transition.");
+      gLeftFrameVisited = true;
+      maybeClickLink();
+    }
+    else if (url == RIGHT_URL ) {
+      is(getTransitionForUrl(url), PlacesUtils.history.TRANSITION_FRAMED_LINK,
+         "User activated visits should get a FRAMED_LINK transition.");
+      finish();
+    }
+  },
+  QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver])
+};
+Services.obs.addObserver(observer, "uri-visit-saved", false);
+
+function test()
+{
+  waitForExplicitFinish();
+  gBrowser.selectedTab = gBrowser.addTab(PAGE_URL);
+  let frameCount = 0;
+  gBrowser.selectedTab.linkedBrowser.addEventListener("DOMContentLoaded",
+    function (event)
+    {
+      // Wait for all the frames.
+      if (frameCount++ < 2)
+        return;
+      gBrowser.selectedTab.linkedBrowser.removeEventListener("DOMContentLoaded", arguments.callee, false)
+      gTabLoaded = true;
+      maybeClickLink();
+    }, false
+  );
+}
+
+function maybeClickLink() {
+  if (gTabLoaded && gLeftFrameVisited) {
+    // Click on the link in the left frame to cause a page load in the
+    // right frame.
+    EventUtils.sendMouseEvent({type: "click"}, "clickme", content.frames[0]);
+  }
+}
+
+function getTransitionForUrl(aUrl)
+{
+  let dbConn = PlacesUtils.history
+                          .QueryInterface(Ci.nsPIPlacesDatabase).DBConnection;
+  let stmt = dbConn.createStatement(
+    "SELECT visit_type FROM moz_historyvisits_view WHERE place_id = " +
+      "(SELECT id FROM moz_places_view WHERE url = :page_url)");
+  stmt.params.page_url = aUrl;
+  try {
+    ok(stmt.executeStep(), "Found the visit in the database");
+    return stmt.row.visit_type;
+  }
+  finally {
+    stmt.finalize();
+  }
+}
+
+registerCleanupFunction(function ()
+{
+  gBrowser.removeTab(gBrowser.selectedTab);
+  Services.obs.removeObserver(observer, "uri-visit-saved");
+})
new file mode 100644
--- /dev/null
+++ b/suite/common/tests/browser/frameLeft.html
@@ -0,0 +1,8 @@
+<html>
+  <head>
+    <title>Left frame</title>
+  </head>
+  <body>
+    <a id="clickme" href="frameRight.html" target="right">Open page in the right frame.</a>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/suite/common/tests/browser/frameRight.html
@@ -0,0 +1,8 @@
+<html>
+  <head>
+    <title>Right Frame</title>
+  </head>
+  <body>
+    This is the right frame.
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/suite/common/tests/browser/framedPage.html
@@ -0,0 +1,9 @@
+<html>
+  <head>
+    <title>Framed page</title>
+  </head>
+  <frameset cols="*,*">
+    <frame name="left" src="frameLeft.html">
+    <frame name="right" src="about:mozilla">
+  </frameset>
+</html>
--- a/suite/locales/en-US/chrome/common/bookmarks/places.dtd
+++ b/suite/locales/en-US/chrome/common/bookmarks/places.dtd
@@ -80,16 +80,18 @@
 <!ENTITY col.visitcount.label    "Visit Count">
 <!ENTITY col.keyword.label       "Keyword">
 <!ENTITY col.description.label   "Description">
 <!ENTITY col.dateadded.label     "Added">
 <!ENTITY col.lastmodified.label  "Last Modified">
 
 <!ENTITY search.label                              "Search:">
 <!ENTITY search.placeholder                        "Search Bookmarks">
+
+<!ENTITY search.in.label                           "Search in:">
 <!ENTITY search.scopeFolder.label                  "Selected Folder">
 <!ENTITY search.scopeFolder.accesskey              "r">
 <!ENTITY search.scopeBookmarks.label               "Bookmarks">
 <!ENTITY search.scopeBookmarks.accesskey           "k">
 <!ENTITY saveSearch.label                          "Save">
 <!ENTITY saveSearch.accesskey                      "S">
 
 <!ENTITY cmd.find.key  "f">