Merge fx-team to m-c.

This commit is contained in:
Ryan VanderMeulen 2014-02-13 10:16:03 -05:00
commit 49f79aa23d
50 changed files with 598 additions and 115 deletions

View File

@ -195,8 +195,12 @@ let gFxAccounts = {
},
onMenuPanelCommand: function (event) {
if (event.originalTarget.hasAttribute("signedin")) {
let button = event.originalTarget;
if (button.hasAttribute("signedin")) {
this.openPreferences();
} else if (button.hasAttribute("failed")) {
this.openSignInAgainPage();
} else {
this.openAccountsPage();
}

View File

@ -174,13 +174,6 @@
this.setAttribute("viewtype", "main");
}
this._mainViewObserver.observe(this._mainView, {
attributes: true,
characterData: true,
childList: true,
subtree: true
});
this._shiftMainView();
]]></body>
</method>
@ -296,6 +289,14 @@
// every time the popup closes, which is why we have to set it each time.
this._panel.autoPosition = false;
this._syncContainerWithMainView();
this._mainViewObserver.observe(this._mainView, {
attributes: true,
characterData: true,
childList: true,
subtree: true
});
break;
case "popupshown":
this._setMaxHeight();
@ -304,6 +305,7 @@
this.removeAttribute("panelopen");
this._mainView.style.removeProperty("height");
this.showMainView();
this._mainViewObserver.disconnect();
break;
}
]]></body>

View File

@ -240,7 +240,11 @@ let DebuggerController = {
} else {
this._startDebuggingTab(startedDebugging.resolve);
const startedTracing = promise.defer();
this._startTracingTab(traceActor, startedTracing.resolve);
if (Prefs.tracerEnabled && traceActor) {
this._startTracingTab(traceActor, startedTracing.resolve);
} else {
startedTracing.resolve();
}
return promise.all([startedDebugging.promise, startedTracing.promise]);
}

View File

@ -214,6 +214,7 @@ support-files =
[browser_dbg_tracing-03.js]
[browser_dbg_tracing-04.js]
[browser_dbg_tracing-05.js]
[browser_dbg_tracing-06.js]
[browser_dbg_variables-view-01.js]
[browser_dbg_variables-view-02.js]
[browser_dbg_variables-view-03.js]

View File

@ -0,0 +1,39 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* Test that the tracer doesn't connect to the backend when tracing is disabled.
*/
const TAB_URL = EXAMPLE_URL + "doc_tracing-01.html";
const TRACER_PREF = "devtools.debugger.tracer";
let gTab, gDebuggee, gPanel, gDebugger;
let gOriginalPref = Services.prefs.getBoolPref(TRACER_PREF);
Services.prefs.setBoolPref(TRACER_PREF, false);
function test() {
initDebugger(TAB_URL).then(([aTab, aDebuggee, aPanel]) => {
gTab = aTab;
gDebuggee = aDebuggee;
gPanel = aPanel;
gDebugger = gPanel.panelWin;
waitForSourceShown(gPanel, "code_tracing-01.js")
.then(() => {
ok(!gDebugger.DebuggerController.traceClient, "Should not have a trace client");
closeDebuggerAndFinish(gPanel);
})
.then(null, aError => {
ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
});
});
}
registerCleanupFunction(function() {
gTab = null;
gDebuggee = null;
gPanel = null;
gDebugger = null;
Services.prefs.setBoolPref(TRACER_PREF, gOriginalPref);
});

View File

@ -640,12 +640,6 @@ tabmodalprompt:not([promptType="promptUserAndPass"]) .infoContainer {
.meta {
background-color: @panel_light_color@;
/* bug 969354
background-image: url("chrome://browser/skin/images/firefox-watermark.png");
background-repeat: no-repeat;
background-position: center center;
background-attachment: fixed;
*/
}
/* needs to observe the viewstate */

View File

@ -38,3 +38,19 @@
#start-container[viewstate="snapped"] .meta-section:not([expanded]) > richgrid {
visibility: collapse;
}
/* Watermark */
#startui-body::after {
content: '';
width: 256px;
height: 256px;
position: fixed;
left: 50%;
top: 50%;
margin-top: -128px;
margin-left: -128px;
z-index: -1;
background-image: url("chrome://browser/skin/images/firefox-watermark.png");
background-repeat: no-repeat;
background-position: center center;
}

View File

@ -697,9 +697,13 @@ toolbarbutton[sdk-button="true"][cui-areatype="toolbar"] > .toolbarbutton-icon {
#sync-button {
-moz-image-region: rect(0px 144px 24px 120px);
}
#sync-button[status="active"] {
list-style-image: url("chrome://browser/skin/sync-24-throbber.png");
-moz-image-region: rect(0px 24px 24px 0px);
#sync-button[cui-areatype="toolbar"][status="active"] {
list-style-image: url("chrome://browser/skin/syncProgress-toolbar.png");
-moz-image-region: rect(0px 18px 18px 0px);
}
#sync-button[cui-areatype="menu-panel"][status="active"] {
list-style-image: url("chrome://browser/skin/syncProgress-menuPanel.png");
-moz-image-region: rect(0px 32px 32px 0px);
}
#feed-button {

View File

@ -269,15 +269,15 @@ browser.jar:
skin/classic/browser/devtools/app-manager/noise.png (../shared/devtools/app-manager/images/noise.png)
skin/classic/browser/devtools/app-manager/default-app-icon.png (../shared/devtools/app-manager/images/default-app-icon.png)
#ifdef MOZ_SERVICES_SYNC
skin/classic/browser/sync-16-throbber.png
skin/classic/browser/sync-16.png
skin/classic/browser/sync-24-throbber.png
skin/classic/browser/sync-32.png
skin/classic/browser/sync-bg.png
skin/classic/browser/sync-128.png
skin/classic/browser/sync-desktopIcon.png
skin/classic/browser/sync-mobileIcon.png
skin/classic/browser/sync-notification-24.png
skin/classic/browser/syncProgress-menuPanel.png
skin/classic/browser/syncProgress-toolbar.png
skin/classic/browser/syncSetup.css
skin/classic/browser/syncCommon.css
skin/classic/browser/syncQuota.css

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 623 B

