Merge fx-team to m-c a=merge
authorWes Kocher <wkocher@mozilla.com>
Wed, 08 Oct 2014 16:46:52 -0700
changeset 232626 a689386944da22b83a337c63052ef0f93fb43f30
parent 232608 7b16babf6a732741631ff2bb4c83eaf6e7b4fc66 (current diff)
parent 232625 d6dbd2f1b73412e0da32e6f44ca3d993e7ef82fa (diff)
child 232675 f0bb13ef0ee488d4ece70467e4e819cb7506f5e3
push id4187
push userbhearsum@mozilla.com
push dateFri, 28 Nov 2014 15:29:12 +0000
treeherdermozilla-beta@f23cc6a30c11 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone35.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
Merge fx-team to m-c a=merge
--- a/browser/base/content/browser.css
+++ b/browser/base/content/browser.css
@@ -393,18 +393,17 @@ panel[noactions] > richlistbox > richlis
 panel[noactions] > richlistbox > richlistitem[type~="action"] > .ac-url-box > .ac-action-icon {
   visibility: collapse;
 }
 
 panel[noactions] > richlistbox > richlistitem[type~="action"] > .ac-url-box > .ac-url > .ac-url-text {
   visibility: visible;
 }
 
-#urlbar:not([actiontype]) > #urlbar-display-box,
-#urlbar:not([actiontype="switchtab"]) > #urlbar-display-box > .urlbar-display-switchtab {
+#urlbar:not([actiontype="switchtab"]) > #urlbar-display-box {
   display: none;
 }
 
 #PopupAutoComplete {
   -moz-binding: url("chrome://browser/content/urlbarBindings.xml#browser-autocomplete-result-popup");
 }
 
 #PopupAutoCompleteRichResult {
