Bug 1271998 - Part 2 - Make our URL bar scrollable. r=jwu
authorJan Henning <jh+bugzilla@buttercookie.de>
Sun, 27 Aug 2017 17:31:13 +0200
changeset 429520 c8aa9b29b27834382b0b9ce67114927c2f348925
parent 429519 fe02dfd16dc37b13888abd56e279b2d5b08a8d9e
child 429521 953adb3e5e838f53df4a3a590a40b4c6f2c62949
push id7761
push userjlund@mozilla.com
push dateFri, 15 Sep 2017 00:19:52 +0000
treeherdermozilla-beta@c38455951db4 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjwu
bugs1271998
milestone57.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1271998 - Part 2 - Make our URL bar scrollable. r=jwu Limited space for URLs on mobile browsers has given rise to a class of phishing attacks that rely on a carefully crafted URL with a long subdomain being cut off such as to give the impression of another, legitimate URL [1]. We've experimented in the past with avoiding this by showing only the base domain or the EV certificate owner, but had to revert to the old behaviour because of users complaining about not being able to see as much of the URL as formerly possible. Making the displayed URL scrollable is therefore a nice solution: It allows us to choose the initial scroll position such as to put the focus on the base domain, while giving users the freedom to easily view all the rest of the URL without having to enter editing mode. To make the URL scrollable, we wrap the TextView with a HorizontalScrollView. Alternatively, it would have been possible to use a ScrollingMovementMethod with the TextView, however that way - flinging the text doesn't work out of the box - dragging the text around is still detected as a normal long-press as well and triggers the context menu [1]. E.g. https://manage-myaccount.paypal.com-webapps.verifcheck.com/signin/ (see https://twitter.com/ericlaw/status/900429796240277504 for an example screenshot). MozReview-Commit-ID: LPEXQA2kBvD
mobile/android/app/src/photon/java/org/mozilla/gecko/toolbar/ToolbarDisplayLayout.java
mobile/android/app/src/photon/res/layout/toolbar_display_layout.xml
mobile/android/base/java/org/mozilla/gecko/toolbar/BrowserToolbar.java
--- a/mobile/android/app/src/photon/java/org/mozilla/gecko/toolbar/ToolbarDisplayLayout.java
+++ b/mobile/android/app/src/photon/java/org/mozilla/gecko/toolbar/ToolbarDisplayLayout.java
@@ -35,16 +35,17 @@ import android.text.SpannableStringBuild
 import android.text.TextUtils;
 import android.text.style.ForegroundColorSpan;
 import android.util.AttributeSet;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.widget.Button;
 
 import org.mozilla.gecko.switchboard.SwitchBoard;