View File

@ -6,6 +6,7 @@
package org.mozilla.gecko;
import org.mozilla.gecko.SiteIdentity.SecurityMode;
import org.mozilla.gecko.db.BrowserContract.Bookmarks;
import org.mozilla.gecko.db.BrowserDB;
import org.mozilla.gecko.gfx.Layer;
import org.mozilla.gecko.util.ThreadUtils;
@ -418,9 +419,9 @@ public class Tab {
return;
}
mBookmark = BrowserDB.isBookmark(getContentResolver(), url);
mReadingListItem = BrowserDB.isReadingListItem(getContentResolver(), url);
final int flags = BrowserDB.getItemFlags(getContentResolver(), url);
mBookmark = (flags & Bookmarks.FLAG_BOOKMARK) > 0;
mReadingListItem = (flags & Bookmarks.FLAG_READING) > 0;
Tabs.getInstance().notifyListeners(Tab.this, Tabs.TabEvents.MENU_UPDATED);
}
});

View File

@ -56,6 +56,7 @@ public class Tabs implements GeckoEventListener {
private static final int LOAD_PROGRESS_START = 20;
private static final int LOAD_PROGRESS_LOCATION_CHANGE = 60;
private static final int LOAD_PROGRESS_LOADED = 80;
private static final int LOAD_PROGRESS_STOP = 100;
public static final int LOADURL_NONE = 0;
public static final int LOADURL_NEW_TAB = 1 << 0;
@ -459,6 +460,7 @@ public class Tabs implements GeckoEventListener {
notifyListeners(tab, Tabs.TabEvents.START);
} else if ((state & GeckoAppShell.WPL_STATE_STOP) != 0) {
tab.handleDocumentStop(message.getBoolean("success"));
tab.setLoadProgress(LOAD_PROGRESS_STOP);
notifyListeners(tab, Tabs.TabEvents.STOP);
}
}

View File