--- a/browser/components/loop/GoogleImporter.jsm
+++ b/browser/components/loop/GoogleImporter.jsm
@@ -420,17 +420,17 @@ this.GoogleImporter.prototype = {
           ["locality", "city"],
           ["postalCode", "postcode"],
           ["region", "region"],
           ["streetAddress", "street"]
         ]), addressNode, kNS_GD);
         if (Object.keys(adr).length) {
           adr.pref = (addressNode.getAttribute("primary") == "true");
           adr.type = [getFieldType(addressNode)];
-          contacts.adr.push(adr);
+          contact.adr.push(adr);
         }
       }
     }
 
     // Process email addresses.
     let emailNodes = entry.getElementsByTagNameNS(kNS_GD, "email");
     if (emailNodes.length) {
       contact.email = [];
--- a/browser/components/loop/ui/ui-showcase.js
+++ b/browser/components/loop/ui/ui-showcase.js
@@ -53,16 +53,23 @@
   var stageFeedbackApiClient = new loop.FeedbackAPIClient(
     "https://input.allizom.org/api/v1/feedback", {
       product: "Loop"
     }
   );
 
   // Local mocks
 
+  var mockContact = {
+    name: ["Mr Smith"],
+    email: [{
+      value: "smith@invalid.com"
+    }]
+  };
+
   var mockClient = {
     requestCallUrl: noop,
     requestCallUrlInfo: noop
   };
 
   var mockSDK = {};
 
   var mockConversationModel = new loop.shared.models.ConversationModel({}, {
@@ -249,17 +256,18 @@
               )
             )
           ), 
 
           Section({name: "PendingConversationView (Desktop)"}, 
             Example({summary: "Connecting", dashed: "true", 
                      style: {width: "260px", height: "265px"}}, 
               React.DOM.div({className: "fx-embedded"}, 
-                DesktopPendingConversationView({callState: "gather", calleeId: "Mr Smith"})
+                DesktopPendingConversationView({callState: "gather", 
+                                                contact: mockContact})
               )
             )
           ), 
 
           Section({name: "CallFailedView"}, 
             Example({summary: "Call Failed", dashed: "true", 
                      style: {width: "260px", height: "265px"}}, 
               React.DOM.div({className: "fx-embedded"}, 
--- a/browser/components/loop/ui/ui-showcase.jsx
+++ b/browser/components/loop/ui/ui-showcase.jsx
@@ -53,16 +53,23 @@
   var stageFeedbackApiClient = new loop.FeedbackAPIClient(
     "https://input.allizom.org/api/v1/feedback", {
       product: "Loop"
     }
   );
 
   // Local mocks
 
+  var mockContact = {
+    name: ["Mr Smith"],
+    email: [{
+      value: "smith@invalid.com"
+    }]
+  };
+
   var mockClient = {
     requestCallUrl: noop,
     requestCallUrlInfo: noop
   };
 
   var mockSDK = {};
 
   var mockConversationModel = new loop.shared.models.ConversationModel({}, {
@@ -249,17 +256,18 @@
               </div>
             </Example>
           </Section>
 
           <Section name="PendingConversationView (Desktop)">
             <Example summary="Connecting" dashed="true"
                      style={{width: "260px", height: "265px"}}>
               <div className="fx-embedded">
-                <DesktopPendingConversationView callState={"gather"} calleeId="Mr Smith" />
+                <DesktopPendingConversationView callState={"gather"}
+                                                contact={mockContact} />
               </div>
             </Example>
           </Section>
 
           <Section name="CallFailedView">
             <Example summary="Call Failed" dashed="true"
                      style={{width: "260px", height: "265px"}}>
               <div className="fx-embedded">
--- a/mobile/android/base/BrowserApp.java
+++ b/mobile/android/base/BrowserApp.java
@@ -186,17 +186,17 @@ public class BrowserApp extends GeckoApp
         public boolean checkable;
         public boolean checked;
         public boolean enabled = true;
         public boolean visible = true;
         public int parent;
         public boolean added;   // So we can re-add after a locale change.
     }
 
-    // The types of guest mdoe dialogs we show
+    // The types of guest mode dialogs we show.
     public static enum GuestModeDialog {
         ENTERING,
         LEAVING
     }
 
     private Vector<MenuItemInfo> mAddonMenuItemsCache;
     private PropertyAnimator mMainLayoutAnimator;
 
@@ -515,21 +515,22 @@ public class BrowserApp extends GeckoApp
         }).execute();
     }
 
     @Override
     public void onCreate(Bundle savedInstanceState) {
         mAboutHomeStartupTimer = new Telemetry.UptimeTimer("FENNEC_STARTUP_TIME_ABOUTHOME");
 
         final Intent intent = getIntent();
-        final String args = intent.getStringExtra("args");
-
-        if (GuestSession.shouldUse(this, args)) {
-            mProfile = GeckoProfile.createGuestProfile(this);
-        } else {
+        final GeckoProfile p = GeckoProfile.get(this);
+        if (p != null && !p.inGuestMode()) {
+            // This is *only* valid because we never want to use the guest mode
+            // profile concurrently with a normal profile -- no syncing to it,
+            // no dual-profile usage, nothing. BrowserApp startup with a conventional
+            // GeckoProfile will cause the guest profile to be deleted.
             GeckoProfile.maybeCleanupGuestProfile(this);
         }
 
         // This has to be prepared prior to calling GeckoApp.onCreate, because
         // widget code and BrowserToolbar need it, and they're created by the
         // layout, which GeckoApp takes care of.
         ((GeckoApplication) getApplication()).prepareLightweightTheme();
         super.onCreate(savedInstanceState);
@@ -668,23 +669,16 @@ public class BrowserApp extends GeckoApp
         if (mediaManagerClass != null) {
             try {
                 Method init = mediaManagerClass.getMethod("init", Context.class);
                 init.invoke(null, this);
             } catch(Exception ex) {
                 Log.e(LOGTAG, "Error initializing media manager", ex);
             }
         }
-
-        if (getProfile().inGuestMode()) {
-            GuestSession.showNotification(this);
-        } else {
-            // If we're restarting, we won't destroy the activity. Make sure we remove any guest notifications that might have been shown.
-            GuestSession.hideNotification(this);
-        }
     }
 
     private void setupSystemUITinting() {
         if (!Versions.feature19Plus) {
             return;
         }
 
         getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
@@ -786,16 +780,45 @@ public class BrowserApp extends GeckoApp
         EventDispatcher.getInstance().registerGeckoThreadListener((GeckoEventListener)this,
             "Prompt:ShowTop");
 
         final LocalBroadcastManager lbm = LocalBroadcastManager.getInstance(this);
         lbm.unregisterReceiver(mOnboardingReceiver);
     }
 
     @Override
+    public void onStart() {
+        super.onStart();
+
+        // Queue this work so that the first launch of the activity doesn't
+        // trigger profile init too early.
+        ThreadUtils.postToBackgroundThread(new Runnable() {
+            @Override
+            public void run() {
+                if (getProfile().inGuestMode()) {
+                    GuestSession.showNotification(BrowserApp.this);
+                } else {
+                    // If we're restarting, we won't destroy the activity.
+                    // Make sure we remove any guest notifications that might
+                    // have been shown.
+                    GuestSession.hideNotification(BrowserApp.this);
+                }
+            }
+        });
+    }
+
+    @Override
+    public void onStop() {
+        super.onStop();
+
+        // We only show the guest mode notification when our activity is in the foreground.
+        GuestSession.hideNotification(this);
+    }
+
+    @Override
     public void onWindowFocusChanged(boolean hasFocus) {
         super.onWindowFocusChanged(hasFocus);
         // If Home Page is visible, the layerView surface has to be visible
         // to avoid a surface issue in Gingerbread phones.
         // We need to do this on the next iteration.
         // See bugs: 1058027 and 1003123
         if (mInitialized && hasFocus &&
             Versions.preHC && isHomePagerVisible() &&
@@ -1082,18 +1105,16 @@ public class BrowserApp extends GeckoApp
             mOrderedBroadcastHelper = null;
         }
 
         if (mBrowserHealthReporter != null) {
             mBrowserHealthReporter.uninit();
             mBrowserHealthReporter = null;
         }
 
-        GuestSession.onDestroy(this);
-
         EventDispatcher.getInstance().unregisterGeckoThreadListener((GeckoEventListener)this,
             "Menu:Update",
             "Reader:Added",
             "Reader:FaviconRequest",
             "Search:Keyword",
             "Prompt:ShowTop",
             "Accounts:Exist");
 
@@ -2934,16 +2955,19 @@ public class BrowserApp extends GeckoApp
                 try {
                     int itemId = new JSONObject(result).getInt("button");
                     if (itemId == 0) {
                         String args = "";
                         if (type == GuestModeDialog.ENTERING) {
                             args = GUEST_BROWSING_ARG;
                         } else {
                             GeckoProfile.leaveGuestSession(BrowserApp.this);
+
+                            // Now's a good time to make sure we're not displaying the Guest Browsing notification.
+                            GuestSession.hideNotification(BrowserApp.this);
                         }
 
                         if (!GuestSession.isSecureKeyguardLocked(BrowserApp.this)) {
                             doRestart(args);
                         } else {
                             // If the secure keyguard is up, we don't want to restart.
                             // Just clear the guest profile data.
                             GeckoProfile.maybeCleanupGuestProfile(BrowserApp.this);
--- a/mobile/android/base/ChromeCast.java
+++ b/mobile/android/base/ChromeCast.java
@@ -188,17 +188,18 @@ class ChromeCast implements GeckoMediaPl
             public void onApplicationDisconnected(int errorCode) { }
         });
 
         apiClient = new GoogleApiClient.Builder(context)
             .addApi(Cast.API, apiOptionsBuilder.build())
             .addConnectionCallbacks(new GoogleApiClient.ConnectionCallbacks() {
                 @Override
                 public void onConnected(Bundle connectionHint) {
-                    if (!apiClient.isConnected()) {
+                    // Sometimes apiClient is null here. See bug 1061032
+                    if (apiClient != null && !apiClient.isConnected()) {
                         debug("Connection failed");
                         callback.sendError("Not connected");
                         return;
                     }
 
                     // Launch the media player app and launch this url once its loaded
                     try {
                         Cast.CastApi.launchApplication(apiClient, CastMediaControlIntent.DEFAULT_MEDIA_RECEIVER_APPLICATION_ID, true)
@@ -248,80 +249,95 @@ class ChromeCast implements GeckoMediaPl
         return true;
     }
 
     public void play(final EventCallback callback) {
         if (!verifySession(callback)) {
             return;
         }
 
-        remoteMediaPlayer.play(apiClient).setResultCallback(new ResultCallback<MediaChannelResult>() {
-            @Override
-            public void onResult(MediaChannelResult result) {
-                Status status = result.getStatus();
-                if (!status.isSuccess()) {
-                    debug("Unable to play: " + status.getStatusCode());
-                    callback.sendError(status.toString());
-                } else {
-                    callback.sendSuccess(null);
+        try {
+            remoteMediaPlayer.play(apiClient).setResultCallback(new ResultCallback<MediaChannelResult>() {
+                @Override
+                public void onResult(MediaChannelResult result) {
+                    Status status = result.getStatus();
+                    if (!status.isSuccess()) {
+                        debug("Unable to play: " + status.getStatusCode());
+                        callback.sendError(status.toString());
+                    } else {
+                        callback.sendSuccess(null);
+                    }
                 }
-            }
-        });
+            });
+        } catch(IllegalStateException ex) {
+            // The media player may throw if the session has been killed. For now, we're just catching this here.
+            callback.sendError("Error playing");
+        }
     }
 
     public void pause(final EventCallback callback) {
         if (!verifySession(callback)) {
             return;
         }
 
-        remoteMediaPlayer.pause(apiClient).setResultCallback(new ResultCallback<MediaChannelResult>() {
-            @Override
-            public void onResult(MediaChannelResult result) {
-                Status status = result.getStatus();
-                if (!status.isSuccess()) {
-                    debug("Unable to pause: " + status.getStatusCode());
-                    callback.sendError(status.toString());
-                } else {
-                    callback.sendSuccess(null);
+        try {
+            remoteMediaPlayer.pause(apiClient).setResultCallback(new ResultCallback<MediaChannelResult>() {
+                @Override
+                public void onResult(MediaChannelResult result) {
+                    Status status = result.getStatus();
+                    if (!status.isSuccess()) {
+                        debug("Unable to pause: " + status.getStatusCode());
+                        callback.sendError(status.toString());
+                    } else {
+                        callback.sendSuccess(null);
+                    }
                 }
-            }
-        });
+            });
+        } catch(IllegalStateException ex) {
+            // The media player may throw if the session has been killed. For now, we're just catching this here.
+            callback.sendError("Error pausing");
+        }
     }
 
     public void end(final EventCallback callback) {
         if (!verifySession(callback)) {
             return;
         }
 
-        Cast.CastApi.stopApplication(apiClient).setResultCallback(new ResultCallback<Status>() {
-            @Override
-            public void onResult(Status result) {
-                if (result.isSuccess()) {
-                    try {
-                        Cast.CastApi.removeMessageReceivedCallbacks(apiClient, remoteMediaPlayer.getNamespace());
-                        remoteMediaPlayer = null;
-                        mSessionId = null;
-                        apiClient.disconnect();
-                        apiClient = null;
+        try {
+            Cast.CastApi.stopApplication(apiClient).setResultCallback(new ResultCallback<Status>() {
+                @Override
+                public void onResult(Status result) {
+                    if (result.isSuccess()) {
+                        try {
+                            Cast.CastApi.removeMessageReceivedCallbacks(apiClient, remoteMediaPlayer.getNamespace());
+                            remoteMediaPlayer = null;
+                            mSessionId = null;
+                            apiClient.disconnect();
+                            apiClient = null;
 
-                        if (callback != null) {
-                            callback.sendSuccess(null);
-                        }
+                            if (callback != null) {
+                                callback.sendSuccess(null);
+                            }
 
-                        return;
-                    } catch(Exception ex) {
-                        debug("Error ending", ex);
+                            return;
+                        } catch(Exception ex) {
+                            debug("Error ending", ex);
+                        }
+                    }
+
+                    if (callback != null) {
+                        callback.sendError(result.getStatus().toString());
                     }
                 }
-
-                if (callback != null) {
-                    callback.sendError(result.getStatus().toString());
-                }
-            }
-        });
+            });
+        } catch(IllegalStateException ex) {
+            // The media player may throw if the session has been killed. For now, we're just catching this here.
+            callback.sendError("Error stopping");
+        }
     }
 
     class MirrorChannel implements MessageReceivedCallback {
         /**
          * @return custom namespace
          */
         public String getNamespace() {
             return "urn:x-cast:org.mozilla.mirror";
@@ -408,17 +424,18 @@ class ChromeCast implements GeckoMediaPl
                 public void onApplicationDisconnected(int errorCode) { }
             });
 
         apiClient = new GoogleApiClient.Builder(context)
             .addApi(Cast.API, apiOptionsBuilder.build())
             .addConnectionCallbacks(new GoogleApiClient.ConnectionCallbacks() {
                     @Override
                     public void onConnected(Bundle connectionHint) {
-                        if (!apiClient.isConnected()) {
+                        // Sometimes apiClient is null here. See bug 1061032
+                        if (apiClient == null || !apiClient.isConnected()) {
                             return;
                         }
 
                         // Launch the media player app and launch this url once its loaded
                         try {
                             Cast.CastApi.launchApplication(apiClient, MIRROR_RECIEVER_APP_ID, true)
                                 .setResultCallback(new MirrorCallback(callback));
                         } catch (Exception e) {
--- a/mobile/android/base/GeckoApp.java
+++ b/mobile/android/base/GeckoApp.java
@@ -4,17 +4,16 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 package org.mozilla.gecko;
 
 import java.io.ByteArrayOutputStream;
 import java.io.File;
 import java.io.IOException;
 import java.io.InputStream;
-import java.net.HttpURLConnection;
 import java.net.URL;
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Iterator;
 import java.util.LinkedList;
 import java.util.List;
 import java.util.Locale;
@@ -40,17 +39,16 @@ import org.mozilla.gecko.health.SessionI
 import org.mozilla.gecko.health.StubbedHealthRecorder;
 import org.mozilla.gecko.menu.GeckoMenu;
 import org.mozilla.gecko.menu.GeckoMenuInflater;
 import org.mozilla.gecko.menu.MenuPanel;
 import org.mozilla.gecko.mozglue.GeckoLoader;
 import org.mozilla.gecko.preferences.ClearOnShutdownPref;
 import org.mozilla.gecko.preferences.GeckoPreferences;
 import org.mozilla.gecko.prompts.PromptService;
-import org.mozilla.gecko.SmsManager;
 import org.mozilla.gecko.updater.UpdateService;
 import org.mozilla.gecko.updater.UpdateServiceHelper;
 import org.mozilla.gecko.util.ActivityResultHandler;
 import org.mozilla.gecko.util.EventCallback;
 import org.mozilla.gecko.util.FileUtils;
 import org.mozilla.gecko.util.GeckoEventListener;
 import org.mozilla.gecko.util.HardwareUtils;
 import org.mozilla.gecko.util.NativeEventListener;
@@ -438,16 +436,19 @@ public abstract class GeckoApp
         }
 
         return super.onMenuOpened(featureId, menu);
     }
 
     @Override
     public boolean onOptionsItemSelected(MenuItem item) {
         if (item.getItemId() == R.id.quit) {
+            // Make sure the Guest Browsing notification goes away when we quit.
+            GuestSession.hideNotification(this);
+
             if (GeckoThread.checkAndSetLaunchState(GeckoThread.LaunchState.GeckoRunning, GeckoThread.LaunchState.GeckoExiting)) {
                 final SharedPreferences prefs = GeckoSharedPrefs.forProfile(this);
                 final Set<String> clearSet = PrefUtils.getStringSet(prefs, ClearOnShutdownPref.PREF, new HashSet<String>());
 
                 final JSONObject clearObj = new JSONObject();
                 for (String clear : clearSet) {
                     try {
                         clearObj.put(clear, true);
--- a/mobile/android/base/GeckoProfile.java
+++ b/mobile/android/base/GeckoProfile.java
@@ -18,16 +18,17 @@ import java.util.Hashtable;
 import org.mozilla.gecko.GeckoProfileDirectories.NoMozillaDirectoryException;
 import org.mozilla.gecko.GeckoProfileDirectories.NoSuchProfileException;
 import org.mozilla.gecko.db.LocalBrowserDB;
 import org.mozilla.gecko.distribution.Distribution;
 import org.mozilla.gecko.mozglue.RobocopTarget;
 import org.mozilla.gecko.util.INIParser;
 import org.mozilla.gecko.util.INISection;
 
+import android.app.Activity;
 import android.content.ContentResolver;
 import android.content.Context;
 import android.content.Intent;
 import android.support.v4.content.LocalBroadcastManager;
 import android.text.TextUtils;
 import android.util.Log;
 
 public final class GeckoProfile {
@@ -85,19 +86,29 @@ public final class GeckoProfile {
             // Check for a cached profile on this context already
             // TODO: We should not be caching profile information on the Activity context
             final GeckoApp geckoApp = (GeckoApp) context;
             if (geckoApp.mProfile != null) {
                 return geckoApp.mProfile;
             }
         }
 
-        // If the guest profile should be used return it.
-        if (GuestSession.shouldUse(context, "")) {
-            return GeckoProfile.getGuestProfile(context);
+        final String args;
+        if (context instanceof Activity) {
+            args = ((Activity) context).getIntent().getStringExtra("args");
+        } else {
+            args = null;
+        }
+
+        if (GuestSession.shouldUse(context, args)) {
+            GeckoProfile p = GeckoProfile.getOrCreateGuestProfile(context);
+            if (isGeckoApp) {
+                ((GeckoApp) context).mProfile = p;
+            }
+            return p;
         }
 
         if (isGeckoApp) {
             final GeckoApp geckoApp = (GeckoApp) context;
             String defaultProfileName;
             try {
                 defaultProfileName = geckoApp.getDefaultProfileName();
             } catch (NoMozillaDirectoryException e) {
@@ -186,16 +197,18 @@ public final class GeckoProfile {
             // Clear all shared prefs for the given profile.
             GeckoSharedPrefs.forProfileName(context, profileName)
                             .edit().clear().apply();
         }
 
         return success;
     }
 
+    // Only public for access from tests.
+    @RobocopTarget
     public static GeckoProfile createGuestProfile(Context context) {
         try {
             // We need to force the creation of a new guest profile if we want it outside of the normal profile path,
             // otherwise GeckoProfile.getDir will try to be smart and build it for us in the normal profiles dir.
             getGuestDir(context).mkdir();
             GeckoProfile profile = getGuestProfile(context);
 
             // If we're creating this guest session over the keyguard, don't lock it.
@@ -227,16 +240,28 @@ public final class GeckoProfile {
 
     private static File getGuestDir(Context context) {
         if (sGuestDir == null) {
             sGuestDir = context.getFileStreamPath("guest");
         }
         return sGuestDir;
     }
 
+    /**
+     * Performs IO. Be careful of using this on the main thread.
+     */
+    public static GeckoProfile getOrCreateGuestProfile(Context context) {
+        GeckoProfile p = getGuestProfile(context);
+        if (p == null) {
+            return createGuestProfile(context);
+        }
+
+        return p;
+    }
+
     public static GeckoProfile getGuestProfile(Context context) {
         if (sGuestProfile == null) {
             File guestDir = getGuestDir(context);
             if (guestDir.exists()) {
                 sGuestProfile = get(context, GUEST_PROFILE, guestDir);
                 sGuestProfile.mInGuestMode = true;
             }
         }
--- a/mobile/android/base/GuestSession.java
+++ b/mobile/android/base/GuestSession.java
@@ -1,37 +1,23 @@
 /* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
  * This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 package org.mozilla.gecko;
 
+import android.app.KeyguardManager;
 import android.app.NotificationManager;
-import android.app.KeyguardManager;
 import android.app.PendingIntent;
 import android.content.Context;
 import android.content.Intent;
 import android.content.res.Resources;
-import android.net.Uri;
-import android.util.Log;
+import android.support.v4.app.NotificationCompat;
 import android.view.Window;
 import android.view.WindowManager;
-import android.widget.ListView;
-import android.support.v4.app.NotificationCompat;
-
-import org.mozilla.gecko.prompts.Prompt;
-import org.mozilla.gecko.util.EventCallback;
-import org.mozilla.gecko.util.NativeEventListener;
-import org.mozilla.gecko.util.NativeJSObject;
-import org.mozilla.gecko.util.ThreadUtils;
-
-import java.io.File;
-
-import org.json.JSONException;
-import org.json.JSONObject;
 
 // Utility methods for entering/exiting guest mode.
 public class GuestSession {
     public static final String NOTIFICATION_INTENT = "org.mozilla.gecko.GUEST_SESSION_INPROGRESS";
     private static final String LOGTAG = "GeckoGuestSession";
 
     // Returns true if the user is using a secure keyguard, and its currently locked.
     static boolean isSecureKeyguardLocked(Context context) {
@@ -100,19 +86,13 @@ public class GuestSession {
         manager.notify(R.id.guestNotification, builder.build());
     }
 
     public static void hideNotification(Context context) {
         final NotificationManager manager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
         manager.cancel(R.id.guestNotification);
     }
 
-    public static void onDestroy(Context context) {
-        if (GeckoProfile.get(context).inGuestMode()) {
-            hideNotification(context);
-        }
-    }
-
     public static void handleIntent(BrowserApp context, Intent intent) {
         context.showGuestModeDialog(BrowserApp.GuestModeDialog.LEAVING);
     }
 
 }
--- a/mobile/android/base/locales/en-US/android_strings.dtd
+++ b/mobile/android/base/locales/en-US/android_strings.dtd
@@ -177,17 +177,17 @@
 <!ENTITY pref_donottrack_allow_tracking "Tell sites that I want to be tracked">
 <!ENTITY pref_donottrack_no_pref "Do not tell sites anything about my tracking preferences">
 
 <!ENTITY pref_char_encoding "Character encoding">
 <!ENTITY pref_char_encoding_on "Show menu">
 <!ENTITY pref_char_encoding_off "Don\'t show menu">
 <!ENTITY pref_clear_private_data2 "Clear now">
 <!ENTITY pref_clear_private_data_category "Clear private data">
-<!ENTITY pref_clear_on_exit_title "Always clear when quitting">
+<!ENTITY pref_clear_on_exit_title2 "Clear on exit">
 <!ENTITY pref_clear_on_exit_summary2 "&brandShortName; will automatically clear your data whenever you select \u0022Quit\u0022 from the main menu">
 <!ENTITY pref_clear_on_exit_dialog_title "Select which data to clear">
 <!ENTITY pref_plugins "Plugins">
 <!ENTITY pref_plugins_enabled "Enabled">
 <!ENTITY pref_plugins_tap_to_play "Tap to play">
 <!ENTITY pref_plugins_disabled "Disabled">
 <!ENTITY pref_text_size "Text size">
 <!ENTITY pref_reflow_on_zoom4 "Text reflow">
--- a/mobile/android/base/strings.xml.in
+++ b/mobile/android/base/strings.xml.in
@@ -186,17 +186,17 @@
   <string name="pref_donottrack_allow_tracking">&pref_donottrack_allow_tracking;</string>
   <string name="pref_donottrack_no_pref">&pref_donottrack_no_pref;</string>
 
   <string name="pref_char_encoding">&pref_char_encoding;</string>
   <string name="pref_char_encoding_on">&pref_char_encoding_on;</string>
   <string name="pref_char_encoding_off">&pref_char_encoding_off;</string>
   <string name="pref_clear_private_data">&pref_clear_private_data2;</string>
   <string name="pref_clear_private_data_category">&pref_clear_private_data_category;</string>
-  <string name="pref_clear_on_exit_title">&pref_clear_on_exit_title;</string>
+  <string name="pref_clear_on_exit_title">&pref_clear_on_exit_title2;</string>
   <string name="pref_clear_on_exit_summary2">&pref_clear_on_exit_summary2;</string>
   <string name="pref_clear_on_exit_dialog_title">&pref_clear_on_exit_dialog_title;</string>
   <string name="pref_plugins">&pref_plugins;</string>
   <string name="pref_plugins_enabled">&pref_plugins_enabled;</string>
   <string name="pref_plugins_tap_to_play">&pref_plugins_tap_to_play;</string>
   <string name="pref_plugins_disabled">&pref_plugins_disabled;</string>
   <string name="pref_text_size">&pref_text_size;</string>
   <string name="pref_font_size_tiny">&pref_font_size_tiny;</string>
--- a/mobile/android/base/tabs/TabsGridLayout.java
+++ b/mobile/android/base/tabs/TabsGridLayout.java
@@ -50,43 +50,43 @@ class TabsGridLayout extends GridView
 
         mTabsAdapter = new TabsGridLayoutAdapter(mContext);
         setAdapter(mTabsAdapter);
 
         setRecyclerListener(new RecyclerListener() {
             @Override
             public void onMovedToScrapHeap(View view) {
                 TabsLayoutItemView item = (TabsLayoutItemView) view;
-                item.thumbnail.setImageDrawable(null);
+                item.setThumbnail(null);
             }
         });
     }
 
     private class TabsGridLayoutAdapter extends TabsLayoutAdapter {
 
         final private Button.OnClickListener mCloseClickListener;
         final private View.OnClickListener mSelectClickListener;
 
         public TabsGridLayoutAdapter (Context context) {
             super(context);
 
             mCloseClickListener = new Button.OnClickListener() {
                 @Override
                 public void onClick(View v) {
                     TabsLayoutItemView itemView = (TabsLayoutItemView) v.getTag();
-                    Tab tab = Tabs.getInstance().getTab(itemView.id);
+                    Tab tab = Tabs.getInstance().getTab(itemView.getTabId());
                     Tabs.getInstance().closeTab(tab);
                 }
             };
 
             mSelectClickListener = new View.OnClickListener() {
                 @Override
                 public void onClick(View v) {
                     TabsLayoutItemView tab = (TabsLayoutItemView) v;
-                    Tabs.getInstance().selectTab(tab.id);
+                    Tabs.getInstance().selectTab(tab.getTabId());
                     autoHidePanel();
                 }
             };
         }
 
         @Override
         TabsLayoutItemView newView(int position, ViewGroup parent) {
             final TabsLayoutItemView item = super.newView(position, parent);
--- a/mobile/android/base/tabs/TabsLayoutItemView.java
+++ b/mobile/android/base/tabs/TabsLayoutItemView.java
@@ -19,23 +19,21 @@ import android.widget.LinearLayout;
 import android.widget.TextView;
 
 public class TabsLayoutItemView extends LinearLayout
                                 implements Checkable {
     private static final String LOGTAG = "Gecko" + TabsLayoutItemView.class.getSimpleName();
     private static final int[] STATE_CHECKED = { android.R.attr.state_checked };
     private boolean mChecked;
 
-    // yeah, it's a bit nasty having two different styles for the class members,
-    // this'll be fixed once bug 1058574 is addressed
-    int id;
-    TextView title;
-    ImageView thumbnail;
-    ImageButton close;
-    TabThumbnailWrapper thumbnailWrapper;
+    private int mTabId;
+    private TextView mTitle;
+    private ImageView mThumbnail;
+    private ImageButton mCloseButton;
+    private TabThumbnailWrapper mThumbnailWrapper;
 
     public TabsLayoutItemView(Context context, AttributeSet attrs) {
         super(context, attrs);
     }
 
     @Override
     public int[] onCreateDrawableState(int extraSpace) {
         final int[] drawableState = super.onCreateDrawableState(extraSpace + 1);
@@ -71,40 +69,52 @@ public class TabsLayoutItemView extends 
     }
 
     @Override
     public void toggle() {
         mChecked = !mChecked;
     }
 
     public void setCloseOnClickListener(OnClickListener mOnClickListener) {
-        close.setOnClickListener(mOnClickListener);
+        mCloseButton.setOnClickListener(mOnClickListener);
     }
 
     @Override
     protected void onFinishInflate() {
         super.onFinishInflate();
-        title = (TextView) findViewById(R.id.title);
-        thumbnail = (ImageView) findViewById(R.id.thumbnail);
-        close = (ImageButton) findViewById(R.id.close);
-        thumbnailWrapper = (TabThumbnailWrapper) findViewById(R.id.wrapper);
+        mTitle = (TextView) findViewById(R.id.title);
+        mThumbnail = (ImageView) findViewById(R.id.thumbnail);
+        mCloseButton = (ImageButton) findViewById(R.id.close);
+        mThumbnailWrapper = (TabThumbnailWrapper) findViewById(R.id.wrapper);
     }
 
     protected void assignValues(Tab tab)  {
         if (tab == null) {
             return;
         }
 
-        id = tab.getId();
+        mTabId = tab.getId();
 
         Drawable thumbnailImage = tab.getThumbnail();
         if (thumbnailImage != null) {
-            thumbnail.setImageDrawable(thumbnailImage);
+            mThumbnail.setImageDrawable(thumbnailImage);
         } else {
-            thumbnail.setImageResource(R.drawable.tab_thumbnail_default);
+            mThumbnail.setImageResource(R.drawable.tab_thumbnail_default);
+        }
+        if (mThumbnailWrapper != null) {
+            mThumbnailWrapper.setRecording(tab.isRecording());
         }
-        if (thumbnailWrapper != null) {
-            thumbnailWrapper.setRecording(tab.isRecording());
-        }
-        title.setText(tab.getDisplayTitle());
-        close.setTag(this);
+        mTitle.setText(tab.getDisplayTitle());
+        mCloseButton.setTag(this);
+    }
+
+    public int getTabId() {
+        return mTabId;
+    }
+
+    public void setThumbnail(Drawable thumbnail) {
+        mThumbnail.setImageDrawable(thumbnail);
+    }
+
+    public void setCloseVisibile(boolean visible) {
+        mCloseButton.setVisibility(visible ? View.VISIBLE : View.INVISIBLE);
     }
 }
--- a/mobile/android/base/tabs/TabsListLayout.java
+++ b/mobile/android/base/tabs/TabsListLayout.java
@@ -75,18 +75,18 @@ class TabsListLayout extends TwoWayView
         mSwipeListener = new TabSwipeGestureListener();
         setOnTouchListener(mSwipeListener);
         setOnScrollListener(mSwipeListener.makeScrollListener());
 
         setRecyclerListener(new RecyclerListener() {
             @Override
             public void onMovedToScrapHeap(View view) {
                 TabsLayoutItemView item = (TabsLayoutItemView) view;
-                item.thumbnail.setImageDrawable(null);
-                item.close.setVisibility(View.VISIBLE);
+                item.setThumbnail(null);
+                item.setCloseVisibile(true);
             }
         });
     }
 
     private class TabsListLayoutAdapter extends TabsLayoutAdapter {
         private Button.OnClickListener mCloseOnClickListener;
         public TabsListLayoutAdapter (Context context) {
             super(context);
@@ -355,17 +355,17 @@ class TabsListLayout extends TwoWayView
         PropertyAnimator animator = new PropertyAnimator(ANIMATION_DURATION);
 
         final boolean isVertical = isVertical();
         if (isVertical)
             animator.attach(view, Property.HEIGHT, 1);
         else
             animator.attach(view, Property.WIDTH, 1);
 
-        final int tabId = ((TabsLayoutItemView) view).id;
+        final int tabId = ((TabsLayoutItemView) view).getTabId();
 
         // Caching this assumes that all rows are the same height
         if (mOriginalSize == 0) {
             mOriginalSize = (isVertical ? view.getHeight() : view.getWidth());
         }
 
         animator.addPropertyAnimationListener(new PropertyAnimator.PropertyAnimationListener() {
             @Override
@@ -392,17 +392,17 @@ class TabsListLayout extends TwoWayView
 
 
         animator.addPropertyAnimationListener(new PropertyAnimator.PropertyAnimationListener() {
             @Override
             public void onPropertyAnimationStart() { }
             @Override
             public void onPropertyAnimationEnd() {
                 TabsLayoutItemView tab = (TabsLayoutItemView) view;
-                tab.close.setVisibility(View.VISIBLE);
+                tab.setCloseVisibile(true);
             }
         });
 
         animator.start();
     }
 
     private class TabSwipeGestureListener implements View.OnTouchListener {
         // same value the stock browser uses for after drag animation velocity in pixels/sec
@@ -490,17 +490,17 @@ class TabsListLayout extends TwoWayView
                     if (mSwipeView == null)
                         break;
 
                     cancelCheckForTap();
                     mSwipeView.setPressed(false);
 
                     if (!mSwiping) {
                         TabsLayoutItemView item = (TabsLayoutItemView) mSwipeView;
-                        Tabs.getInstance().selectTab(item.id);
+                        Tabs.getInstance().selectTab(item.getTabId());
                         autoHidePanel();
 
                         mVelocityTracker.recycle();
                         mVelocityTracker = null;
                         break;
                     }
 
                     mVelocityTracker.addMovement(e);
@@ -577,17 +577,17 @@ class TabsListLayout extends TwoWayView
                     // set pressed state on the swiped view.
                     if (isScrollingX || isScrollingY)
                         cancelCheckForTap();
 
                     if (isSwipingToClose) {
                         mSwiping = true;
                         TabsListLayout.this.requestDisallowInterceptTouchEvent(true);
 
-                        ((TabsLayoutItemView) mSwipeView).close.setVisibility(View.INVISIBLE);
+                        ((TabsLayoutItemView) mSwipeView).setCloseVisibile(false);
 
                         // Stops listview from highlighting the touched item
                         // in the list when swiping.
                         MotionEvent cancelEvent = MotionEvent.obtain(e);
                         cancelEvent.setAction(MotionEvent.ACTION_CANCEL |
                                 (e.getActionIndex() << MotionEvent.ACTION_POINTER_INDEX_SHIFT));
                         TabsListLayout.this.onTouchEvent(cancelEvent);
                         cancelEvent.recycle();
--- a/mobile/android/components/HelperAppDialog.js
+++ b/mobile/android/components/HelperAppDialog.js
@@ -114,17 +114,17 @@ HelperAppLauncherDialog.prototype = {
       if (!win) {
         // Oops.
         Services.console.logStringMessage("Refusing download, but can't show a toast.");
         return;
       }
 
       Services.console.logStringMessage("Refusing download of non-downloadable file.");
       let bundle = Services.strings.createBundle("chrome://browser/locale/handling.properties");
-      let failedText = bundle.GetStringFromName("protocol.failed");
+      let failedText = bundle.GetStringFromName("download.blocked");
       win.toast.show(failedText, "long");
 
       return;
     }
 
     let bundle = Services.strings.createBundle("chrome://browser/locale/browser.properties");
 
     let defaultHandler = new Object();
--- a/mobile/android/locales/en-US/chrome/handling.properties
+++ b/mobile/android/locales/en-US/chrome/handling.properties
@@ -1,7 +1,8 @@
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
+download.blocked=Unable to download file
 protocol.failed=Couldn't find an app to open this link
 # A very short string shown in the button toast when no application can open the url
 protocol.toast.search=Search
--- a/testing/xpcshell/mach_commands.py
+++ b/testing/xpcshell/mach_commands.py
@@ -137,19 +137,20 @@ class XPCShellRunner(MozbuildObject):
 
         xpcshell_filter = TestStartFilter()
         self.log_manager.terminal_handler.addFilter(xpcshell_filter)
 
         tests_dir = os.path.join(self.topobjdir, '_tests', 'xpcshell')
         modules_dir = os.path.join(self.topobjdir, '_tests', 'modules')
         # We want output from the test to be written immediately if we are only
         # running a single test.
-        verbose_output = (test_path is not None or
-                          (manifest and len(manifest.test_paths())==1) or
-                          verbose)
+        single_test = (test_path is not None or
+                       (manifest and len(manifest.test_paths())==1))
+        verbose_output = verbose or single_test
+        sequential = sequential or single_test
 
         args = {
             'manifest': manifest,
             'xpcshell': self.get_binary_path('xpcshell'),
             'mozInfo': os.path.join(self.topobjdir, 'mozinfo.json'),
             'symbolsPath': os.path.join(self.distdir, 'crashreporter-symbols'),
             'interactive': interactive,
             'keepGoing': keep_going,
--- a/toolkit/components/places/Bookmarks.jsm
+++ b/toolkit/components/places/Bookmarks.jsm
@@ -2,65 +2,67 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 /**
  * This module provides an asynchronous API for managing bookmarks.
  *
- * Bookmarks are organized in a tree structure, and can be bookmarked URIs,
- * folders or separators.  Multiple bookmarks for the same URI are allowed.
+ * Bookmarks are organized in a tree structure, and can be bookmarked URLs,
+ * folders or separators.  Multiple bookmarks for the same URL are allowed.
  *
  * Note that if you are handling bookmarks operations in the UI, you should
  * not use this API directly, but rather use PlacesTransactions.jsm, so that
  * any operation is undo/redo-able.
  *
  * Each bookmarked item is represented by an object having the following
  * properties:
  *
  *  - guid (string)
  *      The globally unique identifier of the item.
  *  - parentGuid (string)
  *      The globally unique identifier of the folder containing the item.
  *      This will be an empty string for the Places root folder.
  *  - index (number)
  *      The 0-based position of the item in the parent folder.
- *  - dateAdded (number, microseconds from the epoch)
- *      The time at which the item was added.  This is a PRTime (microseconds).
- *  - lastModified (number, microseconds from the epoch)
- *      The time at which the item was last modified. This is a PRTime (microseconds).
+ *  - dateAdded (Date)
+ *      The time at which the item was added.
+ *  - lastModified (Date)
+ *      The time at which the item was last modified.
  *  - type (number)
  *      The item's type, either TYPE_BOOKMARK, TYPE_FOLDER or TYPE_SEPARATOR.
  *
  *  The following properties are only valid for bookmarks or folders.
  *
  *  - title (string)
  *      The item's title, if any.  Empty titles and null titles are considered
  *      the same and the property is unset on retrieval in such a case.
+ *      Title cannot be longer than TITLE_LENGTH_MAX, or it will be truncated.
  *
  *  The following properties are only valid for bookmarks:
  *
- *  - uri (nsIURI)
- *      The item's URI.
+ *  - url (URL, href or nsIURI)
+ *      The item's URL.  Note that while input objects can contains either
+ *      an URL object, an href string, or an nsIURI, output objects will always
+ *      contain an URL object.
+ *      URL cannot be longer than URL_LENGTH_MAX, methods will throw if a
+ *      longer value is provided
  *  - keyword (string)
  *      The associated keyword, if any.
  *
  * Each successful operation notifies through the nsINavBookmarksObserver
  * interface.  To listen to such notifications you must register using
  * nsINavBookmarksService addObserver and removeObserver methods.
  * Note that bookmark addition or order changes won't notify onItemMoved for
  * items that have their indexes changed.
  * Similarly, lastModified changes not done explicitly (like changing another
  * property) won't fire an onItemChanged notification for the lastModified
  * property.
  * @see nsINavBookmarkObserver
- *
- * @note livemarks are implemented as empty folders.
- *       @see mozIAsyncLivemarks.idl
  */
 
 this.EXPORTED_SYMBOLS = [ "Bookmarks" ];
 
 const { classes: Cc, interfaces: Ci, results: Cr, utils: Cu } = Components;
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "Services",
@@ -69,175 +71,189 @@ XPCOMUtils.defineLazyModuleGetter(this, 
                                   "resource://gre/modules/NetUtil.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "Promise",
                                   "resource://gre/modules/Promise.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "Task",
                                   "resource://gre/modules/Task.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "Sqlite",
                                   "resource://gre/modules/Sqlite.jsm");
 
-const URI_LENGTH_MAX = 65536;
+const URL_LENGTH_MAX = 65536;
 const TITLE_LENGTH_MAX = 4096;
 
 let Bookmarks = Object.freeze({
   /**
    * Item's type constants.
    * These should stay consistent with nsINavBookmarksService.idl
    */
   TYPE_BOOKMARK: 1,
   TYPE_FOLDER: 2,
   TYPE_SEPARATOR: 3,
 
   /**
+   * Default index used to append a bookmarked item at the end of a container. 
+   * This should stay consistent with nsINavBookmarksService.idl
+   */
+  DEFAULT_INDEX: -1,
+
+  /**
    * Creates or updates a bookmarked item.
    *
    * If the given guid is found the corresponding item is updated, otherwise,
    * if no guid is provided, a bookmark is created and a new guid is assigned
    * to it.
    *
    * In the creation case, a minimum set of properties must be provided:
    *  - type
    *  - parentGuid
-   *  - URI, only for bookmarks
+   *  - url, only for bookmarks
    * If an index is not specified, it defaults to appending.
    * It's also possible to pass a non-existent guid to force creation of an
    * item with the given guid, but unless you have a very sound reason, such as
    * an undo manager implementation or synchronization, you should not do that.
    *
    * In the update case, you should only set the properties which should be
    * changed, undefined properties won't be taken into account for the update.
    * Moreover, the item's type and the guid are ignored, since they are
    * immutable after creation.  Note that if the passed in values are not
    * coherent with the known values, this rejects.
    * Passing null or an empty string as keyword clears any keyword
    * associated with this bookmark.
    *
    * Note that any known property that doesn't apply to the specific item type
-   * causes rejection.
+   * throws an exception.
    *
    * @param info
    *        object representing a bookmarked item, as defined above.
+   * @param onResult [optional]
+   *        Callback invoked for each created or updated bookmark.  It gets
+   *        the bookmark object as argument.
    *
    * @return {Promise} resolved when the update is complete.
-   * @resolves to the input object, updated with relevant information.
-   * @rejects JavaScript exception.
-   *
-   * @note title is truncated to TITLE_LENGTH_MAX and URI is rejected if
-   *       greater than URI_LENGTH_MAX.
+   * @resolves to a boolean indicating whether any new bookmark has been created.
+   * @rejects JavaScript exception if it's not possible to update or create the
+   *          requested bookmark.
+   * @throws if input is invalid.
    */
   // XXX WIP XXX Will replace functionality from these methods:
   // long long insertBookmark(in long long aParentId, in nsIURI aURI, in long aIndex, in AUTF8String aTitle, [optional] in ACString aGUID);
   // long long createFolder(in long long aParentFolder, in AUTF8String name, in long index, [optional] in ACString aGUID);
   // void moveItem(in long long aItemId, in long long aNewParentId, in long aIndex);
   // long long insertSeparator(in long long aParentId, in long aIndex, [optional] in ACString aGUID);
   // void setItemTitle(in long long aItemId, in AUTF8String aTitle);
   // void setItemDateAdded(in long long aItemId, in PRTime aDateAdded);
   // void setItemLastModified(in long long aItemId, in PRTime aLastModified);
   // void changeBookmarkURI(in long long aItemId, in nsIURI aNewURI);
   // void setKeywordForBookmark(in long long aItemId, in AString aKeyword);
-  update: Task.async(function* (info) {
+  update: function (info, onResult=null) {
     throw new Error("Not yet implemented");
-  }),
+  },
 
   /**
    * Removes a bookmarked item.
    *
    * Input can either be a guid or an object with one of the following
    * properties set:
    *  - guid: if set, only the corresponding item is removed.
    *  - parentGuid: if it's set and is a folder, any children of that folder is
    *                removed, but not the folder itself.
-   *  - URI: if set, any bookmark for that URI is removed.
-   * If multiple of these properties are set, the method rejects.
+   *  - url: if set, any bookmark for that URL is removed.
+   * If multiple of these properties are set, the method throws.
    *
    * Any other property is ignored, known properties may be overwritten.
    *
    * @param guidOrInfo
    *        The globally unique identifier of the item to remove, or an
    *        object representing it, as defined above.
+   * @param onResult [optional]
+   *        Callback invoked for each removed bookmark.  It gets the bookmark
+   *        object as argument.
    *
    * @return {Promise} resolved when the removal is complete.
-   * @resolves to the removed object or an array of them.
-   * @rejects JavaScript exception.
+   * @resolves to a boolean indicating whether any object has been removed.
+   * @rejects JavaScript exception if the provided guid or parentGuid don't
+   *          match any existing bookmark.
+   * @throws if input is invalid.
    */
   // XXX WIP XXX Will replace functionality from these methods:
   // removeItem(in long long aItemId);
   // removeFolderChildren(in long long aItemId);
-  remove: Task.async(function* (guidOrInfo) {
+  remove: function (guidOrInfo, onResult=null) {
     throw new Error("Not yet implemented");
-  }),
+  },
 
   /**
    * Fetches information about a bookmarked item.
    *
    * Input can be either a guid or an object with one, and only one, of these
    * filtering properties set:
    *  - guid
    *      retrieves the item with the specified guid
    *  - parentGuid and index
    *      retrieves the item by its position
-   *  - URI
-   *      retrieves all items having the given URI.
+   *  - url
+   *      retrieves an array of items having the given URL.
    *  - keyword
-   *      retrieves all items having the given keyword.
+   *      retrieves an array of items having the given keyword.
    *
    * Any other property is ignored.  Known properties may be overwritten.
    *
    * @param guidOrInfo
    *        The globally unique identifier of the item to fetch, or an
    *        object representing it, as defined above.
    *
    * @return {Promise} resolved when the fetch is complete.
    * @resolves to an object representing the found item, as described above, or
    *           an array of such objects.  if no item is found, the returned
    *           promise is resolved to null.
    * @rejects JavaScript exception.
+   * @throws if input is invalid.
    */
   // XXX WIP XXX Will replace functionality from these methods:
   // long long getIdForItemAt(in long long aParentId, in long aIndex);
   // AUTF8String getItemTitle(in long long aItemId);
   // PRTime getItemDateAdded(in long long aItemId);
   // PRTime getItemLastModified(in long long aItemId);
   // nsIURI getBookmarkURI(in long long aItemId);
   // long getItemIndex(in long long aItemId);
   // unsigned short getItemType(in long long aItemId);
   // boolean isBookmarked(in nsIURI aURI);
   // long long getFolderIdForItem(in long long aItemId);
   // void getBookmarkIdsForURI(in nsIURI aURI, [optional] out unsigned long count, [array, retval, size_is(count)] out long long bookmarks);
   // AString getKeywordForURI(in nsIURI aURI);
   // AString getKeywordForBookmark(in long long aItemId);
   // nsIURI getURIForKeyword(in AString keyword);
-  fetch: Task.async(function* (guidOrInfo) {
+  fetch: function (guidOrInfo) {
     throw new Error("Not yet implemented");
-  }),
+  },
 
   /**
    * Retrieves an object representation of a bookmarked item, along with all of
    * its descendants, if any.
    *
-   * Each node in the tree is an object that extends
-   * the item representation described above with some additional properties:
+   * Each node in the tree is an object that extends the item representation
+   * described above with some additional properties:
    *
    *  - [deprecated] id (number)
    *      the item's id.  Defined only if aOptions.includeItemIds is set.
    *  - annos (array)
    *      the item's annotations.  This is not set if there are no annotations
    *      set for the item.
    *
    * The root object of the tree also has the following properties set:
    *  - itemsCount (number, not enumerable)
    *      the number of items, including the root item itself, which are
    *      represented in the resolved object.
    *
-   * Bookmarked URIs may also have the following properties:
+   * Bookmarked URLs may also have the following properties:
    *  - tags (string)
    *      csv string of the bookmark's tags, if any.
    *  - charset (string)
    *      the last known charset of the bookmark, if any.
-   *  - iconuri (string)
+   *  - iconurl (URL)
    *      the bookmark's favicon URL, if any.
    *
    * Folders may also have the following properties:
    *  - children (array)
    *      the folder's children information, each of them having the same set of
    *      properties as above.
    *
    * @param [optional] guid
@@ -261,35 +277,37 @@ let Bookmarks = Object.freeze({
    *           Use it if you must. It'll be removed once the switch to guids is
    *           complete.
    *
    * @return {Promise} resolved when the fetch is complete.
    * @resolves to an object that represents either a single item or a
    *           bookmarks tree.  if guid points to a non-existent item, the
    *           returned promise is resolved to null.
    * @rejects JavaScript exception.
+   * @throws if input is invalid.
    */
   // XXX WIP XXX: will replace functionality for these methods:
   // PlacesUtils.promiseBookmarksTree()
-  fetchTree: Task.async(function* (guid = "", options = {}) {
+  fetchTree: function (guid = "", options = {}) {
     throw new Error("Not yet implemented");
-  }),
+  },
 
   /**
    * Reorders contents of a folder based on a provided array of GUIDs.
    *
    * @param parentGuid
    *        The globally unique identifier of the folder whose contents should
    *        be reordered.
    * @param orderedChildrenGuids
    *        Ordered array of the children's GUIDs.  If this list contains
    *        non-existing entries they will be ignored.  If the list is
    *        incomplete, missing entries will be appended.
    *
    * @return {Promise} resolved when reordering is complete.
    * @rejects JavaScript exception.
+   * @throws if input is invalid.
    */
   // XXX WIP XXX Will replace functionality from these methods:
   // void setItemIndex(in long long aItemId, in long aNewIndex);
-  reorder: Task.async(function* (parentGuid, orderedChildrenGuids) {
+  reorder: function (parentGuid, orderedChildrenGuids) {
     throw new Error("Not yet implemented");
-  })
+  }
 });
--- a/toolkit/themes/linux/global/autocomplete.css
+++ b/toolkit/themes/linux/global/autocomplete.css
@@ -112,33 +112,36 @@ treechildren.autocomplete-treebody::-moz
   color: HighlightText;
 }
 
 .autocomplete-richlistitem {
   padding: 6px 2px;
   color: MenuText;
 }
 
+.ac-url-box {
+  /* When setting a vertical margin here, half of that needs to be added
+     .ac-title-box's translateY for when .ac-url-box is hidden (see below). */
+  margin-top: 1px;
+}
+
 .autocomplete-richlistitem[actiontype="keyword"] .ac-url-box,
 .autocomplete-richlistitem[actiontype="searchengine"] .ac-url-box,
 .autocomplete-richlistitem[actiontype="visiturl"] .ac-url-box,
 .autocomplete-richlistitem[type~="autofill"] .ac-url-box {
-  display: none;
+  visibility: hidden;
 }
 
 .autocomplete-richlistitem[actiontype="keyword"] .ac-title-box,
 .autocomplete-richlistitem[actiontype="searchengine"] .ac-title-box,
 .autocomplete-richlistitem[actiontype="visiturl"] .ac-title-box,
 .autocomplete-richlistitem[type~="autofill"] .ac-title-box {
-  margin-top: 12px;
-  margin-bottom: 12px;
-}
-
-.ac-url-box {
-  margin-top: 1px;
+  /* Center the title by moving it down by half of .ac-url-box's height,
+     including vertical margins (if any). */
+  transform: translateY(.5em);
 }
 
 .ac-site-icon {
   width: 16px; 
   height: 16px;
   margin-bottom: -2px;
   -moz-margin-start: 3px;
   -moz-margin-end: 6px;
--- a/toolkit/themes/osx/global/autocomplete.css
+++ b/toolkit/themes/osx/global/autocomplete.css
@@ -97,33 +97,36 @@ treechildren.autocomplete-treebody::-moz
   color: HighlightText;
   background-image: linear-gradient(rgba(255,255,255,0.3), transparent);
 }
 
 .autocomplete-richlistitem {
   padding: 5px 2px;
 }
 
+.ac-url-box {
+  /* When setting a vertical margin here, half of that needs to be added
+     .ac-title-box's translateY for when .ac-url-box is hidden (see below). */
+  margin-top: 1px;
+}
+
 .autocomplete-richlistitem[actiontype="keyword"] .ac-url-box,
 .autocomplete-richlistitem[actiontype="searchengine"] .ac-url-box,
 .autocomplete-richlistitem[actiontype="visiturl"] .ac-url-box,
 .autocomplete-richlistitem[type~="autofill"] .ac-url-box {
-  display: none;
+  visibility: hidden;
 }
 
 .autocomplete-richlistitem[actiontype="keyword"] .ac-title-box,
 .autocomplete-richlistitem[actiontype="searchengine"] .ac-title-box,
 .autocomplete-richlistitem[actiontype="visiturl"] .ac-title-box,
 .autocomplete-richlistitem[type~="autofill"] .ac-title-box {
-  margin-top: 12px;
-  margin-bottom: 12px;
-}
-
-.ac-url-box {
-  margin-top: 1px;
+  /* Center the title by moving it down by half of .ac-url-box's height,
+     including vertical margins (if any). */
+  transform: translateY(.5em);
 }
 
 .ac-site-icon {
   width: 16px; 
   height: 16px;
   margin-bottom: -1px;
   -moz-margin-start: 7px;
   -moz-margin-end: 5px;
--- a/toolkit/themes/windows/global/autocomplete.css
+++ b/toolkit/themes/windows/global/autocomplete.css
@@ -125,37 +125,40 @@ treechildren.autocomplete-treebody::-moz
     border-radius: 6px;
     outline: 1px solid rgb(124,163,206);
     -moz-outline-radius: 3px;
     outline-offset: -2px;
   }
 }
 %endif
 
+.ac-title-box {
+  margin-top: 4px;
+}
+
+.ac-url-box {
+  /* When setting a vertical margin here, half of that needs to be added
+     .ac-title-box's translateY for when .ac-url-box is hidden (see below). */
+  margin: 1px 0 4px;
+}
+
 .autocomplete-richlistitem[actiontype="keyword"] .ac-url-box,
 .autocomplete-richlistitem[actiontype="searchengine"] .ac-url-box,
 .autocomplete-richlistitem[actiontype="visiturl"] .ac-url-box,
 .autocomplete-richlistitem[type~="autofill"] .ac-url-box {
-  display: none;
+  visibility: hidden;
 }
 
 .autocomplete-richlistitem[actiontype="keyword"] .ac-title-box,
 .autocomplete-richlistitem[actiontype="searchengine"] .ac-title-box,
 .autocomplete-richlistitem[actiontype="visiturl"] .ac-title-box,
 .autocomplete-richlistitem[type~="autofill"] .ac-title-box {
-  margin-top: 12px;
-  margin-bottom: 12px;
-}
-
-.ac-title-box {
-  margin-top: 4px;
-}
-
-.ac-url-box {
-  margin: 1px 0 4px;
+  /* Center the title by moving it down by half of .ac-url-box's height,
+     including vertical margins (if any). */
+  transform: translateY(calc(.5em + 2px));
 }
 
 .ac-site-icon {
   width: 16px; 
   height: 16px;
   margin: 0 5px -2px;
 }