Merge f-t to m-c, a=merge
authorPhil Ringnalda <philringnalda@gmail.com>
Sat, 14 May 2016 13:04:50 -0700
changeset 336464 403912ca555eb65f814b18ecf38ad8e8e98569f5
parent 336442 1665c708ebabf7b6e31c741cd4590f14e3e5beba (current diff)
parent 336463 e9b22ce9e0296b451dd3dd84ee7f054ccdc381b5 (diff)
child 336465 4a8ed77f6bb573f20980056bf8c1dadd125c1a85
child 336466 8c85bfdc54424712869b6e62b3ff3c1477dacabe
child 336478 c67242e935ee610b6f6a6e37596a274bc9ed2c9f
push id6249
push userjlund@mozilla.com
push dateMon, 01 Aug 2016 13:59:36 +0000
treeherdermozilla-beta@bad9d4f5bf7e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone49.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 f-t to m-c, a=merge
browser/components/nsBrowserGlue.js
mobile/android/base/resources/drawable/bookmark_folder.xml
services/common/KintoBlocklist.js
services/common/kinto-updater.js
services/common/tests/unit/test_kintoAddonPluginBlocklist.js
services/common/tests/unit/test_kintoCertBlocklist.js
services/common/tests/unit/test_kinto_updater.js
--- a/browser/components/nsBrowserGlue.js
+++ b/browser/components/nsBrowserGlue.js
@@ -373,17 +373,19 @@ BrowserGlue.prototype = {
       case "initial-migration-did-import-default-bookmarks":
         this._initPlaces(true);
         break;
       case "handle-xul-text-link":
         let linkHandled = subject.QueryInterface(Ci.nsISupportsPRBool);
         if (!linkHandled.data) {
           let win = RecentWindow.getMostRecentBrowserWindow();
           if (win) {
-            win.openUILinkIn(data, "tab");
+            data = JSON.parse(data);
+            let where = win.whereToOpenLink(data);
+            win.openUILinkIn(data.href, where);
             linkHandled.data = true;
           }
         }
         break;
       case "profile-before-change":
          // Any component depending on Places should be finalized in
          // _onPlacesShutdown.  Any component that doesn't need to act after
          // the UI has gone should be finalized in _onQuitApplicationGranted.
--- a/browser/extensions/pocket/install.rdf.in
+++ b/browser/extensions/pocket/install.rdf.in
@@ -5,17 +5,17 @@
 
 #filter substitution
 
 <RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
      xmlns:em="http://www.mozilla.org/2004/em-rdf#">
 
   <Description about="urn:mozilla:install-manifest">
     <em:id>firefox@getpocket.com</em:id>
-    <em:version>1.0</em:version>
+    <em:version>1.0.3b1</em:version>
     <em:type>2</em:type>
     <em:bootstrap>true</em:bootstrap>
 
     <!-- Target Application this theme can install into,
         with minimum and maximum supported versions. -->
     <em:targetApplication>
       <Description>
         <em:id>{ec8030f7-c20a-464f-9b0e-13a3a9e97384}</em:id>
--- a/devtools/.eslintrc
+++ b/devtools/.eslintrc
@@ -12,16 +12,17 @@
     "dump": true,
     "exports": true,
     "isWorker": true,
     "loader": true,
     "module": true,
     "require": true,
     "setInterval": true,
     "setTimeout": true,
+    "XMLHttpRequest": true,
     "_Iterator": true,
   },
   "rules": {
     // These are the rules that have been configured so far to match the
     // devtools coding style.
 
     // Rules from the mozilla plugin
     "mozilla/mark-test-function-used": 1,
--- a/devtools/client/shared/widgets/MdnDocsWidget.js
+++ b/devtools/client/shared/widgets/MdnDocsWidget.js
@@ -155,17 +155,17 @@ exports.appendSyntaxHighlightedCSS = app
  * The promise is resolved with the page as an XML document.
  *
  * The promise is rejected with an error message if
  * we could not load the page.
  */
 function getMdnPage(pageUrl) {
   let deferred = Promise.defer();
 
-  let xhr = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"].createInstance(Ci.nsIXMLHttpRequest);
+  let xhr = new XMLHttpRequest();
 
   xhr.addEventListener("load", onLoaded, false);
   xhr.addEventListener("error", onError, false);
 
   xhr.open("GET", pageUrl);
   xhr.responseType = "document";
   xhr.send();
 
--- a/devtools/shared/builtin-modules.js
+++ b/devtools/shared/builtin-modules.js
@@ -239,16 +239,23 @@ const globals = exports.globals = {
   _Iterator: Iterator,
   loader: {
     lazyGetter: defineLazyGetter,
     lazyImporter: defineLazyModuleGetter,
     lazyServiceGetter: defineLazyServiceGetter,
     lazyRequireGetter: lazyRequireGetter,
     id: null // Defined by Loader.jsm
   },
+
+  // Let new XMLHttpRequest do the right thing.
+  XMLHttpRequest: function () {
+    return Cc["@mozilla.org/xmlextras/xmlhttprequest;1"]
+           .createInstance(Ci.nsIXMLHttpRequest);
+  },
+
   // Make sure `define` function exists.  This allows defining some modules
   // in AMD format while retaining CommonJS compatibility through this hook.
   // JSON Viewer needs modules in AMD format, as it currently uses RequireJS
   // from a content document and can't access our usual loaders.  So, any
   // modules shared with the JSON Viewer should include a define wrapper:
   //
   //   // Make this available to both AMD and CJS environments
   //   define(function(require, exports, module) {
--- a/mobile/android/base/java/org/mozilla/gecko/BrowserApp.java
+++ b/mobile/android/base/java/org/mozilla/gecko/BrowserApp.java
@@ -988,16 +988,19 @@ public class BrowserApp extends GeckoApp
         if (urls != null) {
             openUrls(urls);
         }
     }
 
     @Override
     public void onResume() {
         super.onResume();
+        if (mIsAbortingAppLaunch) {
+            return;
+        }
 
         // Needed for Adjust to get accurate session measurements
         AdjustConstants.getAdjustHelper().onResume();
 
         if (!mHasResumed) {
             EventDispatcher.getInstance().unregisterGeckoThreadListener((GeckoEventListener) this,
                     "Prompt:ShowTop");
             mHasResumed = true;
@@ -1008,16 +1011,19 @@ public class BrowserApp extends GeckoApp
         for (BrowserAppDelegate delegate : delegates) {
             delegate.onResume(this);
         }
     }
 
     @Override
     public void onPause() {
         super.onPause();
+        if (mIsAbortingAppLaunch) {
+            return;
+        }
 
         // Needed for Adjust to get accurate session measurements
         AdjustConstants.getAdjustHelper().onPause();
 
         if (mHasResumed) {
             // Register for Prompt:ShowTop so we can foreground this activity even if it's hidden.
             EventDispatcher.getInstance().registerGeckoThreadListener((GeckoEventListener) this,
                 "Prompt:ShowTop");
@@ -1027,25 +1033,31 @@ public class BrowserApp extends GeckoApp
         for (BrowserAppDelegate delegate : delegates) {
             delegate.onPause(this);
         }
     }
 
     @Override
     public void onRestart() {
         super.onRestart();
+        if (mIsAbortingAppLaunch) {
+            return;
+        }
 
         for (final BrowserAppDelegate delegate : delegates) {
             delegate.onRestart(this);
         }
     }
 
     @Override
     public void onStart() {
         super.onStart();
+        if (mIsAbortingAppLaunch) {
+            return;
+        }
 
         // 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() {
                 final GeckoProfile profile = getProfile();
                 if (profile.inGuestMode()) {
@@ -1076,16 +1088,19 @@ public class BrowserApp extends GeckoApp
         for (final BrowserAppDelegate delegate : delegates) {
             delegate.onStart(this);
         }
     }
 
     @Override
     public void onStop() {
         super.onStop();
+        if (mIsAbortingAppLaunch) {
+            return;
+        }
 
         // We only show the guest mode notification when our activity is in the foreground.
         GuestSession.hideNotification(this);
 
         for (final BrowserAppDelegate delegate : delegates) {
             delegate.onStop(this);
         }
     }
@@ -1320,18 +1335,17 @@ public class BrowserApp extends GeckoApp
     @Override
     public void setAccessibilityEnabled(boolean enabled) {
         super.setAccessibilityEnabled(enabled);
         mDynamicToolbar.setAccessibilityEnabled(enabled);
     }
 
     @Override
     public void onDestroy() {
-        if (!HardwareUtils.isSupportedSystem()) {
-            // This build does not support the Android version of the device; Exit early.
+        if (mIsAbortingAppLaunch) {
             super.onDestroy();
             return;
         }
 
         mDynamicToolbar.destroy();
 
         if (mBrowserToolbar != null)
             mBrowserToolbar.onDestroy();
--- a/mobile/android/base/java/org/mozilla/gecko/GeckoApp.java
+++ b/mobile/android/base/java/org/mozilla/gecko/GeckoApp.java
@@ -173,16 +173,19 @@ public abstract class GeckoApp
     protected RelativeLayout mGeckoLayout;
     private View mCameraView;
     private OrientationEventListener mCameraOrientationEventListener;
     public List<GeckoAppShell.AppStateListener> mAppStateListeners = new LinkedList<GeckoAppShell.AppStateListener>();
     protected MenuPanel mMenuPanel;
     protected Menu mMenu;
     protected boolean mIsRestoringActivity;
 
+    /** Tells if we're aborting app launch, e.g. if this is an unsupported device configuration. */
+    protected boolean mIsAbortingAppLaunch;
+
     private ContactService mContactService;
     private PromptService mPromptService;
     protected TextSelection mTextSelection;
 
     protected DoorHangerPopup mDoorHangerPopup;
     protected FormAssistPopup mFormAssistPopup;
 
 
@@ -1089,16 +1092,17 @@ public abstract class GeckoApp
 
         // Enable Android Strict Mode for developers' local builds (the "default" channel).
         if ("default".equals(AppConstants.MOZ_UPDATE_CHANNEL)) {
             enableStrictMode();
         }
 
         if (!HardwareUtils.isSupportedSystem()) {
             // This build does not support the Android version of the device: Show an error and finish the app.
+            mIsAbortingAppLaunch = true;
             super.onCreate(savedInstanceState);
             showSDKVersionError();
             finish();
             return;
         }
 
         // The clock starts...now. Better hurry!
         mJavaUiStartupTimer = new Telemetry.UptimeTimer("FENNEC_STARTUP_TIME_JAVAUI");
@@ -1343,19 +1347,33 @@ public abstract class GeckoApp
 
         GeckoAppShell.setNotificationClient(makeNotificationClient());
         IntentHelper.init(this);
     }
 
     @Override
     public void onStart() {
         super.onStart();
+        if (mIsAbortingAppLaunch) {
+            return;
+        }
+
         mWasFirstTabShownAfterActivityUnhidden = false; // onStart indicates we were hidden.
     }
 
+    @Override
+    protected void onStop() {
+        super.onStop();
+        // Overriding here is not necessary, but we do this so we don't
+        // forget to add the abort if we override this method later.
+        if (mIsAbortingAppLaunch) {
+            return;
+        }
+    }
+
     /**
      * At this point, the resource system and the rest of the browser are
      * aware of the locale.
      *
      * Now we can display strings!
      *
      * You can think of this as being something like a second phase of onCreate,
      * where you can do string-related operations. Use this in place of embedding
@@ -1971,16 +1989,19 @@ public abstract class GeckoApp
     }
 
     @Override
     public void onResume()
     {
         // After an onPause, the activity is back in the foreground.
         // Undo whatever we did in onPause.
         super.onResume();
+        if (mIsAbortingAppLaunch) {
+            return;
+        }
 
         int newOrientation = getResources().getConfiguration().orientation;
         if (GeckoScreenOrientation.getInstance().update(newOrientation)) {
             refreshChrome();
         }
 
         if (!Versions.feature14Plus) {
             // Update accessibility settings in case it has been changed by the
@@ -2048,16 +2069,21 @@ public abstract class GeckoApp
             mLayerView.setFocusableInTouchMode(true);
             getWindow().setBackgroundDrawable(null);
         }
     }
 
     @Override
     public void onPause()
     {
+        if (mIsAbortingAppLaunch) {
+            super.onPause();
+            return;
+        }
+
         final HealthRecorder rec = mHealthRecorder;
         final Context context = this;
 
         // In some way it's sad that Android will trigger StrictMode warnings
         // here as the whole point is to save to disk while the activity is not
         // interacting with the user.
         ThreadUtils.postToBackgroundThread(new Runnable() {
             @Override
@@ -2096,32 +2122,37 @@ public abstract class GeckoApp
             }
         }
 
         super.onPause();
     }
 
     @Override
     public void onRestart() {
+        if (mIsAbortingAppLaunch) {
+            super.onRestart();
+            return;
+        }
+
         // Faster on main thread with an async apply().
         final StrictMode.ThreadPolicy savedPolicy = StrictMode.allowThreadDiskReads();
         try {
             SharedPreferences.Editor editor = GeckoApp.this.getSharedPreferences().edit();
             editor.putBoolean(GeckoApp.PREFS_WAS_STOPPED, false);
             editor.apply();
         } finally {
             StrictMode.setThreadPolicy(savedPolicy);
         }
 
         super.onRestart();
     }
 
     @Override
     public void onDestroy() {
-        if (!HardwareUtils.isSupportedSystem()) {
+        if (mIsAbortingAppLaunch) {
             // This build does not support the Android version of the device:
             // We did not initialize anything, so skip cleaning up.
             super.onDestroy();
             return;
         }
 
         EventDispatcher.getInstance().unregisterGeckoThreadListener((GeckoEventListener)this,
             "Gecko:Ready",
--- a/mobile/android/base/java/org/mozilla/gecko/Telemetry.java
+++ b/mobile/android/base/java/org/mozilla/gecko/Telemetry.java
@@ -164,18 +164,21 @@ public class Telemetry {
      * @param method A non-null method (if null is desired, consider using Method.NONE)
      */
     private static void sendUIEvent(final String eventName, final Method method,
             final long timestamp, final String extras) {
         if (method == null) {
             throw new IllegalArgumentException("Expected non-null method - use Method.NONE?");
         }
 
-        Log.d(LOGTAG, "SendUIEvent: event = " + eventName + " method = " + method +
-                " timestamp = " + timestamp + " extras = " + extras);
+        String logString = "SendUIEvent: event = " + eventName + " method = " + method + " timestamp = " + timestamp;
+        if (!AppConstants.MOZILLA_OFFICIAL) {
+            logString += " extras = " + extras;
+        }
+        Log.d(LOGTAG, logString);
         final GeckoEvent geckoEvent = GeckoEvent.createTelemetryUIEvent(
                 eventName, method.toString(), timestamp, extras);
         GeckoAppShell.sendEventToGecko(geckoEvent);
     }
 
     public static void sendUIEvent(final Event event, final Method method, final long timestamp,
             final String extras) {
         sendUIEvent(event.toString(), method, timestamp, extras);
--- a/mobile/android/base/java/org/mozilla/gecko/db/BrowserDB.java
+++ b/mobile/android/base/java/org/mozilla/gecko/db/BrowserDB.java
@@ -117,16 +117,18 @@ public interface BrowserDB {
     public abstract void updateBookmark(ContentResolver cr, int id, String uri, String title, String keyword);
     public abstract boolean hasBookmarkWithGuid(ContentResolver cr, String guid);
 
     /**
      * Can return <code>null</code>.
      */
     public abstract Cursor getBookmarksInFolder(ContentResolver cr, long folderId);
 
+    public abstract int getBookmarkCountForFolder(ContentResolver cr, long folderId);
+
     /**
      * Get the favicon from the database, if any, associated with the given favicon URL. (That is,
      * the URL of the actual favicon image, not the URL of the page with which the favicon is associated.)
      * @param cr The ContentResolver to use.
      * @param faviconURL The URL of the favicon to fetch from the database.
      * @return The decoded Bitmap from the database, if any. null if none is stored.
      */
     public abstract LoadFaviconResult getFaviconForUrl(ContentResolver cr, String faviconURL);
--- a/mobile/android/base/java/org/mozilla/gecko/db/LocalBrowserDB.java
+++ b/mobile/android/base/java/org/mozilla/gecko/db/LocalBrowserDB.java
@@ -877,16 +877,25 @@ public class LocalBrowserDB implements B
             cursorsToMerge.add(c);
             final Cursor[] arr = (Cursor[]) Array.newInstance(Cursor.class, cursorsToMerge.size());
             return new MergeCursor(cursorsToMerge.toArray(arr));
         } else {
             return c;
         }
     }
 
+    @Override
+    public int getBookmarkCountForFolder(ContentResolver cr, long folderID) {
+        if (folderID == Bookmarks.FAKE_READINGLIST_SMARTFOLDER_ID) {
+            return getUrlAnnotations().getAnnotationCount(cr, BrowserContract.UrlAnnotations.Key.READER_VIEW);
+        } else {
+            throw new IllegalArgumentException("Retrieving bookmark count for folder with ID=" + folderID + " not supported yet");
+        }
+    }
+
     @CheckResult
     private ArrayList<Cursor> getSpecialFoldersCursorList(final boolean addDesktopFolder,
             final boolean addScreenshotsFolder, final boolean addReadingListFolder) {
         if (addDesktopFolder || addScreenshotsFolder || addReadingListFolder) {
             // Avoid calling this twice.
             assertDefaultBookmarkColumnOrdering();
         }
 
--- a/mobile/android/base/java/org/mozilla/gecko/db/LocalUrlAnnotations.java
+++ b/mobile/android/base/java/org/mozilla/gecko/db/LocalUrlAnnotations.java
@@ -223,9 +223,31 @@ public class LocalUrlAnnotations impleme
     public void insertReaderViewUrl(final ContentResolver cr, final String pageUrl) {
         insertAnnotation(cr, pageUrl, Key.READER_VIEW.getDbValue(), BrowserContract.UrlAnnotations.READER_VIEW_SAVED_VALUE);
     }
 
     @Override
     public void deleteReaderViewUrl(ContentResolver cr, String pageURL) {
         deleteAnnotation(cr, pageURL, Key.READER_VIEW);
     }
+
+    public int getAnnotationCount(ContentResolver cr, Key key) {
+        final String countColumnname = "count";
+        final Cursor c = queryByKey(cr,
+                key,
+                new String[] {
+                        "COUNT(*) AS " + countColumnname
+                },
+                null);
+
+        try {
+            if (c != null && c.moveToFirst()) {
+                return c.getInt(c.getColumnIndexOrThrow(countColumnname));
+            } else {
+                return 0;
+            }
+        } finally {
+            if (c != null) {
+                c.close();
+            }
+        }
+    }
 }
--- a/mobile/android/base/java/org/mozilla/gecko/db/StubBrowserDB.java
+++ b/mobile/android/base/java/org/mozilla/gecko/db/StubBrowserDB.java
@@ -136,16 +136,21 @@ class StubUrlAnnotations implements UrlA
     @Override
     public void deleteReaderViewUrl(ContentResolver cr, String pageURL) {}
 
     @Override
     public boolean hasAcceptedOrDeclinedHomeScreenShortcut(ContentResolver cr, String url) { return false; }
 
     @Override
     public void insertHomeScreenShortcut(ContentResolver cr, String url, boolean hasCreatedShortCut) {}
+
+    @Override
+    public int getAnnotationCount(ContentResolver cr, BrowserContract.UrlAnnotations.Key key) {
+        return 0;
+    }
 }
 
 /*
  * This base implementation just stubs all methods. For the
  * real implementations, see LocalBrowserDB.java.
  */
 public class StubBrowserDB implements BrowserDB {
     private final StubSearches searches = new StubSearches();
@@ -237,16 +242,18 @@ public class StubBrowserDB implements Br
     public void clearHistory(ContentResolver cr, boolean clearSearchHistory) {
     }
 
     @RobocopTarget
     public Cursor getBookmarksInFolder(ContentResolver cr, long folderId) {
         return null;
     }
 
+    public int getBookmarkCountForFolder(ContentResolver cr, long folderId) { return 0; }
+
     @RobocopTarget
     public boolean isBookmark(ContentResolver cr, String uri) {
         return false;
     }
 
     public String getUrlForKeyword(ContentResolver cr, String keyword) {
         return null;
     }
--- a/mobile/android/base/java/org/mozilla/gecko/db/UrlAnnotations.java
+++ b/mobile/android/base/java/org/mozilla/gecko/db/UrlAnnotations.java
@@ -41,9 +41,11 @@ public interface UrlAnnotations {
     /**
      * Insert an indication that the user has interacted with this URL in regards to home screen
      * shortcuts.
      *
      * @param hasCreatedShortCut True if a home screen shortcut has been created for this URL. False
      *                           if the user has actively declined to create a shortcut for this URL.
      */
     void insertHomeScreenShortcut(ContentResolver cr, String url, boolean hasCreatedShortCut);
+
+    int getAnnotationCount(ContentResolver cr, BrowserContract.UrlAnnotations.Key key);
 }
--- a/mobile/android/base/java/org/mozilla/gecko/home/BookmarkFolderView.java
+++ b/mobile/android/base/java/org/mozilla/gecko/home/BookmarkFolderView.java
@@ -1,73 +1,147 @@
 /* -*- 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.home;
 
+import org.mozilla.gecko.GeckoProfile;
 import org.mozilla.gecko.R;
+import org.mozilla.gecko.db.BrowserContract;
+import org.mozilla.gecko.db.BrowserDB;
+import org.mozilla.gecko.util.ThreadUtils;
+import org.mozilla.gecko.util.UIAsyncTask;
 
 import android.content.Context;
 import android.support.annotation.NonNull;
 import android.util.AttributeSet;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
 import android.widget.TextView;
 
-public class BookmarkFolderView extends TextView {
+import java.lang.ref.WeakReference;
+import java.util.Collections;
+import java.util.Set;
+import java.util.TreeSet;
+
+public class BookmarkFolderView extends LinearLayout {
+    private static final Set<Integer> FOLDERS_WITH_COUNT;
+
+    static {
+        final Set<Integer> folders = new TreeSet<>();
+        folders.add(BrowserContract.Bookmarks.FAKE_READINGLIST_SMARTFOLDER_ID);
+
+        FOLDERS_WITH_COUNT = Collections.unmodifiableSet(folders);
+    }
+
     public enum FolderState {
         /**
          * A standard folder, i.e. a folder in a list of bookmarks and folders.
          */
-        FOLDER(0),
+        FOLDER(R.drawable.folder_closed),
 
         /**
          * The parent folder: this indicates that you are able to return to the previous
          * folder ("Back to {name}").
          */
-        PARENT(R.attr.parent),
+        PARENT(R.drawable.bookmark_folder_arrow_up),
 
         /**
          * The reading list smartfolder: this displays a reading list icon instead of the
          * normal folder icon.
          */
-        READING_LIST(R.attr.reading_list);
+        READING_LIST(R.drawable.reading_list_folder);
 
-        public final int state;
+        public final int image;
 
-        FolderState(final int state) { this.state = state; }
+        FolderState(final int image) { this.image = image; }
     }
 
-    private FolderState mState;
+    private final TextView mTitle;
+    private final TextView mSubtitle;
+
+    private final ImageView mIcon;
 
     public BookmarkFolderView(Context context) {
-        super(context);
-        mState = FolderState.FOLDER;
+        this(context, null);
     }
 
     public BookmarkFolderView(Context context, AttributeSet attrs) {
         super(context, attrs);
-        mState = FolderState.FOLDER;
+
+        LayoutInflater.from(context).inflate(R.layout.two_line_folder_row, this);
+
+        mTitle = (TextView) findViewById(R.id.title);
+        mSubtitle = (TextView) findViewById(R.id.subtitle);
+        mIcon =  (ImageView) findViewById(R.id.icon);
     }
 
-    public BookmarkFolderView(Context context, AttributeSet attrs, int defStyle) {
-        super(context, attrs, defStyle);
-        mState = FolderState.FOLDER;
+    public void update(String title, int folderID) {
+        setTitle(title);
+        setID(folderID);
+    }
+
+    private void setTitle(String title) {
+        mTitle.setText(title);
     }
 
-    @Override
-    public int[] onCreateDrawableState(int extraSpace) {
-        final int[] drawableState = super.onCreateDrawableState(extraSpace + 1);
+    private static class ItemCountUpdateTask extends UIAsyncTask.WithoutParams<Integer> {
+        private final WeakReference<TextView> mTextViewReference;
+        private final int mFolderID;
+
+        public ItemCountUpdateTask(final WeakReference<TextView> textViewReference,
+                                   final int folderID) {
+            super(ThreadUtils.getBackgroundHandler());
 
-        if (mState != null && mState != FolderState.FOLDER) {
-            mergeDrawableStates(drawableState, new int[] {  mState.state });
+            mTextViewReference = textViewReference;
+            mFolderID = folderID;
+        }
+
+        @Override
+        protected Integer doInBackground() {
+            final TextView textView = mTextViewReference.get();
+
+            if (textView == null) {
+                return null;
+            }
+
+            final BrowserDB db = GeckoProfile.get(textView.getContext()).getDB();
+            return db.getBookmarkCountForFolder(textView.getContext().getContentResolver(), mFolderID);
         }
 
-        return drawableState;
+        @Override
+        protected void onPostExecute(Integer count) {
+            final TextView textView = mTextViewReference.get();
+
+            if (textView == null) {
+                return;
+            }
+
+            final String text;
+            if (count == 1) {
+                text = textView.getContext().getResources().getString(R.string.bookmark_folder_one_item);
+            } else {
+                text = textView.getContext().getResources().getString(R.string.bookmark_folder_items, count);
+            }
+
+            textView.setText(text);
+            textView.setVisibility(View.VISIBLE);
+        }
+    }
+
+    private void setID(final int folderID) {
+        if (FOLDERS_WITH_COUNT.contains(folderID)) {
+            final WeakReference<TextView> subTitleReference = new WeakReference<TextView>(mSubtitle);
+
+            new ItemCountUpdateTask(subTitleReference, folderID).execute();
+        } else {
+            mSubtitle.setVisibility(View.GONE);
+        }
     }
 
     public void setState(@NonNull FolderState state) {
-        if (state != mState) {
-            mState = state;
-            refreshDrawableState();
-        }
+        mIcon.setImageResource(state.image);
     }
 }
--- a/mobile/android/base/java/org/mozilla/gecko/home/BookmarksListAdapter.java
+++ b/mobile/android/base/java/org/mozilla/gecko/home/BookmarksListAdapter.java
@@ -329,22 +329,23 @@ class BookmarksListAdapter extends Multi
             ((BookmarkScreenshotRow) view).updateFromCursor(cursor);
         } else if (viewType == VIEW_TYPE_BOOKMARK_ITEM) {
             final TwoLinePageRow row = (TwoLinePageRow) view;
             row.updateFromCursor(cursor);
         } else {
             final BookmarkFolderView row = (BookmarkFolderView) view;
             if (cursor == null) {
                 final Resources res = context.getResources();
-                row.setText(res.getString(R.string.home_move_back_to_filter, mParentStack.get(1).title));
+                row.update(res.getString(R.string.home_move_back_to_filter, mParentStack.get(1).title), -1);
                 row.setState(FolderState.PARENT);
             } else {
-                row.setText(getFolderTitle(context, cursor));
+                int id = cursor.getInt(cursor.getColumnIndexOrThrow(Bookmarks._ID));
 
-                int id = cursor.getInt(cursor.getColumnIndexOrThrow(Bookmarks._ID));
+                row.update(getFolderTitle(context, cursor), id);
+
                 if (id == Bookmarks.FAKE_READINGLIST_SMARTFOLDER_ID) {
                     row.setState(FolderState.READING_LIST);
                 } else {
                     row.setState(FolderState.FOLDER);
                 }
             }
         }
     }
--- a/mobile/android/base/java/org/mozilla/gecko/toolbar/ToolbarDisplayLayout.java
+++ b/mobile/android/base/java/org/mozilla/gecko/toolbar/ToolbarDisplayLayout.java
@@ -17,16 +17,17 @@ import org.mozilla.gecko.reader.ReaderMo
 import org.mozilla.gecko.SiteIdentity;
 import org.mozilla.gecko.SiteIdentity.MixedMode;
 import org.mozilla.gecko.SiteIdentity.SecurityMode;
 import org.mozilla.gecko.SiteIdentity.TrackingMode;
 import org.mozilla.gecko.Tab;
 import org.mozilla.gecko.animation.PropertyAnimator;
 import org.mozilla.gecko.animation.ViewHelper;
 import org.mozilla.gecko.toolbar.BrowserToolbarTabletBase.ForwardButtonAnimation;
+import org.mozilla.gecko.util.Experiments;
 import org.mozilla.gecko.util.HardwareUtils;
 import org.mozilla.gecko.util.StringUtils;
 import org.mozilla.gecko.widget.themed.ThemedLinearLayout;
 import org.mozilla.gecko.widget.themed.ThemedTextView;
 
 import android.content.Context;
 import android.os.SystemClock;
 import android.support.annotation.NonNull;
@@ -37,16 +38,18 @@ import android.text.TextUtils;
 import android.text.style.ForegroundColorSpan;
 import android.util.AttributeSet;
 import android.util.Log;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.widget.Button;
 import android.widget.ImageButton;
 
+import com.keepsafe.switchboard.SwitchBoard;
+
 /**
 * {@code ToolbarDisplayLayout} is the UI for when the toolbar is in
 * display state. It's used to display the state of the currently selected
 * tab. It should always be updated through a single entry point
 * (updateFromTab) and should never track any tab events or gecko messages
 * on its own to keep it as dumb as possible.
 *
 * The UI has two possible modes: progress and display which are triggered
@@ -275,20 +278,21 @@ public class ToolbarDisplayLayout extend
         }
 
         // This value is not visible to screen readers but we rely on it when running UI tests. Screen
         // readers will instead focus BrowserToolbar and read the "base domain" from there. UI tests
         // will read the content description to obtain the full URL for performing assertions.
         setContentDescription(strippedURL);
 
         final SiteIdentity siteIdentity = tab.getSiteIdentity();
-        if (siteIdentity.hasOwner()) {
+        if (siteIdentity.hasOwner() && SwitchBoard.isInExperiment(mActivity, Experiments.URLBAR_SHOW_EV_CERT_OWNER)) {
             // Show Owner of EV certificate as title
             updateTitleFromSiteIdentity(siteIdentity);
-        } else if (isHttpOrHttps && !HardwareUtils.isTablet() && !TextUtils.isEmpty(baseDomain)) {
+        } else if (isHttpOrHttps && !HardwareUtils.isTablet() && !TextUtils.isEmpty(baseDomain)
+                && SwitchBoard.isInExperiment(mActivity, Experiments.URLBAR_SHOW_ORIGIN_ONLY)) {
             // Show just the base domain as title
             setTitle(baseDomain);
         } else if (isHttpOrHttps) {
             // Display full URL with base domain highlighted as title
             updateAndColorTitleFromFullURL(strippedURL, baseDomain, tab.isPrivate());
         } else {
             // Not http(s): Just show the full URL as title
             setTitle(url);
--- a/mobile/android/base/java/org/mozilla/gecko/util/Experiments.java
+++ b/mobile/android/base/java/org/mozilla/gecko/util/Experiments.java
@@ -47,16 +47,22 @@ public class Experiments {
     // Promotion for "Add to homescreen"
     public static final String PROMOTE_ADD_TO_HOMESCREEN = "promote-add-to-homescreen";
 
     public static final String PREF_ONBOARDING_VERSION = "onboarding_version";
 
     // Promotion to bookmark reader-view items after entering reader view three times (Bug 1247689)
     public static final String TRIPLE_READERVIEW_BOOKMARK_PROMPT = "triple-readerview-bookmark-prompt";
 
+    // Only show origin in URL bar instead of full URL (Bug 1236431)
+    public static final String URLBAR_SHOW_ORIGIN_ONLY = "urlbar-show-origin-only";
+
+    // Show name of organization (EV cert) instead of full URL in URL bar (Bug 1249594).
+    public static final String URLBAR_SHOW_EV_CERT_OWNER = "urlbar-show-ev-cert-owner";
+
     private static volatile Boolean disabled = null;
 
     /**
      * Determines whether Switchboard is disabled by the MOZ_DISABLE_SWITCHBOARD
      * environment variable. We need to read this value from the intent string
      * extra because environment variables from our test harness aren't set
      * until Gecko is loaded, and we need to know this before then.
      *
--- a/mobile/android/base/java/org/mozilla/gecko/util/UnusedResourcesUtil.java
+++ b/mobile/android/base/java/org/mozilla/gecko/util/UnusedResourcesUtil.java
@@ -3,25 +3,16 @@ package org.mozilla.gecko.util;
 import org.mozilla.gecko.R;
 
 /**
  * (linter: UnusedResources) We use resources in places Android Lint can't check (e.g. JS) - this is
  * a set of those references so Android Lint stops complaining.
  */
 @SuppressWarnings("unused")
 final class UnusedResourcesUtil {
-    /**
-     * Bug 1269001 prelands some strings for localisation for Aurora (before landing the commits
-     * actually making use of these strings), hence we need to reference them here temporarily.
-     */
-    public static final int[] TEMPORARY_PRELANDED_BOOKMARK_ITEMS = {
-        R.string.bookmark_folder_items,
-        R.string.bookmark_folder_one_item,
-    };
-
     public static final int[] CONSTANTS = {
             R.dimen.match_parent,
             R.dimen.wrap_content,
     };
 
     public static final int[] USED_IN_COLOR_PALETTE = {
             R.color.private_browsing_purple, // This will be used eventually, then this item removed.
     };
--- a/mobile/android/base/java/org/mozilla/gecko/widget/FaviconView.java
+++ b/mobile/android/base/java/org/mozilla/gecko/widget/FaviconView.java
@@ -188,16 +188,17 @@ public class FaviconView extends ImageVi
      *                     (Favicons class), so as to exploit caching.
      */
     private void updateImageInternal(Bitmap bitmap, String key, boolean allowScaling) {
         if (bitmap == null) {
             Log.w(LOGTAG, "updateImageInternal() called without bitmap");
 
             // At this point we do not have a page URL anymore.
             showDefaultFavicon(null);
+            return;
         }
 
         // Reassigning the same bitmap? Don't bother.
         if (mUnscaledBitmap == bitmap) {
             return;
         }
         mUnscaledBitmap = bitmap;
         mIconBitmap = bitmap;
deleted file mode 100644
--- a/mobile/android/base/resources/drawable/bookmark_folder.xml
+++ /dev/null
@@ -1,20 +0,0 @@
-<?xml version="1.0" encoding="utf-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/. -->
-
-<selector xmlns:android="http://schemas.android.com/apk/res/android"
-          xmlns:gecko="http://schemas.android.com/apk/res-auto">
-
-    <!-- state open -->
-    <item gecko:parent="true"
-          android:drawable="@drawable/bookmark_folder_arrow_up"/>
-
-    <!-- reading list folder -->
-    <item gecko:reading_list="true"
-          android:drawable="@drawable/reading_list_folder"/>
-
-    <!-- state close -->
-    <item android:drawable="@drawable/folder_closed"/>
-
-</selector>
--- a/mobile/android/base/resources/layout/bookmark_folder_row.xml
+++ b/mobile/android/base/resources/layout/bookmark_folder_row.xml
@@ -1,12 +1,13 @@
 <?xml version="1.0" encoding="utf-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/. -->
 
 <org.mozilla.gecko.home.BookmarkFolderView xmlns:android="http://schemas.android.com/apk/res/android"
                                            style="@style/Widget.FolderView"
                                            android:layout_width="match_parent"
-                                           android:paddingLeft="20dp"
-                                           android:drawablePadding="20dp"
-                                           android:drawableLeft="@drawable/bookmark_folder"
+                                           android:paddingLeft="0dp"
+                                           android:paddingTop="0dp"
+                                           android:paddingBottom="0dp"
+                                           android:paddingRight="16dp"
                                            android:gravity="center_vertical"/>
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/resources/layout/two_line_folder_row.xml
@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="utf-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/. -->
+
+<merge xmlns:android="http://schemas.android.com/apk/res/android"
+       xmlns:tools="http://schemas.android.com/tools"
+       xmlns:gecko="http://schemas.android.com/apk/res-auto"
+       tools:context=".BrowserApp">
+
+    <ImageView android:id="@+id/icon"
+               android:src="@drawable/folder_closed"
+               android:layout_width="24dp"
+               android:layout_height="24dp"
+               android:scaleType="fitCenter"
+               android:layout_margin="20dp"/>
+
+    <LinearLayout android:layout_width="0dp"
+                  android:layout_height="wrap_content"
+                  android:layout_weight="1"
+                  android:layout_gravity="center_vertical"
+                  android:paddingRight="10dp"
+                  android:orientation="vertical">
+
+        <org.mozilla.gecko.widget.FadedSingleColorTextView
+                android:id="@+id/title"
+                style="@style/Widget.TwoLinePageRow.Title"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                gecko:fadeWidth="90dp"
+                tools:text="This is a long test title"/>
+
+        <org.mozilla.gecko.widget.FadedSingleColorTextView android:id="@+id/subtitle"
+                  style="@style/Widget.TwoLinePageRow.Url"
+                  android:layout_width="match_parent"
+                  android:layout_height="wrap_content"
+                  android:visibility="gone"
+                  gecko:fadeWidth="90dp"
+                  tools:text="1 items"/>
+
+    </LinearLayout>
+
+</merge>
--- a/mobile/android/base/resources/values/attrs.xml
+++ b/mobile/android/base/resources/values/attrs.xml
@@ -128,21 +128,16 @@
     </declare-styleable>
 
     <declare-styleable name="FadedMultiColorTextView">
         <!-- The background color we should be fading over. Useful because the
              background is full alpha and we need to copy the background underneath. -->
         <attr name="fadeBackgroundColor" format="dimension"/>
     </declare-styleable>
 
-    <declare-styleable name="BookmarkFolderView">
-        <attr name="parent" format="boolean"/>
-        <attr name="reading_list" format="boolean"/>
-    </declare-styleable>
-
     <declare-styleable name="IconTabWidget">
         <attr name="android:layout"/>
 
         <!-- Sets the tab's content type. Defaults to icon. -->
         <attr name="display">
             <enum name="icon" value="0x00" />
             <enum name="text" value="0x01" />
         </attr>
--- a/mobile/android/config/mozconfigs/common
+++ b/mobile/android/config/mozconfigs/common
@@ -47,17 +47,21 @@ fi
 ac_add_options --with-android-version=9
 ac_add_options --with-system-zlib
 ac_add_options --enable-update-channel=${MOZ_UPDATE_CHANNEL}
 
 # Treat warnings as errors (modulo ALLOW_COMPILER_WARNINGS).
 ac_add_options --enable-warnings-as-errors
 
 ac_add_options --with-mozilla-api-keyfile=/builds/mozilla-fennec-geoloc-api.key
-ac_add_options --with-adjust-sdk-keyfile=/builds/adjust-sdk.token
+if test "$MOZ_UPDATE_CHANNEL" = "release" ; then
+    ac_add_options --with-adjust-sdk-keyfile=/builds/adjust-sdk.token
+elif test "$MOZ_UPDATE_CHANNEL" = "beta" ; then
+    ac_add_options --with-adjust-sdk-keyfile=/builds/adjust-sdk-beta.token
+fi
 export SOCORRO_SYMBOL_UPLOAD_TOKEN_FILE=/builds/crash-stats-api.token
 
 # Package js shell.
 export MOZ_PACKAGE_JSSHELL=1
 
 # Use ccache
 . "$topsrcdir/build/mozconfig.cache"
 
--- a/toolkit/components/places/tests/bookmarks/test_384228.js
+++ b/toolkit/components/places/tests/bookmarks/test_384228.js
@@ -1,76 +1,98 @@
 /* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* vim:set ts=2 sw=2 sts=2 et: */
 /* 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/. */
 
-// Get bookmark service
-try {
-  var bmsvc = Cc["@mozilla.org/browser/nav-bookmarks-service;1"].getService(Ci.nsINavBookmarksService);
-} catch(ex) {
-  do_throw("Could not get nav-bookmarks-service\n");
-}
+/**
+ * test querying for bookmarks in multiple folders.
+ */
+add_task(function* search_bookmark_in_folder() {
+  let testFolder1 = yield PlacesUtils.bookmarks.insert({
+    parentGuid: PlacesUtils.bookmarks.unfiledGuid,
+    type: PlacesUtils.bookmarks.TYPE_FOLDER,
+    title: "bug 384228 test folder 1"
+  });
+  Assert.equal(testFolder1.index, 0);
+
+  let testFolder2 = yield PlacesUtils.bookmarks.insert({
+    parentGuid: PlacesUtils.bookmarks.unfiledGuid,
+    type: PlacesUtils.bookmarks.TYPE_FOLDER,
+    title: "bug 384228 test folder 2"
+  });
+  Assert.equal(testFolder2.index, 1);
 
-// Get history service
-try {
-  var histsvc = Cc["@mozilla.org/browser/nav-history-service;1"].getService(Ci.nsINavHistoryService);
-} catch(ex) {
-  do_throw("Could not get history service\n");
-}
+  let testFolder3 = yield PlacesUtils.bookmarks.insert({
+    parentGuid: PlacesUtils.bookmarks.unfiledGuid,
+    type: PlacesUtils.bookmarks.TYPE_FOLDER,
+    title: "bug 384228 test folder 3"
+  });
+  Assert.equal(testFolder3.index, 2);
 
-// get bookmarks root id
-var root = bmsvc.bookmarksMenuFolder;
+  let b1 = yield PlacesUtils.bookmarks.insert({
+    parentGuid: testFolder1.guid,
+    url: "http://foo.tld/",
+    title: "title b1 (folder 1)"
+  });
+  Assert.equal(b1.index, 0);
+
+  let b2 = yield PlacesUtils.bookmarks.insert({
+    parentGuid: testFolder1.guid,
+    url: "http://foo.tld/",
+    title: "title b2 (folder 1)"
+  });
+  Assert.equal(b2.index, 1);
 
-// main
-function run_test() {
-  // test querying for bookmarks in multiple folders
-  var testFolder1 = bmsvc.createFolder(root, "bug 384228 test folder 1",
-                                       bmsvc.DEFAULT_INDEX);
-  do_check_eq(bmsvc.getItemIndex(testFolder1), 0);
-  var testFolder2 = bmsvc.createFolder(root, "bug 384228 test folder 2",
-                                       bmsvc.DEFAULT_INDEX);
-  do_check_eq(bmsvc.getItemIndex(testFolder2), 1);
-  var testFolder3 = bmsvc.createFolder(root, "bug 384228 test folder 3",
-                                       bmsvc.DEFAULT_INDEX);
-  do_check_eq(bmsvc.getItemIndex(testFolder3), 2);
+  let b3 = yield PlacesUtils.bookmarks.insert({
+    parentGuid: testFolder2.guid,
+    url: "http://foo.tld/",
+    title: "title b3 (folder 2)"
+  });
+  Assert.equal(b3.index, 0);
+
+  let b4 = yield PlacesUtils.bookmarks.insert({
+    parentGuid: testFolder3.guid,
+    url: "http://foo.tld/",
+    title: "title b4 (folder 3)"
+  });
+  Assert.equal(b4.index, 0);
 
-  var b1 = bmsvc.insertBookmark(testFolder1, uri("http://foo.tld/"),
-                                bmsvc.DEFAULT_INDEX, "title b1 (folder 1)");
-  do_check_eq(bmsvc.getItemIndex(b1), 0);
-  var b2 = bmsvc.insertBookmark(testFolder1, uri("http://foo.tld/"),
-                                bmsvc.DEFAULT_INDEX, "title b2 (folder 1)");
-  do_check_eq(bmsvc.getItemIndex(b2), 1);
-  var b3 = bmsvc.insertBookmark(testFolder2, uri("http://foo.tld/"),
-                                bmsvc.DEFAULT_INDEX, "title b3 (folder 2)");
-  do_check_eq(bmsvc.getItemIndex(b3), 0);
-  var b4 = bmsvc.insertBookmark(testFolder3, uri("http://foo.tld/"),
-                                bmsvc.DEFAULT_INDEX, "title b4 (folder 3)");
-  do_check_eq(bmsvc.getItemIndex(b4), 0);
   // also test recursive search
-  var testFolder1_1 = bmsvc.createFolder(testFolder1, "bug 384228 test folder 1.1",
-                                         bmsvc.DEFAULT_INDEX);
-  do_check_eq(bmsvc.getItemIndex(testFolder1_1), 2);
-  var b5 = bmsvc.insertBookmark(testFolder1_1, uri("http://a1.com/"),
-                                bmsvc.DEFAULT_INDEX, "title b5 (folder 1.1)");
-  do_check_eq(bmsvc.getItemIndex(b5), 0);
+  let testFolder1_1 = yield PlacesUtils.bookmarks.insert({
+    parentGuid: testFolder1.guid,
+    type: PlacesUtils.bookmarks.TYPE_FOLDER,
+    title: "bug 384228 test folder 1.1"
+  });
+  Assert.equal(testFolder1_1.index, 2);
+
+  let b5 = yield PlacesUtils.bookmarks.insert({
+    parentGuid: testFolder1_1.guid,
+    url: "http://foo.tld/",
+    title: "title b5 (folder 1.1)"
+  });
+  Assert.equal(b5.index, 0);
 
-  var options = histsvc.getNewQueryOptions();
-  var query = histsvc.getNewQuery();
+
+  // query folder 1, folder 2 and get 4 bookmarks
+  let folderIds = [];
+  folderIds.push(yield PlacesUtils.promiseItemId(testFolder1.guid));
+  folderIds.push(yield PlacesUtils.promiseItemId(testFolder2.guid));
+
+  let hs = PlacesUtils.history;
+  let options = hs.getNewQueryOptions();
+  let query = hs.getNewQuery();
   query.searchTerms = "title";
-  options.queryType = Ci.nsINavHistoryQueryOptions.QUERY_TYPE_BOOKMARKS;
-  query.setFolders([testFolder1, testFolder2], 2);
-
-  var result = histsvc.executeQuery(query, options);
-  var rootNode = result.root;
+  options.queryType = options.QUERY_TYPE_BOOKMARKS;
+  query.setFolders(folderIds, folderIds.length);
+  let rootNode = hs.executeQuery(query, options).root;
   rootNode.containerOpen = true;
 
   // should not match item from folder 3
-  do_check_eq(rootNode.childCount, 4);
-
-  do_check_eq(rootNode.getChild(0).itemId, b1);
-  do_check_eq(rootNode.getChild(1).itemId, b2);
-  do_check_eq(rootNode.getChild(2).itemId, b3);
-  do_check_eq(rootNode.getChild(3).itemId, b5);
+  Assert.equal(rootNode.childCount, 4);
+  Assert.equal(rootNode.getChild(0).bookmarkGuid, b1.guid);
+  Assert.equal(rootNode.getChild(1).bookmarkGuid, b2.guid);
+  Assert.equal(rootNode.getChild(2).bookmarkGuid, b3.guid);
+  Assert.equal(rootNode.getChild(3).bookmarkGuid, b5.guid);
 
   rootNode.containerOpen = false;
-}
+});
--- a/toolkit/content/aboutSupport.xhtml
+++ b/toolkit/content/aboutSupport.xhtml
@@ -194,16 +194,26 @@
 
             <td>
               <a href="about:memory">about:memory</a>
             </td>
           </tr>
 
           <tr class="no-copy">
             <th class="column">
+              &aboutSupport.appBasicsPerformance;
+            </th>
+
+            <td>
+              <a href="about:performance">about:performance</a>
+            </td>
+          </tr>
+
+          <tr class="no-copy">
+            <th class="column">
               &aboutSupport.appBasicsServiceWorkers;
             </th>
 
             <td>
               <a href="about:serviceworkers">about:serviceworkers</a>
             </td>
           </tr>
 
--- a/toolkit/content/tests/browser/browser.ini
+++ b/toolkit/content/tests/browser/browser.ini
@@ -9,16 +9,17 @@ support-files =
 [browser_bug594509.js]
 [browser_bug982298.js]
 [browser_bug1198465.js]
 [browser_contentTitle.js]
 [browser_default_image_filename.js]
 [browser_f7_caret_browsing.js]
 skip-if = e10s
 [browser_findbar.js]
+[browser_label_textlink.js]
 [browser_isSynthetic.js]
 support-files =
   empty.png
 [browser_keyevents_during_autoscrolling.js]
 [browser_save_resend_postdata.js]
 support-files =
   common/mockTransfer.js
   data/post_form_inner.sjs
new file mode 100644
--- /dev/null
+++ b/toolkit/content/tests/browser/browser_label_textlink.js
@@ -0,0 +1,38 @@
+add_task(function* () {
+  yield BrowserTestUtils.withNewTab({gBrowser, url: "about:config"}, function*(browser) {
+    let newTabURL = "http://www.example.com/";
+    yield ContentTask.spawn(browser, newTabURL, function*(newTabURL) {
+      let doc = content.document;
+      let label = doc.createElement("label");
+      label.href = newTabURL;
+      label.id = "textlink-test";
+      label.className = "text-link";
+      label.textContent = "click me";
+      doc.documentElement.append(label);
+    });
+
+    // Test that click will open tab in foreground.
+    let awaitNewTab = BrowserTestUtils.waitForNewTab(gBrowser, newTabURL);
+    yield BrowserTestUtils.synthesizeMouseAtCenter("#textlink-test", {}, browser);
+    let newTab = yield awaitNewTab;
+    is(newTab.linkedBrowser, gBrowser.selectedBrowser, "selected tab should be example page");
+    yield BrowserTestUtils.removeTab(gBrowser.selectedTab);
+
+    // Test that ctrl+shift+click/meta+shift+click will open tab in background.
+    awaitNewTab = BrowserTestUtils.waitForNewTab(gBrowser, newTabURL);
+    yield BrowserTestUtils.synthesizeMouseAtCenter("#textlink-test",
+      {ctrlKey: true, metaKey: true, shiftKey: true},
+      browser);
+    yield awaitNewTab;
+    is(gBrowser.selectedBrowser, browser, "selected tab should be original tab");
+    yield BrowserTestUtils.removeTab(gBrowser.tabs[gBrowser.tabs.length - 1]);
+
+    // Middle-clicking should open tab in foreground.
+    awaitNewTab = BrowserTestUtils.waitForNewTab(gBrowser, newTabURL);
+    yield BrowserTestUtils.synthesizeMouseAtCenter("#textlink-test",
+      {button: 1}, browser);
+    newTab = yield awaitNewTab;
+    is(newTab.linkedBrowser, gBrowser.selectedBrowser, "selected tab should be example page");
+    yield BrowserTestUtils.removeTab(gBrowser.tabs[gBrowser.tabs.length - 1]);
+  });
+});
--- a/toolkit/content/widgets/text.xml
+++ b/toolkit/content/widgets/text.xml
@@ -351,19 +351,32 @@
           aEvent.preventDefault();
           href = uri ? uri.spec : href;
 
           // Try handing off the link to the host application, e.g. for
           // opening it in a tabbed browser.
           var linkHandled = Components.classes["@mozilla.org/supports-PRBool;1"]
                                       .createInstance(Components.interfaces.nsISupportsPRBool);
           linkHandled.data = false;
+          let {shiftKey, ctrlKey, metaKey, altKey, button} = aEvent;
+          if (!shiftKey && !altKey) {
+            // Preserve legacy behavior of non-modifier left-clicks
+            // opening in a new selected tab.
+            let {AppConstants} =
+              Components.utils.import("resource://gre/modules/AppConstants.jsm", {});
+            if (AppConstants.platform == "macosx") {
+              metaKey = true;
+            } else {
+              ctrlKey = true;
+            }
+          }
+          let data = {shiftKey, ctrlKey, metaKey, altKey, button, href};
           Components.classes["@mozilla.org/observer-service;1"]
                     .getService(Components.interfaces.nsIObserverService)
-                    .notifyObservers(linkHandled, "handle-xul-text-link", href);
+                    .notifyObservers(linkHandled, "handle-xul-text-link", JSON.stringify(data));
           if (linkHandled.data)
             return;
 
           // otherwise, fall back to opening the anchor directly
           var win = window;
           if (window instanceof Components.interfaces.nsIDOMChromeWindow) {
             while (win.opener && !win.opener.closed)
               win = win.opener;
@@ -371,13 +384,14 @@
           win.open(href);
         ]]>
         </body>
       </method>
     </implementation>
 
     <handlers>
       <handler event="click" phase="capturing" button="0" action="this.open(event)"/>
+      <handler event="click" phase="capturing" button="1" action="this.open(event)"/>
       <handler event="keypress" preventdefault="true" keycode="VK_RETURN" action="this.click()" />
     </handlers>
   </binding>
 
 </bindings>
--- a/toolkit/locales/en-US/chrome/global/aboutSupport.dtd
+++ b/toolkit/locales/en-US/chrome/global/aboutSupport.dtd
@@ -51,16 +51,17 @@ This is the Windows- and Mac-specific va
 Windows/Mac use the term "Folder" instead of "Directory" -->
 <!ENTITY aboutSupport.appBasicsProfileDirWinMac "Profile Folder">
 
 <!ENTITY aboutSupport.appBasicsEnabledPlugins "Enabled Plugins">
 <!ENTITY aboutSupport.appBasicsBuildConfig "Build Configuration">
 <!ENTITY aboutSupport.appBasicsUserAgent "User Agent">
 <!ENTITY aboutSupport.appBasicsOS "OS">
 <!ENTITY aboutSupport.appBasicsMemoryUse "Memory Use">
+<!ENTITY aboutSupport.appBasicsPerformance "Performance">
 
 <!-- LOCALIZATION NOTE the term "Service Workers" should not be translated. -->
 <!ENTITY aboutSupport.appBasicsServiceWorkers "Registered Service Workers">
 
 <!ENTITY aboutSupport.appBasicsProfiles "Profiles">
 
 <!ENTITY aboutSupport.appBasicsMultiProcessSupport "Multiprocess Windows">