Merge fx-team to m-c.

This commit is contained in:
Ryan VanderMeulen 2013-08-29 20:43:45 -04:00
commit fb74bca91e
19 changed files with 449 additions and 56 deletions

View File

@ -4653,7 +4653,9 @@ let TabStateCache = {
*/
get: function(aKey) {
let key = this._normalizeToBrowser(aKey);
return this._data.get(key);
let result = this._data.get(key);
TabStateCacheTelemetry.recordAccess(!!result);
return result;
},
/**
@ -4672,6 +4674,7 @@ let TabStateCache = {
* Delete all tab data.
*/
clear: function() {
TabStateCacheTelemetry.recordClear();
this._data.clear();
},
@ -4689,6 +4692,7 @@ let TabStateCache = {
if (data) {
data[aField] = aValue;
}
TabStateCacheTelemetry.recordAccess(!!data);
},
_normalizeToBrowser: function(aKey) {
@ -4702,3 +4706,68 @@ let TabStateCache = {
throw new TypeError("Key is neither a tab nor a browser: " + nodeName);
}
};
let TabStateCacheTelemetry = {
// Total number of hits during the session
_hits: 0,
// Total number of misses during the session
_misses: 0,
// Total number of clears during the session
_clears: 0,
// |true| once we have been initialized
_initialized: false,
/**
* Record a cache access.
*
* @param {boolean} isHit If |true|, the access was a hit, otherwise
* a miss.
*/
recordAccess: function(isHit) {
this._init();
if (isHit) {
++this._hits;
} else {
++this._misses;
}
},
/**
* Record a cache clear
*/
recordClear: function() {
this._init();
++this._clears;
},
/**
* Initialize the telemetry.
*/
_init: function() {
if (this._initialized) {
// Avoid double initialization
return;
}
Services.obs.addObserver(this, "profile-before-change", false);
},
observe: function() {
Services.obs.removeObserver(this, "profile-before-change");
// Record hit/miss rate
let accesses = this._hits + this._misses;
if (accesses == 0) {
return;
}
this._fillHistogram("HIT_RATE", this._hits, accesses);
this._fillHistogram("CLEAR_RATIO", this._clears, accesses);
},
_fillHistogram: function(suffix, positive, total) {
let PREFIX = "FX_SESSION_RESTORE_TABSTATECACHE_";
let histo = Services.telemetry.getHistogramById(PREFIX + suffix);
let rate = Math.floor( ( positive * 100 ) / total );
histo.add(rate);
}
};

View File

@ -94,6 +94,7 @@ var TouchModule = {
// capture phase events
window.addEventListener("CancelTouchSequence", this, true);
window.addEventListener("dblclick", this, true);
window.addEventListener("keydown", this, true);
// bubble phase
window.addEventListener("contextmenu", this, false);
@ -156,11 +157,41 @@ var TouchModule = {
}, 50);
}
break;
case "keydown":
this._handleKeyDown(aEvent);
break;
}
}
}
},
_handleKeyDown: function _handleKeyDown(aEvent) {
const TABKEY = 9;
if (aEvent.keyCode == TABKEY && !InputSourceHelper.isPrecise) {
if (Util.isEditable(aEvent.target) &&
aEvent.target.selectionStart != aEvent.target.selectionEnd) {
SelectionHelperUI.closeEditSession(false);
}
setTimeout(function() {
let element = Browser.selectedBrowser.contentDocument.activeElement;
// We only want to attach monocles if we have an input, text area,
// there is selection, and the target element changed.
// Sometimes the target element won't change even though selection is
// cleared because of focus outside the browser.
if (Util.isEditable(element) &&
!SelectionHelperUI.isActive &&
element.selectionStart != element.selectionEnd &&
// not e10s friendly
aEvent.target != element) {
let rect = element.getBoundingClientRect();
SelectionHelperUI.attachEditSession(Browser.selectedBrowser,
rect.left + rect.width / 2,
rect.top + rect.height / 2);
}
}, 50);
}
},
sample: function sample(aTimeStamp) {
this._waitingForPaint = false;
},

View File

@ -1441,9 +1441,11 @@ abstract public class BrowserApp extends GeckoApp
void filterEditingMode(String searchTerm, AutocompleteHandler handler) {
if (TextUtils.isEmpty(searchTerm)) {
mHomePager.setVisibility(View.VISIBLE);
hideBrowserSearch();
} else {
showBrowserSearch();
mHomePager.setVisibility(View.INVISIBLE);
mBrowserSearch.filter(searchTerm, handler);
}
}