@ -143,6 +143,17 @@ public class BrowserContract {
public static final int TYPE_LIVEMARK = 3;
public static final int TYPE_QUERY = 4;
/*
* These values are returned by getItemFlags. They're not really
* exclusive to bookmarks, but there's no better place to put them.
*/
public static final int FLAG_SUCCESS = 1 << 1; // The query succeeded.
public static final int FLAG_BOOKMARK = 1 << 2;
public static final int FLAG_PINNED = 1 << 3;
public static final int FLAG_READING = 1 << 4;
public static final Uri FLAGS_URI = Uri.withAppendedPath(AUTHORITY_URI, "flags");
public static final Uri CONTENT_URI = Uri.withAppendedPath(AUTHORITY_URI, "bookmarks");
public static final Uri PARENTS_CONTENT_URI = Uri.withAppendedPath(CONTENT_URI, "parents");
// Hacky API for bulk-updating positions. Bug 728783.

View File

@ -41,8 +41,8 @@ public class BrowserDB {
@RobocopTarget
public Cursor filter(ContentResolver cr, CharSequence constraint, int limit);
// This should onlyl return frecent sites, BrowserDB.getTopSites will do the
// work to combine that list with the pinned sites list
// This should only return frecent sites. BrowserDB.getTopSites will do the
// work to combine that list with the pinned sites list.
public Cursor getTopSites(ContentResolver cr, int limit);
public void updateVisitedHistory(ContentResolver cr, String uri);
@ -78,6 +78,12 @@ public class BrowserDB {
public boolean isReadingListItem(ContentResolver cr, String uri);
/**
* Return a combination of fields about the provided URI
* in a single hit on the DB.
*/
public int getItemFlags(ContentResolver cr, String uri);
public String getUrlForKeyword(ContentResolver cr, String keyword);
@RobocopTarget
@ -232,6 +238,13 @@ public class BrowserDB {
return (sAreContentProvidersEnabled && sDb.isReadingListItem(cr, uri));
}
public static int getItemFlags(ContentResolver cr, String uri) {
if (!sAreContentProvidersEnabled) {
return 0;
}
return sDb.getItemFlags(cr, uri);
}
public static void addBookmark(ContentResolver cr, String title, String uri) {
sDb.addBookmark(cr, title, uri);
}

View File

@ -109,6 +109,8 @@ public class BrowserProvider extends ContentProvider {
static final String VIEW_HISTORY_WITH_FAVICONS = "history_with_favicons";
static final String VIEW_COMBINED_WITH_FAVICONS = "combined_with_favicons";
static final String VIEW_FLAGS = "flags";
// Bookmark matches
static final int BOOKMARKS = 100;
static final int BOOKMARKS_ID = 101;
@ -141,6 +143,8 @@ public class BrowserProvider extends ContentProvider {
static final int THUMBNAILS = 800;
static final int THUMBNAIL_ID = 801;
static final int FLAGS = 900;
static final String DEFAULT_BOOKMARKS_SORT_ORDER = Bookmarks.TYPE
+ " ASC, " + Bookmarks.POSITION + " ASC, " + Bookmarks._ID
+ " ASC";
@ -305,6 +309,8 @@ public class BrowserProvider extends ContentProvider {
// Search Suggest
URI_MATCHER.addURI(BrowserContract.AUTHORITY, SearchManager.SUGGEST_URI_PATH_QUERY + "/*", SEARCH_SUGGEST);
URI_MATCHER.addURI(BrowserContract.AUTHORITY, "flags", FLAGS);
map = new HashMap<String, String>();
map.put(SearchManager.SUGGEST_COLUMN_TEXT_1,
Combined.TITLE + " AS " + SearchManager.SUGGEST_COLUMN_TEXT_1);
@ -2154,6 +2160,9 @@ public class BrowserProvider extends ContentProvider {
case SEARCH_SUGGEST:
trace("URI is SEARCH_SUGGEST: " + uri);
return SearchManager.SUGGEST_MIME_TYPE;
case FLAGS:
trace("URI is FLAGS.");
return Bookmarks.CONTENT_ITEM_TYPE;
}
debug("URI has unrecognized type: " + uri);
@ -2492,6 +2501,40 @@ public class BrowserProvider extends ContentProvider {
SQLiteDatabase db = getReadableDatabase(uri);
final int match = URI_MATCHER.match(uri);
// The first selectionArgs value is the URI for which to query.
if (match == FLAGS) {
// We don't need the QB below for this.
//
// There are three possible kinds of bookmarks:
// * Regular bookmarks
// * Bookmarks whose parent is FIXED_READING_LIST_ID (reading list items)
// * Bookmarks whose parent is FIXED_PINNED_LIST_ID (pinned items).
//
// Although SQLite doesn't have an aggregate operator for bitwise-OR, we're
// using disjoint flags, so we can simply use SUM and DISTINCT to get the
// flags we need.
// We turn parents into flags according to the three kinds, above.
//
// When this query is extended to support queries across multiple tables, simply
// extend it to look like
//
// SELECT COALESCE((SELECT ...), 0) | COALESCE(...) | ...
final String query = "SELECT COALESCE(SUM(flag), 0) AS flags " +
"FROM ( SELECT DISTINCT CASE" +
" WHEN " + Bookmarks.PARENT + " = " + Bookmarks.FIXED_READING_LIST_ID +
" THEN " + Bookmarks.FLAG_READING +
" WHEN " + Bookmarks.PARENT + " = " + Bookmarks.FIXED_PINNED_LIST_ID +
" THEN " + Bookmarks.FLAG_PINNED +
" ELSE " + Bookmarks.FLAG_BOOKMARK +
" END flag " +
"FROM " + TABLE_BOOKMARKS + " WHERE " + Bookmarks.URL + " = ? " +
")";
return db.rawQuery(query, selectionArgs);
}
SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
String limit = uri.getQueryParameter(BrowserContract.PARAM_LIMIT);
String groupBy = null;

View File

@ -59,6 +59,7 @@ public class LocalBrowserDB implements BrowserDB.BrowserDBIface {
private final Uri mBookmarksUriWithProfile;
private final Uri mParentsUriWithProfile;
private final Uri mFlagsUriWithProfile;
private final Uri mHistoryUriWithProfile;
private final Uri mHistoryExpireUriWithProfile;
private final Uri mCombinedUriWithProfile;
@ -82,6 +83,7 @@ public class LocalBrowserDB implements BrowserDB.BrowserDBIface {
mBookmarksUriWithProfile = appendProfile(Bookmarks.CONTENT_URI);
mParentsUriWithProfile = appendProfile(Bookmarks.PARENTS_CONTENT_URI);
mFlagsUriWithProfile = appendProfile(Bookmarks.FLAGS_URI);
mHistoryUriWithProfile = appendProfile(History.CONTENT_URI);
mHistoryExpireUriWithProfile = appendProfile(History.CONTENT_OLD_URI);
mCombinedUriWithProfile = appendProfile(Combined.CONTENT_URI);
@ -471,7 +473,7 @@ public class LocalBrowserDB implements BrowserDB.BrowserDBIface {
@Override
public boolean isBookmark(ContentResolver cr, String uri) {
// This method is about normal bookmarks, not the Reading List
// This method is about normal bookmarks, not the Reading List.
Cursor c = null;
try {
c = cr.query(bookmarksUriWithLimit(1),
@ -517,6 +519,34 @@ public class LocalBrowserDB implements BrowserDB.BrowserDBIface {
return false;
}
/**
* For a given URI, we want to return a number of things:
*
* * Is this URI the URI of a bookmark?
* * ... a reading list item?
*
* This will expand as necessary to eliminate multiple consecutive queries.
*/
@Override
public int getItemFlags(ContentResolver cr, String uri) {
final Cursor c = cr.query(mFlagsUriWithProfile,
null,
null,
new String[] { uri },
null);
if (c == null) {
return 0;
}
try {
// This should never fail: it returns a single `flags` row.
c.moveToFirst();
return Bookmarks.FLAG_SUCCESS | c.getInt(0);
} finally {
c.close();
}
}
@Override
public String getUrlForKeyword(ContentResolver cr, String keyword) {
Cursor c = null;

View File

@ -130,8 +130,10 @@ class BookmarksListAdapter extends MultiTypeCursorAdapter {
* @return Whether the adapter successfully moved to a parent folder.
*/
public boolean moveToParentFolder() {
// If we're already at the root, we can't move to a parent folder
if (mParentStack.size() == 1) {
// If we're already at the root, we can't move to a parent folder.
// An empty parent stack here means we're still waiting for the
// initial list of bookmarks and can't go to a parent folder.
if (mParentStack.size() <= 1) {
return false;
}

View File

@ -106,8 +106,7 @@ public final class HomeConfig {
public enum Flags {
DEFAULT_PANEL,
DISABLED_PANEL,
DELETED_PANEL
DISABLED_PANEL
}
public PanelConfig(JSONObject json) throws JSONException, IllegalArgumentException {
@ -286,18 +285,6 @@ public final class HomeConfig {
}
}
public boolean isDeleted() {
return mFlags.contains(Flags.DELETED_PANEL);
}
public void setIsDeleted(boolean isDeleted) {
if (isDeleted) {
mFlags.add(Flags.DELETED_PANEL);
} else {
mFlags.remove(Flags.DELETED_PANEL);
}
}
public JSONObject toJSON() throws JSONException {
final JSONObject json = new JSONObject();
@ -596,12 +583,6 @@ public final class HomeConfig {
}
public void save(List<PanelConfig> panelConfigs) {
for (PanelConfig panelConfig : panelConfigs) {
if (panelConfig.isDeleted()) {
throw new IllegalArgumentException("Should never save a deleted PanelConfig: " + panelConfig.getId());
}
}
mBackend.save(panelConfigs);
}

View File

@ -25,6 +25,7 @@ import org.json.JSONObject;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Queue;
import java.util.Set;
import java.util.concurrent.ConcurrentLinkedQueue;
@ -38,13 +39,30 @@ public class HomeConfigInvalidator implements GeckoEventListener {
private static final String EVENT_HOMEPANELS_INSTALL = "HomePanels:Install";
private static final String EVENT_HOMEPANELS_REMOVE = "HomePanels:Remove";
private static final String EVENT_HOMEPANELS_REFRESH = "HomePanels:Refresh";
private static final String JSON_KEY_PANEL = "panel";
private enum ChangeType {
REMOVE,
INSTALL,
REFRESH
}
private static class ConfigChange {
private final ChangeType type;
private final PanelConfig target;
public ConfigChange(ChangeType type, PanelConfig target) {
this.type = type;
this.target = target;
}
}
private Context mContext;
private HomeConfig mHomeConfig;
private final ConcurrentLinkedQueue<PanelConfig> mPendingChanges = new ConcurrentLinkedQueue<PanelConfig>();
private final Queue<ConfigChange> mPendingChanges = new ConcurrentLinkedQueue<ConfigChange>();
private final Runnable mInvalidationRunnable = new InvalidationRunnable();
public static HomeConfigInvalidator getInstance() {
@ -57,6 +75,11 @@ public class HomeConfigInvalidator implements GeckoEventListener {
GeckoAppShell.getEventDispatcher().registerEventListener(EVENT_HOMEPANELS_INSTALL, this);
GeckoAppShell.getEventDispatcher().registerEventListener(EVENT_HOMEPANELS_REMOVE, this);
GeckoAppShell.getEventDispatcher().registerEventListener(EVENT_HOMEPANELS_REFRESH, this);
}
public void refreshAll() {
handlePanelRefresh(null);
}
@Override
@ -71,6 +94,9 @@ public class HomeConfigInvalidator implements GeckoEventListener {
} else if (event.equals(EVENT_HOMEPANELS_REMOVE)) {
Log.d(LOGTAG, EVENT_HOMEPANELS_REMOVE);
handlePanelRemove(panelConfig);
} else if (event.equals(EVENT_HOMEPANELS_REFRESH)) {
Log.d(LOGTAG, EVENT_HOMEPANELS_REFRESH);
handlePanelRefresh(panelConfig);
}
} catch (Exception e) {
Log.e(LOGTAG, "Failed to handle event " + event, e);
@ -81,7 +107,7 @@ public class HomeConfigInvalidator implements GeckoEventListener {
* Runs in the gecko thread.
*/
private void handlePanelInstall(PanelConfig panelConfig) {
mPendingChanges.offer(panelConfig);
mPendingChanges.offer(new ConfigChange(ChangeType.INSTALL, panelConfig));
Log.d(LOGTAG, "handlePanelInstall: " + mPendingChanges.size());
scheduleInvalidation();
@ -91,13 +117,25 @@ public class HomeConfigInvalidator implements GeckoEventListener {
* Runs in the gecko thread.
*/
private void handlePanelRemove(PanelConfig panelConfig) {
panelConfig.setIsDeleted(true);
mPendingChanges.offer(panelConfig);
mPendingChanges.offer(new ConfigChange(ChangeType.REMOVE, panelConfig));
Log.d(LOGTAG, "handlePanelRemove: " + mPendingChanges.size());
scheduleInvalidation();
}
/**
* Schedules a panel refresh in HomeConfig. Runs in the gecko thread.
*
* @param panelConfig the target PanelConfig instance or NULL to refresh
* all HomeConfig entries.
*/
private void handlePanelRefresh(PanelConfig panelConfig) {
mPendingChanges.offer(new ConfigChange(ChangeType.REFRESH, panelConfig));
Log.d(LOGTAG, "handlePanelRefresh: " + mPendingChanges.size());
scheduleInvalidation();
}
/**
* Runs in the gecko or main thread.
*/
@ -110,31 +148,63 @@ public class HomeConfigInvalidator implements GeckoEventListener {
Log.d(LOGTAG, "scheduleInvalidation: scheduled new invalidation");
}
/**
* Replace an element if a matching PanelConfig is
* present in the given list.
*/
private boolean replacePanelConfig(List<PanelConfig> panelConfigs, PanelConfig panelConfig) {
final int index = panelConfigs.indexOf(panelConfig);
if (index >= 0) {
panelConfigs.set(index, panelConfig);
Log.d(LOGTAG, "executePendingChanges: replaced position " + index + " with " + panelConfig.getId());
return true;
}
return false;
}
/**
* Runs in the background thread.
*/
private List<PanelConfig> executePendingChanges(List<PanelConfig> panelConfigs) {
while (!mPendingChanges.isEmpty()) {
final PanelConfig panelConfig = mPendingChanges.poll();
final String id = panelConfig.getId();
boolean shouldRefreshAll = false;
if (panelConfig.isDeleted()) {
if (panelConfigs.remove(panelConfig)) {
Log.d(LOGTAG, "executePendingChanges: removed panel " + id);
}
} else {
final int index = panelConfigs.indexOf(panelConfig);
if (index >= 0) {
panelConfigs.set(index, panelConfig);
Log.d(LOGTAG, "executePendingChanges: replaced position " + index + " with " + id);
} else {
panelConfigs.add(panelConfig);
Log.d(LOGTAG, "executePendingChanges: added panel " + id);
}
while (!mPendingChanges.isEmpty()) {
final ConfigChange pendingChange = mPendingChanges.poll();
final PanelConfig panelConfig = pendingChange.target;
switch (pendingChange.type) {
case REMOVE:
if (panelConfigs.remove(panelConfig)) {
Log.d(LOGTAG, "executePendingChanges: removed panel " + panelConfig.getId());
}
break;
case INSTALL:
if (!replacePanelConfig(panelConfigs, panelConfig)) {
panelConfigs.add(panelConfig);
Log.d(LOGTAG, "executePendingChanges: added panel " + panelConfig.getId());
}
break;
case REFRESH:
if (panelConfig != null) {
if (!replacePanelConfig(panelConfigs, panelConfig)) {
Log.w(LOGTAG, "Tried to refresh non-existing panel " + panelConfig.getId());
}
} else {
shouldRefreshAll = true;
}
break;
}
}
return executeRefresh(panelConfigs);
if (shouldRefreshAll) {
return executeRefresh(panelConfigs);
} else {
return panelConfigs;
}
}
/**

View File

@ -318,10 +318,11 @@ public class HomePager extends ViewPager {
setAdapter(adapter);
// Use the default panel as defined in the HomePager's configuration
// if the initial panel wasn't explicitly set by the show() caller.
if (mInitialPanelId != null) {
// XXX: Handle the case where the desired panel isn't currently in the adapter (bug 949178)
setCurrentItem(adapter.getItemPosition(mInitialPanelId), false);
// if the initial panel wasn't explicitly set by the show() caller,
// or if the initial panel is not found in the adapter.
final int itemPosition = (mInitialPanelId == null) ? -1 : adapter.getItemPosition(mInitialPanelId);
if (itemPosition > -1) {
setCurrentItem(itemPosition, false);
mInitialPanelId = null;
} else {
for (int i = 0; i < count; i++) {

View File

@ -175,6 +175,15 @@ class PinSiteDialog extends DialogFragment {
filter("");
}
@Override
public void onDestroyView() {
super.onDestroyView();
// Discard any additional site selection as the dialog
// is getting destroyed (see bug 935542).
setOnSiteSelectedListener(null);
}
public void setSearchTerm(String searchTerm) {
mSearchTerm = searchTerm;
}

View File

@ -449,7 +449,7 @@ public class TopSitesPanel extends HomeFragment {
public void onEditPinnedSite(int position, String searchTerm) {
mPosition = position;
final FragmentManager manager = getActivity().getSupportFragmentManager();
final FragmentManager manager = getChildFragmentManager();
PinSiteDialog dialog = (PinSiteDialog) manager.findFragmentByTag(TAG_PIN_SITE);
if (dialog == null) {
dialog = PinSiteDialog.newInstance();

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 601 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 283 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 283 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.0 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.5 KiB

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.5 KiB

After

Width:  |  Height:  |  Size: 569 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 213 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 213 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 KiB

After

Width:  |  Height:  |  Size: 901 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.8 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 603 B

After

Width:  |  Height:  |  Size: 421 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 206 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 213 B

View File

@ -475,13 +475,13 @@ public class BrowserToolbar extends GeckoRelativeLayout
case LOCATION_CHANGE:
case LOAD_ERROR:
case LOADED:
case STOP:
flags.add(UpdateFlags.PROGRESS);
if (mProgressBar.getVisibility() == View.VISIBLE) {
mProgressBar.animateProgress(tab.getLoadProgress());
}
break;
case STOP:
case SELECTED:
flags.add(UpdateFlags.PROGRESS);
updateProgressVisibility();

View File

@ -35,8 +35,9 @@ import android.view.View;
* perceived performance.
*/
public class ToolbarProgressView extends ImageView {
public static final int MAX_PROGRESS = 10000;
private static final int MSG_UPDATE = 42;
private static final int MAX_PROGRESS = 10000;
private static final int MSG_UPDATE = 0;
private static final int MSG_HIDE = 1;
private static final int STEPS = 10;
private static final int DELAY = 40;
@ -68,15 +69,23 @@ public class ToolbarProgressView extends ImageView {
mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
if (msg.what == MSG_UPDATE) {
final int progress = Math.min(mTargetProgress, mCurrentProgress + mIncrement);
mCurrentProgress = progress;
switch (msg.what) {
case MSG_UPDATE:
mCurrentProgress = Math.min(mTargetProgress, mCurrentProgress + mIncrement);
updateBounds();
updateBounds();
if (progress < mTargetProgress) {
sendMessageDelayed(mHandler.obtainMessage(MSG_UPDATE), DELAY);
}
if (mCurrentProgress < mTargetProgress) {
final int delay = (mTargetProgress < MAX_PROGRESS) ? DELAY : DELAY / 4;
sendMessageDelayed(mHandler.obtainMessage(msg.what), delay);
} else if (mCurrentProgress == MAX_PROGRESS) {
sendMessageDelayed(mHandler.obtainMessage(MSG_HIDE), DELAY);
}
break;
case MSG_HIDE:
setVisibility(View.GONE);
break;
}
}
@ -107,7 +116,7 @@ public class ToolbarProgressView extends ImageView {
mCurrentProgress = mTargetProgress = getAbsoluteProgress(progressPercentage);
updateBounds();
mHandler.removeMessages(MSG_UPDATE);
clearMessages();
}
/**
@ -118,18 +127,26 @@ public class ToolbarProgressView extends ImageView {
*/
void animateProgress(int progressPercentage) {
final int absoluteProgress = getAbsoluteProgress(progressPercentage);
if (absoluteProgress == mTargetProgress) {
if (absoluteProgress <= mTargetProgress) {
// After we manually click stop, we can still receive page load
// events (e.g., DOMContentLoaded). Updating for other updates
// after a STOP event can freeze the progress bar, so guard against
// that here.
return;
}
mCurrentProgress = mTargetProgress;
mTargetProgress = absoluteProgress;
mIncrement = (mTargetProgress - mCurrentProgress) / STEPS;
mHandler.removeMessages(MSG_UPDATE);
clearMessages();
mHandler.sendEmptyMessage(MSG_UPDATE);
}
private void clearMessages() {
mHandler.removeMessages(MSG_UPDATE);
mHandler.removeMessages(MSG_HIDE);
}
private int getAbsoluteProgress(int progressPercentage) {
if (progressPercentage < 0) {
return 0;

View File

@ -7,6 +7,9 @@ let Ci = Components.interfaces, Cc = Components.classes, Cu = Components.utils;
Cu.import("resource://gre/modules/Services.jsm")
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
// Panel ID defined in HomeConfig.java.
const READING_LIST_PANEL_ID = "20f4549a-64ad-4c32-93e4-1dcef792733b";
XPCOMUtils.defineLazyGetter(window, "gChromeWin", function ()
window.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIWebNavigation)
@ -366,7 +369,7 @@ AboutReader.prototype = {
if (!this._article || this._readingListCount < 1)
return;
gChromeWin.BrowserApp.loadURI("about:home?page=reading_list");
gChromeWin.BrowserApp.loadURI("about:home?page=" + READING_LIST_PANEL_ID);
},
_onShare: function Reader_onShare() {

View File

@ -163,6 +163,12 @@ let HomePanels = Object.freeze({
GRID: "grid"
}),
// Valid actions for a panel.
Action: Object.freeze({
INSTALL: "install",
REFRESH: "refresh"
}),
// Holds the currrent set of registered panels.
_panels: {},
@ -202,8 +208,11 @@ let HomePanels = Object.freeze({
throw "Home.panels: Can't create a home panel without an id and title!";
}
// Bail if the panel already exists
if (panel.id in this._panels) {
let action = options.action;
// Bail if the panel already exists, except when we're refreshing
// an existing panel instance.
if (panel.id in this._panels && action != this.Action.REFRESH) {
throw "Home.panels: Panel already exists: id = " + panel.id;
}
@ -223,9 +232,24 @@ let HomePanels = Object.freeze({
this._panels[panel.id] = panel;
if (options.autoInstall) {
if (action) {
let messageType;
switch(action) {
case this.Action.INSTALL:
messageType = "HomePanels:Install";
break;
case this.Action.REFRESH:
messageType = "HomePanels:Refresh";
break;
default:
throw "Home.panels: Invalid action for panel: panel.id = " + panel.id + ", action = " + action;
}
sendMessageToJava({
type: "HomePanels:Install",
type: messageType,
panel: this._panelToJSON(panel)
});
}

View File

@ -717,8 +717,14 @@ add_test(function test_timeout() {
do_check_eq(error.result, Cr.NS_ERROR_NET_TIMEOUT);
do_check_eq(this.status, this.ABORTED);
_("Closing connection.");
server_connection.close();
// server_connection is undefined on the Android emulator for reasons
// unknown. Yet, we still get here. If this test is refactored, we should
// investigate the reason why the above callback is behaving differently.
if (server_connection) {
_("Closing connection.");
server_connection.close();
}
_("Shutting down server.");
server.stop(run_next_test);
});

View File

@ -54,6 +54,11 @@ HMAC_EVENT_INTERVAL: 600000,
// How long to wait between sync attempts if the Master Password is locked.
MASTER_PASSWORD_LOCKED_RETRY_INTERVAL: 15 * 60 * 1000, // 15 minutes
// How long to initially wait between sync attempts if the identity manager is
// not ready. As we expect this to become ready relatively quickly, we retry
// in (IDENTITY_NOT_READY_RETRY_INTERVAL * num_failures) seconds.
IDENTITY_NOT_READY_RETRY_INTERVAL: 5 * 1000, // 5 seconds
// Separate from the ID fetch batch size to allow tuning for mobile.
MOBILE_BATCH_SIZE: 50,

View File

@ -30,6 +30,8 @@ SyncScheduler.prototype = {
LOGIN_FAILED_INVALID_PASSPHRASE,
LOGIN_FAILED_LOGIN_REJECTED],
_loginNotReadyCounter: 0,
/**
* The nsITimer object that schedules the next sync. See scheduleNextSync().
*/
@ -113,6 +115,10 @@ SyncScheduler.prototype = {
// we'll handle that later
Status.resetBackoff();
// Reset the loginNotReady counter, just in-case the user signs in
// as another user and re-hits the not-ready state.
this._loginNotReadyCounter = 0;
this.globalScore = 0;
break;
case "weave:service:sync:finish":
@ -155,6 +161,13 @@ SyncScheduler.prototype = {
this._log.debug("Couldn't log in: master password is locked.");
this._log.trace("Scheduling a sync at MASTER_PASSWORD_LOCKED_RETRY_INTERVAL");
this.scheduleAtInterval(MASTER_PASSWORD_LOCKED_RETRY_INTERVAL);
} else if (Status.login == LOGIN_FAILED_NOT_READY) {
this._loginNotReadyCounter++;
this._log.debug("Couldn't log in: identity not ready.");
this._log.trace("Scheduling a sync at IDENTITY_NOT_READY_RETRY_INTERVAL * " +
this._loginNotReadyCounter);
this.scheduleAtInterval(IDENTITY_NOT_READY_RETRY_INTERVAL *
this._loginNotReadyCounter);
} else if (this._fatalLoginStatus.indexOf(Status.login) == -1) {
// Not a fatal login error, just an intermittent network or server
// issue. Keep on syncin'.

View File

@ -30,6 +30,13 @@ const AGGREGATE_STARTUP_DELAY_MS = 57000;
const MILLISECONDS_IN_DAY = 24 * 60 * 60 * 1000;
// Converts Date to days since UNIX epoch.
// This was copied from /services/metrics.storage.jsm. The implementation
// does not account for leap seconds.
function dateToDays(date) {
return Math.floor(date.getTime() / MILLISECONDS_IN_DAY);
}
/**
* A gateway to crash-related data.
@ -520,6 +527,17 @@ let gCrashManager;
* When metadata is updated, the caller must explicitly persist the changes
* to disk. This prevents excessive I/O during updates.
*
* The store has a mechanism for ensuring it doesn't grow too large. A ceiling
* is placed on the number of daily events that can occur for events that can
* occur with relatively high frequency, notably plugin crashes and hangs
* (plugins can enter cycles where they repeatedly crash). If we've reached
* the high water mark and new data arrives, it's silently dropped.
* However, the count of actual events is always preserved. This allows
* us to report on the severity of problems beyond the storage threshold.
*
* Main process crashes are excluded from limits because they are both
* important and should be rare.
*
* @param storeDir (string)
* Directory the store should be located in.
* @param telemetrySizeKey (string)
@ -534,25 +552,41 @@ function CrashStore(storeDir, telemetrySizeKey) {
// Holds the read data from disk.
this._data = null;
// Maps days since UNIX epoch to a Map of event types to counts.
// This data structure is populated when the JSON file is loaded
// and is also updated when new events are added.
this._countsByDay = new Map();
}
CrashStore.prototype = Object.freeze({
// A crash that occurred in the main process.
TYPE_MAIN_CRASH: "main-crash",
// A crash in a plugin process.
TYPE_PLUGIN_CRASH: "plugin-crash",
// A hang in a plugin process.
TYPE_PLUGIN_HANG: "plugin-hang",
// Maximum number of events to store per day. This establishes a
// ceiling on the per-type/per-day records that will be stored.
HIGH_WATER_DAILY_THRESHOLD: 100,
/**
* Load data from disk.
*
* @return Promise<null>
* @return Promise
*/
load: function () {
return Task.spawn(function* () {
// Loading replaces data. So reset data structures.
this._data = {
v: 1,
crashes: new Map(),
corruptDate: null,
};
this._countsByDay = new Map();
try {
let decoder = new TextDecoder();
@ -563,10 +597,42 @@ CrashStore.prototype = Object.freeze({
this._data.corruptDate = new Date(data.corruptDate);
}
// actualCounts is used to validate that the derived counts by
// days stored in the payload matches up to actual data.
let actualCounts = new Map();
for (let id in data.crashes) {
let crash = data.crashes[id];
let denormalized = this._denormalize(crash);
this._data.crashes.set(id, denormalized);
let key = dateToDays(denormalized.crashDate) + "-" + denormalized.type;
actualCounts.set(key, (actualCounts.get(key) || 0) + 1);
}
// The validation in this loop is arguably not necessary. We perform
// it as a defense against unknown bugs.
for (let dayKey in data.countsByDay) {
let day = parseInt(dayKey, 10);
for (let type in data.countsByDay[day]) {
this._ensureCountsForDay(day);
let count = data.countsByDay[day][type];
let key = day + "-" + type;
// If the payload says we have data for a given day but we
// don't, the payload is wrong. Ignore it.
if (!actualCounts.has(key)) {
continue;
}
// If we encountered more data in the payload than what the
// data structure says, use the proper value.
count = Math.max(count, actualCounts.get(key));
this._countsByDay.get(day).set(type, count);
}
}
} catch (ex if ex instanceof OS.File.Error && ex.becauseNoSuchFile) {
// Missing files (first use) are allowed.
@ -594,8 +660,22 @@ CrashStore.prototype = Object.freeze({
}
let normalized = {
// The version should be incremented whenever the format
// changes.
v: 1,
// Maps crash IDs to objects defining the crash.
crashes: {},
// Maps days since UNIX epoch to objects mapping event types to
// counts. This is a mirror of this._countsByDay. e.g.
// {
// 15000: {
// "main-crash": 2,
// "plugin-crash": 1
// }
// }
countsByDay: {},
// When the store was last corrupted.
corruptDate: null,
};
@ -608,6 +688,13 @@ CrashStore.prototype = Object.freeze({
normalized.crashes[id] = c;
}
for (let [day, m] of this._countsByDay) {
normalized.countsByDay[day] = {};
for (let [type, count] of m) {
normalized.countsByDay[day][type] = count;
}
}
let encoder = new TextEncoder();
let data = encoder.encode(JSON.stringify(normalized));
let size = yield OS.File.writeAtomic(this._storePath, data, {
@ -729,16 +816,51 @@ CrashStore.prototype = Object.freeze({
return null;
},
_ensureCrashRecord: function (id) {
_ensureCountsForDay: function (day) {
if (!this._countsByDay.has(day)) {
this._countsByDay.set(day, new Map());
}
},
/**
* Ensure the crash record is present in storage.
*
* Returns the crash record if we're allowed to store it or null
* if we've hit the high water mark.
*
* @param id
* (string) The crash ID.
* @param type
* (string) One of the this.TYPE_* constants describing the crash type.
* @param date
* (Date) When this crash occurred.
*
* @return null | object crash record
*/
_ensureCrashRecord: function (id, type, date) {
let day = dateToDays(date);
this._ensureCountsForDay(day);
let count = (this._countsByDay.get(day).get(type) || 0) + 1;
this._countsByDay.get(day).set(type, count);
if (count > this.HIGH_WATER_DAILY_THRESHOLD && type != this.TYPE_MAIN_CRASH) {
return null;
}
if (!this._data.crashes.has(id)) {
this._data.crashes.set(id, {
id: id,
type: null,
crashDate: null,
type: type,
crashDate: date,
});
}
return this._data.crashes.get(id);
let crash = this._data.crashes.get(id);
crash.type = type;
crash.date = date;
return crash;
},
/**
@ -748,9 +870,7 @@ CrashStore.prototype = Object.freeze({
* @param date (Date) When the crash occurred.
*/
addMainProcessCrash: function (id, date) {
let r = this._ensureCrashRecord(id);
r.type = this.TYPE_MAIN_CRASH;
r.crashDate = date;
this._ensureCrashRecord(id, this.TYPE_MAIN_CRASH, date);
},
/**
@ -760,9 +880,7 @@ CrashStore.prototype = Object.freeze({
* @param date (Date) When the crash occurred.
*/
addPluginCrash: function (id, date) {
let r = this._ensureCrashRecord(id);
r.type = this.TYPE_PLUGIN_CRASH;
r.crashDate = date;
this._ensureCrashRecord(id, this.TYPE_PLUGIN_CRASH, date);
},
/**
@ -772,9 +890,7 @@ CrashStore.prototype = Object.freeze({
* @param date (Date) When the hang was reported.
*/
addPluginHang: function (id, date) {
let r = this._ensureCrashRecord(id);
r.type = this.TYPE_PLUGIN_HANG;
r.crashDate = date;
this._ensureCrashRecord(id, this.TYPE_PLUGIN_HANG, date);
},
get mainProcessCrashes() {
@ -847,6 +963,10 @@ CrashRecord.prototype = Object.freeze({
return this._o.crashDate;
},
get oldestDate() {
return this._o.crashDate;
},
get type() {
return this._o.type;
},

View File

@ -155,9 +155,8 @@ crash data grows. As new data is accumulated, we need to read and write
an entire file to make small updates. LZ4 compression helps reduce I/O.
But, there is a potential for unbounded file growth. We establish a
limit for the max age of records. Anything older than that limit is
pruned. Future patches will also limit the maximum number of records. This
will establish a hard limit on the size of the file, at least in terms of
crashes.
Care must be taken when new crash data is recorded, as this will increase
the size of the file and make I/O a larger concern.
pruned. We also establish a daily limit on the number of crashes we will
store. All crashes beyond the first N in a day have no payload and are
only recorded by the presence of a count. This count ensures we can
distinguish between ``N`` and ``100 * N``, which are very different
values!

View File

@ -258,3 +258,24 @@ add_task(function* test_plugin_hang_event_file() {
count = yield m.aggregateEventsFiles();
Assert.equal(count, 0);
});
// Excessive amounts of files should be processed properly.
add_task(function* test_high_water_mark() {
let m = yield getManager();
let store = yield m._getStore();
for (let i = 0; i < store.HIGH_WATER_DAILY_THRESHOLD + 1; i++) {
yield m.createEventsFile("m" + i, "crash.main.1", DUMMY_DATE, "m" + i);
yield m.createEventsFile("pc" + i, "crash.plugin.1", DUMMY_DATE, "pc" + i);
yield m.createEventsFile("ph" + i, "hang.plugin.1", DUMMY_DATE, "ph" + i);
}
let count = yield m.aggregateEventsFiles();
Assert.equal(count, 3 * bsp.CrashStore.prototype.HIGH_WATER_DAILY_THRESHOLD + 3);
// Need to fetch again in case the first one was garbage collected.
store = yield m._getStore();
// +1 is for preserved main process crash.
Assert.equal(store.crashesCount, 3 * store.HIGH_WATER_DAILY_THRESHOLD + 1);
});

View File

@ -181,3 +181,51 @@ add_task(function* test_add_mixed_types() {
Assert.equal(s.pluginCrashes.length, 1);
Assert.equal(s.pluginHangs.length, 1);
});
// Crashes added beyond the high water mark behave properly.
add_task(function* test_high_water() {
let s = yield getStore();
let d1 = new Date(2014, 0, 1, 0, 0, 0);
let d2 = new Date(2014, 0, 2, 0, 0, 0);
for (let i = 0; i < s.HIGH_WATER_DAILY_THRESHOLD + 1; i++) {
s.addMainProcessCrash("m1" + i, d1);
s.addMainProcessCrash("m2" + i, d2);
s.addPluginCrash("pc1" + i, d1);
s.addPluginCrash("pc2" + i, d2);
s.addPluginHang("ph1" + i, d1);
s.addPluginHang("ph2" + i, d2);
}
// We preserve main process crashes. Plugin crashes and hangs beyond should
// be discarded.
Assert.equal(s.crashesCount, 6 * s.HIGH_WATER_DAILY_THRESHOLD + 2);
Assert.equal(s.mainProcessCrashes.length, 2 * s.HIGH_WATER_DAILY_THRESHOLD + 2);
Assert.equal(s.pluginCrashes.length, 2 * s.HIGH_WATER_DAILY_THRESHOLD);
Assert.equal(s.pluginHangs.length, 2 * s.HIGH_WATER_DAILY_THRESHOLD);
// But raw counts should be preserved.
let day1 = bsp.dateToDays(d1);
let day2 = bsp.dateToDays(d2);
Assert.ok(s._countsByDay.has(day1));
Assert.ok(s._countsByDay.has(day2));
Assert.equal(s._countsByDay.get(day1).get(s.TYPE_MAIN_CRASH),
s.HIGH_WATER_DAILY_THRESHOLD + 1);
Assert.equal(s._countsByDay.get(day1).get(s.TYPE_PLUGIN_CRASH),
s.HIGH_WATER_DAILY_THRESHOLD + 1);
Assert.equal(s._countsByDay.get(day1).get(s.TYPE_PLUGIN_HANG),
s.HIGH_WATER_DAILY_THRESHOLD + 1);
yield s.save();
yield s.load();
Assert.ok(s._countsByDay.has(day1));
Assert.ok(s._countsByDay.has(day2));
Assert.equal(s._countsByDay.get(day1).get(s.TYPE_MAIN_CRASH),
s.HIGH_WATER_DAILY_THRESHOLD + 1);
Assert.equal(s._countsByDay.get(day1).get(s.TYPE_PLUGIN_CRASH),
s.HIGH_WATER_DAILY_THRESHOLD + 1);
Assert.equal(s._countsByDay.get(day1).get(s.TYPE_PLUGIN_HANG),
s.HIGH_WATER_DAILY_THRESHOLD + 1);
});