+import org.mozilla.gecko.widget.themed.ThemedView;
 
 /**
 * {@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.
 *
@@ -91,16 +92,17 @@ public class ToolbarDisplayLayout extend
 
     private final BrowserApp mActivity;
 
     private UIMode mUiMode;
 
     private boolean mIsAttached;
 
     private final ThemedTextView mTitle;
+    private final ThemedView mTitleBackground;
     private final int mTitlePadding;
     private ToolbarPrefs mPrefs;
     private OnTitleChangeListener mTitleChangeListener;
 
     private final ThemedImageButton mSiteSecurity;
     private final ThemedImageButton mStop;
     private OnStopListener mStopListener;
 
@@ -135,16 +137,17 @@ public class ToolbarDisplayLayout extend
         super(context, attrs);
         setOrientation(HORIZONTAL);
 
         mActivity = (BrowserApp) context;
 
         LayoutInflater.from(context).inflate(R.layout.toolbar_display_layout, this);
 
         mTitle = (ThemedTextView) findViewById(R.id.url_bar_title);
+        mTitleBackground = (ThemedView) findViewById(R.id.url_bar_title_bg);
         mTitlePadding = mTitle.getPaddingRight();
 
         mUrlColorSpan = new ForegroundColorSpan(ContextCompat.getColor(context, R.color.url_bar_urltext));
         mPrivateUrlColorSpan = new ForegroundColorSpan(ContextCompat.getColor(context, R.color.url_bar_urltext_private));
         mBlockedColorSpan = new ForegroundColorSpan(ContextCompat.getColor(context, R.color.url_bar_blockedtext));
         mPrivateBlockedColorSpan = new ForegroundColorSpan(ContextCompat.getColor(context, R.color.url_bar_blockedtext_private));
         mDomainColorSpan = new ForegroundColorSpan(ContextCompat.getColor(context, R.color.url_bar_domaintext));
         mPrivateDomainColorSpan = new ForegroundColorSpan(ContextCompat.getColor(context, R.color.url_bar_domaintext_private));
@@ -162,16 +165,18 @@ public class ToolbarDisplayLayout extend
     }
 
     @Override
     public void setPrivateMode(boolean isPrivate) {
         super.setPrivateMode(isPrivate);
         mSiteSecurity.setPrivateMode(isPrivate);
         mStop.setPrivateMode(isPrivate);
         mPageActionLayout.setPrivateMode(isPrivate);
+        mTitle.setPrivateMode(isPrivate);
+        mTitleBackground.setPrivateMode(isPrivate);
     }
 
     @Override
     public void onAttachedToWindow() {
         super.onAttachedToWindow();
 
         mIsAttached = true;
 
@@ -239,16 +244,17 @@ public class ToolbarDisplayLayout extend
         }
 
         if (flags.contains(UpdateFlags.PROGRESS)) {
             updateProgress(tab);
         }
 
         if (flags.contains(UpdateFlags.PRIVATE_MODE)) {
             mTitle.setPrivateMode(tab.isPrivate());
+            mTitleBackground.setPrivateMode(tab.isPrivate());
         }
     }
 
     void setTitle(CharSequence title) {
         mTitle.setText(title);
 
         if (TextUtils.isEmpty(title)) {
             //  Reset TextDirection to Locale in order to reveal text hint in correct direction
--- a/mobile/android/app/src/photon/res/layout/toolbar_display_layout.xml
+++ b/mobile/android/app/src/photon/res/layout/toolbar_display_layout.xml
@@ -21,25 +21,50 @@
         android:id="@+id/site_security"
         style="@style/UrlBar.SiteIdentity"
         android:layout_gravity="center_vertical"
         android:background="@drawable/url_bar_title_bg"
         android:contentDescription="@string/site_security"
         android:src="@drawable/security_mode_icon"
         tools:src="@drawable/ic_lock"/>
 
-    <org.mozilla.gecko.widget.FadedMultiColorTextView
-        android:id="@+id/url_bar_title"
-        style="@style/UrlBar.Title"
+    <FrameLayout
         android:layout_width="match_parent"
-        android:layout_gravity="center_vertical"
-        android:layout_weight="1.0"
-        android:background="@drawable/url_bar_title_bg"
-        gecko:fadeBackgroundColor="@android:color/transparent"
-        gecko:fadeWidth="40dip"/>
+        android:layout_height="match_parent"
+        android:layout_weight="1.0">
+
+        <org.mozilla.gecko.widget.themed.ThemedView
+            android:id="@+id/url_bar_title_bg"
+            android:layout_width="match_parent"
+            android:layout_height="@dimen/browser_toolbar_url_height"
+            android:layout_gravity="center_vertical"
+            android:background="@drawable/url_bar_title_bg"/>
+
+        <!-- We need this on a separate layer to avoid fading out the toolbar background as well
+     and we can't use a hardware layer because that causes problems with the snapshot
+     for our toolbar animation.-->
+        <org.mozilla.gecko.widget.FadedHorizontalScrollView
+            android:id="@+id/url_bar_title_scroll_view"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:fillViewport="true"
+            android:scrollbars="none"
+            android:overScrollMode="never"
+            android:layerType="software"
+            gecko:fadeWidth="25dp">
+
+            <org.mozilla.gecko.widget.themed.ThemedTextView
+                android:id="@+id/url_bar_title"
+                style="@style/UrlBar.Title"
+                android:layout_width="wrap_content"
+                android:layout_gravity="center_vertical"/>
+
+        </org.mozilla.gecko.widget.FadedHorizontalScrollView>
+
+    </FrameLayout>
 
     <org.mozilla.gecko.toolbar.PageActionLayout
         android:id="@+id/page_action_layout"
         android:layout_width="wrap_content"
         android:layout_height="match_parent"
         android:orientation="horizontal"
         android:visibility="gone"
         tools:visibility="visible"/>
--- a/mobile/android/base/java/org/mozilla/gecko/toolbar/BrowserToolbar.java
+++ b/mobile/android/base/java/org/mozilla/gecko/toolbar/BrowserToolbar.java
@@ -51,16 +51,17 @@ import android.util.Log;
 import android.view.ContextMenu;
 import android.view.LayoutInflater;
 import android.view.MenuInflater;
 import android.view.MotionEvent;
 import android.view.View;
 import android.view.ViewTreeObserver.OnGlobalLayoutListener;
 import android.view.inputmethod.InputMethodManager;
 import android.widget.Button;
+import android.widget.HorizontalScrollView;
 import android.widget.LinearLayout;
 import android.widget.PopupWindow;
 import android.support.annotation.NonNull;
 
 /**
 * {@code BrowserToolbar} is single entry point for users of the toolbar
 * subsystem i.e. this should be the only import outside the 'toolbar'
 * package.
@@ -111,16 +112,17 @@ public abstract class BrowserToolbar ext
     }
 
     protected enum UIMode {
         EDIT,
         DISPLAY
     }
 
     protected final ToolbarDisplayLayout urlDisplayLayout;
+    protected final HorizontalScrollView urlDisplayScroll;
     protected final ToolbarEditLayout urlEditLayout;
     protected final View urlBarEntry;
     protected boolean isSwitchingTabs;
     protected final ThemedImageButton tabsButton;
 
     private AnimatedProgressBar progressBar;
     protected final TabCounter tabsCounter;
     protected final View menuButton;
@@ -181,16 +183,17 @@ public abstract class BrowserToolbar ext
         activity = (BrowserApp) context;
 
         LayoutInflater.from(context).inflate(R.layout.browser_toolbar, this);
 
         Tabs.registerOnTabsChangedListener(this);
         isSwitchingTabs = true;
 
         urlDisplayLayout = (ToolbarDisplayLayout) findViewById(R.id.display_layout);
+        urlDisplayScroll = (HorizontalScrollView) findViewById(R.id.url_bar_title_scroll_view);
         urlBarEntry = findViewById(R.id.url_bar_entry);
         urlEditLayout = (ToolbarEditLayout) findViewById(R.id.edit_layout);
 
         tabsButton = (ThemedImageButton) findViewById(R.id.tabs);
         tabsCounter = (TabCounter) findViewById(R.id.tabs_counter);
         tabsCounter.setLayerType(View.LAYER_TYPE_SOFTWARE, null);
 
         menuButton = findViewById(R.id.menu);
@@ -208,17 +211,20 @@ public abstract class BrowserToolbar ext
         shadowPaint.setStrokeWidth(0.0f);
 
         setUIMode(UIMode.DISPLAY);
 
         prefs = new ToolbarPrefs();
         urlDisplayLayout.setToolbarPrefs(prefs);
         urlEditLayout.setToolbarPrefs(prefs);
 
-        setOnCreateContextMenuListener(new View.OnCreateContextMenuListener() {
+        // ScrollViews are allowed to have only one child.
+        final View scrollChild = urlDisplayScroll.getChildAt(0);
+
+        scrollChild.setOnCreateContextMenuListener(new View.OnCreateContextMenuListener() {
             @Override
             public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) {
                 // Do not show the context menu while editing
                 if (isEditing()) {
                     return;
                 }
 
                 // NOTE: Use MenuUtils.safeSetVisible because some actions might
@@ -251,17 +257,17 @@ public abstract class BrowserToolbar ext
                     menu.findItem(R.id.add_to_launcher).setVisible(false);
                     menu.findItem(R.id.set_as_homepage).setVisible(false);
                     MenuUtils.safeSetVisible(menu, R.id.subscribe, false);
                     MenuUtils.safeSetVisible(menu, R.id.add_search_engine, false);
                 }
             }
         });
 
-        setOnClickListener(new OnClickListener() {
+        scrollChild.setOnClickListener(new OnClickListener() {
             @Override
             public void onClick(View v) {
                 if (activateListener != null) {
                     activateListener.onActivate();
                 }
             }
         });
     }