View File

@ -301,7 +301,7 @@ public class GeckoAccessibility {
default:
info.setParent(host);
info.setSource(host, virtualDescendantId);
info.setVisibleToUser(true);
info.setVisibleToUser(host.isFocused());
info.setPackageName(GeckoAppShell.getContext().getPackageName());
info.setClassName(host.getClass().getName());
info.addAction(AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS);

View File

@ -227,6 +227,7 @@ FENNEC_JAVA_FILES = \
home/HomeListView.java \
home/HomePager.java \
home/HomePagerTabStrip.java \
home/HomeBanner.java \
home/FadedTextView.java \
home/FaviconsLoader.java \
home/LastTabsPage.java \
@ -481,6 +482,7 @@ RES_LAYOUT = \
res/layout/home_pager.xml \
res/layout/home_reading_list_page.xml \
res/layout/home_search_item_row.xml \
res/layout/home_banner.xml \
res/layout/home_suggestion_prompt.xml \
res/layout/web_app.xml \
res/layout/launch_app_list.xml \
@ -1113,6 +1115,7 @@ RES_DRAWABLE += \
res/drawable/handle_start_level.xml \
res/drawable/home_history_tabs_indicator.xml \
res/drawable/home_page_title_background.xml \
res/drawable/home_banner.xml \
res/drawable/ic_menu_back.xml \
res/drawable/ic_menu_desktop_mode_off.xml \
res/drawable/ic_menu_desktop_mode_on.xml \

View File

@ -8,6 +8,9 @@ package org.mozilla.gecko.home;
import org.mozilla.gecko.Favicons;
import org.mozilla.gecko.R;
import org.mozilla.gecko.Tabs;
import org.mozilla.gecko.animation.PropertyAnimator;
import org.mozilla.gecko.animation.PropertyAnimator.Property;
import org.mozilla.gecko.animation.ViewHelper;
import org.mozilla.gecko.db.BrowserContract.Bookmarks;
import org.mozilla.gecko.db.BrowserContract.Thumbnails;
import org.mozilla.gecko.db.BrowserDB;
@ -41,7 +44,9 @@ import android.view.ContextMenu.ContextMenuInfo;
import android.view.LayoutInflater;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnTouchListener;
import android.view.ViewGroup;
import android.widget.Toast;
@ -76,6 +81,9 @@ public class BookmarksPage extends HomeFragment {
// Grid of top bookmarks.
private TopBookmarksView mTopBookmarks;
// Banner to show snippets.
private HomeBanner mBanner;
// Adapter for list of bookmarks.
private BookmarksListAdapter mListAdapter;
@ -91,14 +99,22 @@ public class BookmarksPage extends HomeFragment {
// Listener for pinning bookmarks.
private PinBookmarkListener mPinBookmarkListener;
// Raw Y value of the last event that happened on the list view.
private float mListTouchY = -1;
// Scrolling direction of the banner.
private boolean mSnapBannerToTop;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
BookmarksListView list = (BookmarksListView) inflater.inflate(R.layout.home_bookmarks_page, container, false);
final View view = inflater.inflate(R.layout.home_bookmarks_page, container, false);
mList = (BookmarksListView) view.findViewById(R.id.bookmarks_list);
mTopBookmarks = new TopBookmarksView(getActivity());
list.addHeaderView(mTopBookmarks);
mList.addHeaderView(mTopBookmarks);
return list;
return view;
}
@Override
@ -115,7 +131,6 @@ public class BookmarksPage extends HomeFragment {
mPinBookmarkListener = new PinBookmarkListener();
mList = (BookmarksListView) view.findViewById(R.id.bookmarks_list);
mList.setTag(HomePager.LIST_TAG_BOOKMARKS);
mList.setOnUrlOpenListener(listener);
mList.setHeaderDividersEnabled(false);
@ -125,6 +140,15 @@ public class BookmarksPage extends HomeFragment {
registerForContextMenu(mList);
registerForContextMenu(mTopBookmarks);
mBanner = (HomeBanner) view.findViewById(R.id.home_banner);
mList.setOnTouchListener(new OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
BookmarksPage.this.handleListTouchEvent(event);
return false;
}
});
}
@Override
@ -190,6 +214,60 @@ public class BookmarksPage extends HomeFragment {
}
}
private void handleListTouchEvent(MotionEvent event) {
// Ignore the event if the banner is hidden for this session.
if (mBanner.isDismissed()) {
return;
}
switch (event.getActionMasked()) {
case MotionEvent.ACTION_DOWN: {
mListTouchY = event.getRawY();
break;
}
case MotionEvent.ACTION_MOVE: {
// There is a chance that we won't receive ACTION_DOWN, if the touch event
// actually started on the Grid instead of the List. Treat this as first event.
if (mListTouchY == -1) {
mListTouchY = event.getRawY();
return;
}
final float curY = event.getRawY();
final float delta = mListTouchY - curY;
mSnapBannerToTop = (delta > 0.0f) ? false : true;
final float height = mBanner.getHeight();
float newTranslationY = ViewHelper.getTranslationY(mBanner) + delta;
// Clamp the values to be between 0 and height.
if (newTranslationY < 0.0f) {
newTranslationY = 0.0f;
} else if (newTranslationY > height) {
newTranslationY = height;
}
ViewHelper.setTranslationY(mBanner, newTranslationY);
mListTouchY = curY;
break;
}
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL: {
mListTouchY = -1;
final float y = ViewHelper.getTranslationY(mBanner);
final float height = mBanner.getHeight();
if (y > 0.0f && y < height) {
final PropertyAnimator animator = new PropertyAnimator(100);
animator.attach(mBanner, Property.TRANSLATION_Y, mSnapBannerToTop ? 0 : height);
animator.start();
}
break;
}
}
}
@Override
public void onCreateContextMenu(ContextMenu menu, View view, ContextMenuInfo menuInfo) {
if (menuInfo == null) {

View File

@ -882,7 +882,7 @@ public class BrowserSearch extends HomeFragment
requestFocus();
}
return false;
return super.onInterceptTouchEvent(event);
}
}
}

View File

@ -0,0 +1,51 @@
/* -*- 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.R;
import android.content.Context;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.ImageButton;
import android.widget.LinearLayout;
public class HomeBanner extends LinearLayout {
public HomeBanner(Context context) {
this(context, null);
}
public HomeBanner(Context context, AttributeSet attrs) {
super(context, attrs);
LayoutInflater.from(context).inflate(R.layout.home_banner, this);
}
@Override
public void onAttachedToWindow() {
super.onAttachedToWindow();
// Tapping on the close button will ensure that the banner is never
// showed again on this session.
final ImageButton closeButton = (ImageButton) findViewById(R.id.close);
// The drawable should have 50% opacity.
closeButton.getDrawable().setAlpha(127);
closeButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
HomeBanner.this.setVisibility(View.GONE);
}
});
}
public boolean isDismissed() {
return (getVisibility() == View.GONE);
}
}

View File

@ -99,6 +99,20 @@ public class HomeListView extends ListView
return mContextMenuInfo;
}
@Override
public void setOnItemClickListener(final AdapterView.OnItemClickListener listener) {
super.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
if (mShowTopDivider) {
position--;
}
listener.onItemClick(parent, view, position, id);
}
});
}
public OnUrlOpenListener getOnUrlOpenListener() {
return mUrlOpenListener;
}

View File

@ -0,0 +1,38 @@
<?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">
<item android:state_pressed="true">
<layer-list>
<item android:left="-2dp"
android:right="-2dp"
android:bottom="-2dp">
<shape android:shape="rectangle" >
<stroke android:width="2dp"
android:color="#FFE0E4E7" />
<solid android:color="#FFC5D0DA" />
</shape>
</item>
</layer-list>
</item>
<item>
<layer-list>
<item android:left="-2dp"
android:right="-2dp"
android:bottom="-2dp">
<shape android:shape="rectangle" >
<stroke android:width="2dp"
android:color="#FFE0E4E7" />
<solid android:color="@color/background_light" />
</shape>
</item>
</layer-list>
</item>
</selector>

View File

@ -0,0 +1,36 @@
<?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">
<ImageView android:id="@+id/icon"
android:layout_width="48dip"
android:layout_height="48dip"
android:layout_marginLeft="10dp"
android:layout_marginRight="10dp"
android:scaleType="centerInside"/>
<TextView android:id="@+id/text"
android:layout_width="0dip"
android:layout_height="fill_parent"
android:layout_weight="1"
android:paddingTop="7dp"
android:paddingBottom="7dp"
android:textAppearance="@style/TextAppearance.Widget.HomeBanner"
android:layout_gravity="bottom"
android:singleLine="false"
android:maxLines="3"
android:ellipsize="end"
android:gravity="center_vertical"/>
<ImageButton android:id="@+id/close"
android:layout_width="34dip"
android:layout_height="fill_parent"
android:background="@drawable/home_banner"
android:scaleType="center"
android:contentDescription="@string/close_tab"
android:src="@drawable/tab_close"/>
</merge>

View File

@ -3,7 +3,24 @@
- 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.BookmarksListView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/bookmarks_list"
android:layout_width="fill_parent"
android:layout_height="fill_parent"/>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<org.mozilla.gecko.home.BookmarksListView
android:id="@+id/bookmarks_list"
android:layout_width="fill_parent"
android:layout_height="fill_parent"/>
<org.mozilla.gecko.home.HomeBanner android:id="@+id/home_banner"
style="@style/Widget.HomeBanner"
android:layout_width="fill_parent"
android:layout_height="@dimen/home_banner_height"
android:background="@drawable/home_banner"
android:layout_gravity="bottom"
android:gravity="center_vertical"
android:visibility="gone"
android:clickable="true"
android:focusable="true"/>
</FrameLayout>

View File

@ -50,4 +50,15 @@
<item name="android:paddingTop">30dp</item>
</style>
<!--
The content of the banner should align with the Grid/List views
in BookmarksPage. BookmarksListView has a 120dp padding and
the TwoLinePageRows have a 50dp padding. Hence HomeBanner should
have 170dp padding.
-->
<style name="Widget.HomeBanner">
<item name="android:paddingLeft">170dp</item>
<item name="android:paddingRight">170dp</item>
</style>
</resources>

View File

@ -60,4 +60,9 @@
<item name="topDivider">true</item>
</style>
<style name="Widget.HomeBanner">
<item name="android:paddingLeft">32dp</item>
<item name="android:paddingRight">32dp</item>
</style>
</resources>

View File

@ -87,4 +87,7 @@
<!-- PageActionButtons dimensions -->
<dimen name="page_action_button_width">32dp</dimen>
<!-- Banner -->
<dimen name="home_banner_height">72dp</dimen>
</resources>

View File

@ -160,6 +160,8 @@
<style name="Widget.ReadingListView" parent="Widget.BookmarksListView"/>
<style name="Widget.HomeBanner"/>
<style name="Widget.Home" />
<style name="Widget.Home.HeaderItem">
@ -318,6 +320,10 @@
<item name="android:textColor">#00ACFF</item>
</style>
<style name="TextAppearance.Widget.HomeBanner" parent="TextAppearance.Small">
<item name="android:textColor">?android:attr/textColorHint</item>
</style>
<!-- BrowserToolbar -->
<style name="BrowserToolbar">
<item name="android:layout_width">fill_parent</item>

View File

@ -2563,6 +2563,16 @@
"n_buckets": 15,
"description": "Session restore: Days elapsed since the session was first started"
},
"FX_SESSION_RESTORE_TABSTATECACHE_HIT_RATE": {
"kind": "enumerated",
"n_values": 101,
"description": "Session restore: Percentage of tab state cache hits in all tab state cache accesses"
},
"FX_SESSION_RESTORE_TABSTATECACHE_CLEAR_RATIO": {
"kind": "enumerated",
"n_values": 101,
"description": "Session restore: Number of times the tab state cache has been cleared during a session divided by number of total accesses during the session (percentage)"
},
"INNERWINDOWS_WITH_MUTATION_LISTENERS": {
"kind": "boolean",
"description": "Deleted or to-be-reused innerwindow which has had mutation event listeners."

View File

@ -7,6 +7,8 @@
const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
Cu.import("resource://gre/modules/PageThumbs.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
const backgroundPageThumbsContent = {
@ -26,9 +28,14 @@ const backgroundPageThumbsContent = {
docShell.allowMedia = false;
docShell.allowPlugins = false;
docShell.allowContentRetargeting = false;
addMessageListener("BackgroundPageThumbs:capture",
this._onCapture.bind(this));
docShell.
QueryInterface(Ci.nsIInterfaceRequestor).
getInterface(Ci.nsIWebProgress).
addProgressListener(this, Ci.nsIWebProgress.NOTIFY_STATE_WINDOW);
},
get _webNav() {
@ -36,52 +43,64 @@ const backgroundPageThumbsContent = {
},
_onCapture: function (msg) {
this._webNav.stop(Ci.nsIWebNavigation.STOP_NETWORK);
if (this._onLoad)
removeEventListener("load", this._onLoad, true);
this._onLoad = function onLoad(event) {
if (event.target != content.document)
return;
let pageLoadTime = new Date() - loadDate;
removeEventListener("load", this._onLoad, true);
delete this._onLoad;
let canvas = PageThumbs._createCanvas(content);
let captureDate = new Date();
PageThumbs._captureToCanvas(content, canvas);
let captureTime = new Date() - captureDate;
let channel = docShell.currentDocumentChannel;
let isErrorResponse = PageThumbs._isChannelErrorResponse(channel);
let finalURL = this._webNav.currentURI.spec;
let fileReader = Cc["@mozilla.org/files/filereader;1"].
createInstance(Ci.nsIDOMFileReader);
fileReader.onloadend = function onArrayBufferLoad() {
sendAsyncMessage("BackgroundPageThumbs:didCapture", {
id: msg.json.id,
imageData: fileReader.result,
finalURL: finalURL,
telemetry: {
CAPTURE_PAGE_LOAD_TIME_MS: pageLoadTime,
CAPTURE_CANVAS_DRAW_TIME_MS: captureTime,
},
wasErrorResponse: isErrorResponse,
});
};
canvas.toBlob(blob => fileReader.readAsArrayBuffer(blob));
// Load about:blank to cause the captured window to be collected...
// eventually.
this._webNav.loadURI("about:blank", Ci.nsIWebNavigation.LOAD_FLAGS_NONE,
null, null, null);
}.bind(this);
addEventListener("load", this._onLoad, true);
this._webNav.loadURI(msg.json.url, Ci.nsIWebNavigation.LOAD_FLAGS_NONE,
this._webNav.loadURI(msg.json.url,
Ci.nsIWebNavigation.LOAD_FLAGS_STOP_CONTENT,
null, null, null);
let loadDate = new Date();
// If a page was already loading, onStateChange is synchronously called at
// this point by loadURI.
this._requestID = msg.json.id;
this._requestDate = new Date();
},
onStateChange: function (webProgress, req, flags, status) {
if (!webProgress.isTopLevel ||
!(flags & Ci.nsIWebProgressListener.STATE_STOP) ||
req.name == "about:blank")
return;
let requestID = this._requestID;
let pageLoadTime = new Date() - this._requestDate;
delete this._requestID;
let canvas = PageThumbs._createCanvas(content);
let captureDate = new Date();
PageThumbs._captureToCanvas(content, canvas);
let captureTime = new Date() - captureDate;
let channel = docShell.currentDocumentChannel;
let isErrorResponse = PageThumbs._isChannelErrorResponse(channel);
let finalURL = this._webNav.currentURI.spec;
let fileReader = Cc["@mozilla.org/files/filereader;1"].
createInstance(Ci.nsIDOMFileReader);
fileReader.onloadend = () => {
sendAsyncMessage("BackgroundPageThumbs:didCapture", {
id: requestID,
imageData: fileReader.result,
finalURL: finalURL,
wasErrorResponse: isErrorResponse,
telemetry: {
CAPTURE_PAGE_LOAD_TIME_MS: pageLoadTime,
CAPTURE_CANVAS_DRAW_TIME_MS: captureTime,
},
});
};
canvas.toBlob(blob => fileReader.readAsArrayBuffer(blob));
// If no other pages are loading, load about:blank to cause the captured
// window to be collected... eventually. Calling loadURI at this point
// trips an assertion in nsLoadGroup::Cancel, so do it on another stack.
Services.tm.mainThread.dispatch(() => {
if (!("_requestID" in this))
this._webNav.loadURI("about:blank",
Ci.nsIWebNavigation.LOAD_FLAGS_STOP_CONTENT,
null, null, null);
}, Ci.nsIEventTarget.DISPATCH_NORMAL);
},
QueryInterface: XPCOMUtils.generateQI([
Ci.nsIWebProgressListener,
Ci.nsISupportsWeakReference,
]),
};
backgroundPageThumbsContent.init();

View File

@ -54,7 +54,7 @@ let tests = [
let files = urls.map(fileForURL);
files.forEach(f => ok(!f.exists(), "Thumbnail should not be cached yet."));
urls.forEach(function (url) {
let isTimeoutTest = url.indexOf("?wait") >= 0;
let isTimeoutTest = url.indexOf("wait") >= 0;
imports.BackgroundPageThumbs.capture(url, {
timeout: isTimeoutTest ? 100 : 30000,
onDone: function onDone(capturedURL) {