This commit is contained in:
Wes Kocher 2013-09-18 18:00:34 -07:00
Родитель 2360c584db a5064fa8a6
Коммит 4b896261ba
45 изменённых файлов: 883 добавлений и 490 удалений

Просмотреть файл

@ -1119,18 +1119,25 @@ var gBrowserInit = {
// If the user manually opens the download manager before the timeout, the
// downloads will start right away, and getting the service again won't hurt.
setTimeout(function() {
let DownloadsCommon =
Cu.import("resource:///modules/DownloadsCommon.jsm", {}).DownloadsCommon;
if (DownloadsCommon.useJSTransfer) {
// Open the data link without initalizing nsIDownloadManager.
DownloadsCommon.initializeAllDataLinks();
} else {
// Initalizing nsIDownloadManager will trigger the data link.
Services.downloads;
try {
let DownloadsCommon =
Cu.import("resource:///modules/DownloadsCommon.jsm", {}).DownloadsCommon;
if (DownloadsCommon.useJSTransfer) {
// Open the data link without initalizing nsIDownloadManager.
DownloadsCommon.initializeAllDataLinks();
let DownloadsTaskbar =
Cu.import("resource:///modules/DownloadsTaskbar.jsm", {}).DownloadsTaskbar;
DownloadsTaskbar.registerIndicator(window);
} else {
// Initalizing nsIDownloadManager will trigger the data link.
Services.downloads;
let DownloadTaskbarProgress =
Cu.import("resource://gre/modules/DownloadTaskbarProgress.jsm", {}).DownloadTaskbarProgress;
DownloadTaskbarProgress.onBrowserWindowLoad(window);
}
} catch (ex) {
Cu.reportError(ex);
}
let DownloadTaskbarProgress =
Cu.import("resource://gre/modules/DownloadTaskbarProgress.jsm", {}).DownloadTaskbarProgress;
DownloadTaskbarProgress.onBrowserWindowLoad(window);
}, 10000);
// The object handling the downloads indicator is also initialized here in the

Просмотреть файл

@ -0,0 +1,180 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=2 et sw=2 tw=80 filetype=javascript: */
/* 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/. */
/**
* Handles the download progress indicator in the taskbar.
*/
"use strict";
this.EXPORTED_SYMBOLS = [
"DownloadsTaskbar",
];
////////////////////////////////////////////////////////////////////////////////
//// Globals
const Cc = Components.classes;
const Ci = Components.interfaces;
const Cu = Components.utils;
const Cr = Components.results;
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Downloads",
"resource://gre/modules/Downloads.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "RecentWindow",
"resource:///modules/RecentWindow.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Services",
"resource://gre/modules/Services.jsm");
XPCOMUtils.defineLazyGetter(this, "gWinTaskbar", function () {
if (!("@mozilla.org/windows-taskbar;1" in Cc)) {
return null;
}
let winTaskbar = Cc["@mozilla.org/windows-taskbar;1"]
.getService(Ci.nsIWinTaskbar);
return winTaskbar.available && winTaskbar;
});
XPCOMUtils.defineLazyGetter(this, "gMacTaskbarProgress", function () {
return ("@mozilla.org/widget/macdocksupport;1" in Cc) &&
Cc["@mozilla.org/widget/macdocksupport;1"]
.getService(Ci.nsITaskbarProgress);
});
////////////////////////////////////////////////////////////////////////////////
//// DownloadsTaskbar
/**
* Handles the download progress indicator in the taskbar.
*/
this.DownloadsTaskbar = {
/**
* Underlying DownloadSummary providing the aggregate download information, or
* null if the indicator has never been initialized.
*/
_summary: null,
/**
* nsITaskbarProgress object to which download information is dispatched.
* This can be null if the indicator has never been initialized or if the
* indicator is currently hidden on Windows.
*/
_taskbarProgress: null,
/**
* This method is called after a new browser window is opened, and ensures
* that the download progress indicator is displayed in the taskbar.
*
* On Windows, the indicator is attached to the first browser window that
* calls this method. When the window is closed, the indicator is moved to
* another browser window, if available, in no particular order. When there
* are no browser windows visible, the indicator is hidden.
*
* On Mac OS X, the indicator is initialized globally when this method is
* called for the first time. Subsequent calls have no effect.
*
* @param aBrowserWindow
* nsIDOMWindow object of the newly opened browser window to which the
* indicator may be attached.
*/
registerIndicator: function (aBrowserWindow)
{
if (!this._taskbarProgress) {
if (gMacTaskbarProgress) {
// On Mac OS X, we have to register the global indicator only once.
this._taskbarProgress = gMacTaskbarProgress;
// Free the XPCOM reference on shutdown, to prevent detecting a leak.
Services.obs.addObserver(() => {
this._taskbarProgress = null;
gMacTaskbarProgress = null;
}, "quit-application-granted", false);
} else if (gWinTaskbar) {
// On Windows, the indicator is currently hidden because we have no
// previous browser window, thus we should attach the indicator now.
this._attachIndicator(aBrowserWindow);
} else {
// The taskbar indicator is not available on this platform.
return;
}
}
// Ensure that the DownloadSummary object will be created asynchronously.
if (!this._summary) {
Downloads.getSummary(Downloads.ALL).then(summary => {
// In case the method is re-entered, we simply ignore redundant
// invocations of the callback, instead of keeping separate state.
if (this._summary) {
return;
}
this._summary = summary;
return this._summary.addView(this);
}).then(null, Cu.reportError);
}
},
/**
* On Windows, attaches the taskbar indicator to the specified browser window.
*/
_attachIndicator: function (aWindow)
{
// Activate the indicator on the specified window.
let docShell = aWindow.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIWebNavigation)
.QueryInterface(Ci.nsIDocShellTreeItem).treeOwner
.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIXULWindow).docShell;
this._taskbarProgress = gWinTaskbar.getTaskbarProgress(docShell);
// If the DownloadSummary object has already been created, we should update
// the state of the new indicator, otherwise it will be updated as soon as
// the DownloadSummary view is registered.
if (this._summary) {
this.onSummaryChanged();
}
aWindow.addEventListener("unload", () => {
// Locate another browser window, excluding the one being closed.
let browserWindow = RecentWindow.getMostRecentBrowserWindow();
if (browserWindow) {
// Move the progress indicator to the other browser window.
this._attachIndicator(browserWindow);
} else {
// The last browser window has been closed. We remove the reference to
// the taskbar progress object so that the indicator will be registered
// again on the next browser window that is opened.
this._taskbarProgress = null;
}
}, false);
},
//////////////////////////////////////////////////////////////////////////////
//// DownloadSummary view
onSummaryChanged: function ()
{
// If the last browser window has been closed, we have no indicator anymore.
if (!this._taskbarProgress) {
return;
}
if (this._summary.allHaveStopped || this._summary.progressTotalBytes == 0) {
this._taskbarProgress.setProgressState(
Ci.nsITaskbarProgress.STATE_NO_PROGRESS, 0, 0);
} else {
// For a brief moment before completion, some download components may
// report more transferred bytes than the total number of bytes. Thus,
// ensure that we never break the expectations of the progress indicator.
let progressCurrentBytes = Math.min(this._summary.progressTotalBytes,
this._summary.progressCurrentBytes);
this._taskbarProgress.setProgressState(
Ci.nsITaskbarProgress.STATE_NORMAL,
progressCurrentBytes,
this._summary.progressTotalBytes);
}
},
};

Просмотреть файл

@ -13,5 +13,6 @@ EXTRA_COMPONENTS += [
EXTRA_JS_MODULES += [
'DownloadsCommon.jsm',
'DownloadsLogger.jsm',
'DownloadsTaskbar.jsm',
]

Просмотреть файл

@ -215,34 +215,27 @@
accesskey="&newWindowsAsTabs.accesskey;"
preference="browser.link.open_newwindow"
onsyncfrompreference="return gMainPane.readLinkTarget();"
onsynctopreference="return gMainPane.writeLinkTarget();"
class="indent"/>
onsynctopreference="return gMainPane.writeLinkTarget();"/>
<checkbox id="warnCloseMultiple" label="&warnCloseMultipleTabs.label;"
accesskey="&warnCloseMultipleTabs.accesskey;"
preference="browser.tabs.warnOnClose"
class="indent"/>
preference="browser.tabs.warnOnClose"/>
<checkbox id="warnOpenMany" label="&warnOpenManyTabs.label;"
accesskey="&warnOpenManyTabs.accesskey;"
preference="browser.tabs.warnOnOpen"
class="indent"/>
preference="browser.tabs.warnOnOpen"/>
<checkbox id="restoreOnDemand" label="&restoreTabsOnDemand.label;"
accesskey="&restoreTabsOnDemand.accesskey;"
preference="browser.sessionstore.restore_on_demand"
class="indent"/>
preference="browser.sessionstore.restore_on_demand"/>
<checkbox id="switchToNewTabs" label="&switchToNewTabs.label;"
accesskey="&switchToNewTabs.accesskey;"
preference="browser.tabs.loadInBackground"
class="indent"/>
preference="browser.tabs.loadInBackground"/>
#ifdef XP_WIN
<checkbox id="showTabsInTaskbar" label="&showTabsInTaskbar.label;"
accesskey="&showTabsInTaskbar.accesskey;"
preference="browser.taskbar.previews.enable"
class="indent"/>
preference="browser.taskbar.previews.enable"/>
#endif
</groupbox>

Просмотреть файл

@ -22,6 +22,7 @@ MOCHITEST_BROWSER_TESTS = \
browser_dbg_breakpoints-pane.js \
browser_dbg_chrome-debugging.js \
browser_dbg_clean-exit.js \
browser_dbg_clean-exit-window.js \
browser_dbg_cmd-blackbox.js \
browser_dbg_cmd-break.js \
browser_dbg_cmd-dbg.js \

Просмотреть файл

@ -0,0 +1,90 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* Test that closing a window with the debugger in a paused state exits cleanly.
*/
let gDebuggee, gPanel, gDebugger, gWindow;
const TAB_URL = EXAMPLE_URL + "doc_inline-debugger-statement.html";
function test() {
addWindow(TAB_URL)
.then(win => initDebugger(TAB_URL, win))
.then(([aTab, aDebuggee, aPanel, aWindow]) => {
gDebuggee = aDebuggee;
gPanel = aPanel;
gDebugger = gPanel.panelWin;
gWindow = aWindow;
return testCleanExit(gWindow);
})
.then(null, aError => {
ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
});
}
function testCleanExit(aWindow) {
let deferred = promise.defer();
gWindow = aWindow;
ok(!!gWindow, "Second window created.");
gWindow.focus();
let topWindow = Services.wm.getMostRecentWindow("navigator:browser");
is(topWindow, gWindow,
"The second window is on top.");
let isActive = promise.defer();
let isLoaded = promise.defer();
promise.all([isActive.promise, isLoaded.promise]).then(() => {
gWindow.BrowserChromeTest.runWhenReady(() => {
waitForSourceAndCaretAndScopes(gPanel, ".html", 16).then(() => {
is(gDebugger.gThreadClient.paused, true,
"Should be paused after the debugger statement.");
gWindow.close();
deferred.resolve();
finish();
});
gDebuggee.runDebuggerStatement();
});
});
let focusManager = Cc["@mozilla.org/focus-manager;1"].getService(Ci.nsIFocusManager);
if (focusManager.activeWindow != gWindow) {
gWindow.addEventListener("activate", function onActivate(aEvent) {
if (aEvent.target != gWindow) {
return;
}
gWindow.removeEventListener("activate", onActivate, true);
isActive.resolve();
}, true);
} else {
isActive.resolve();
}
let contentLocation = gWindow.content.location.href;
if (contentLocation != TAB_URL) {
gWindow.document.addEventListener("load", function onLoad(aEvent) {
if (aEvent.target.documentURI != TAB_URL) {
return;
}
gWindow.document.removeEventListener("load", onLoad, true);
isLoaded.resolve();
}, true);
} else {
isLoaded.resolve();
}
return deferred.promise;
}
registerCleanupFunction(function() {
gWindow = null;
gDebuggee = null;
gPanel = null;
gDebugger = null;
});

Просмотреть файл

@ -416,18 +416,18 @@ function backspaceText(aElement, aTimes) {
}
}
function getTab(aTarget) {
function getTab(aTarget, aWindow) {
if (aTarget instanceof XULElement) {
return promise.resolve(aTarget);
} else {
return addTab(aTarget);
return addTab(aTarget, aWindow);
}
}
function initDebugger(aTarget, aWindow) {
info("Initializing a debugger panel.");
return getTab(aTarget).then(aTab => {
return getTab(aTarget, aWindow).then(aTab => {
info("Debugee tab added successfully: " + aTarget);
let deferred = promise.defer();
@ -445,7 +445,7 @@ function initDebugger(aTarget, aWindow) {
info("Debugger client resumed successfully.");
prepareDebugger(debuggerPanel);
deferred.resolve([aTab, debuggee, debuggerPanel]);
deferred.resolve([aTab, debuggee, debuggerPanel, aWindow]);
});
});

Просмотреть файл

@ -668,16 +668,13 @@ PropertyView.prototype = {
/**
* Returns the className that should be assigned to the propertyView.
*
* @return string
*/
get propertyHeaderClassName()
{
if (this.visible) {
this.tree._darkStripe = !this.tree._darkStripe;
let darkValue = this.tree._darkStripe ?
"property-view theme-bg-darker" : "property-view";
return darkValue;
let isDark = this.tree._darkStripe = !this.tree._darkStripe;
return isDark ? "property-view theme-bg-darker" : "property-view";
}
return "property-view-hidden";
},
@ -690,49 +687,66 @@ PropertyView.prototype = {
get propertyContentClassName()
{
if (this.visible) {
let darkValue = this.tree._darkStripe ?
"property-content theme-bg-darker" : "property-content";
return darkValue;
let isDark = this.tree._darkStripe;
return isDark ? "property-content theme-bg-darker" : "property-content";
}
return "property-content-hidden";
},
/**
* Build the markup for on computed style
* @return Element
*/
buildMain: function PropertyView_buildMain()
{
let doc = this.tree.styleDocument;
let onToggle = this.onStyleToggle.bind(this);
// Build the container element
this.element = doc.createElementNS(HTML_NS, "div");
this.element.setAttribute("class", this.propertyHeaderClassName);
this.matchedExpander = doc.createElementNS(HTML_NS, "div");
this.matchedExpander.className = "expander theme-twisty";
this.matchedExpander.setAttribute("tabindex", "0");
this.matchedExpander.addEventListener("click",
this.matchedExpanderClick.bind(this), false);
this.matchedExpander.addEventListener("keydown", function(aEvent) {
// Make it keyboard navigable
this.element.setAttribute("tabindex", "0");
this.element.addEventListener("keydown", function(aEvent) {
let keyEvent = Ci.nsIDOMKeyEvent;
if (aEvent.keyCode == keyEvent.DOM_VK_F1) {
this.mdnLinkClick();
}
if (aEvent.keyCode == keyEvent.DOM_VK_RETURN ||
aEvent.keyCode == keyEvent.DOM_VK_SPACE) {
this.matchedExpanderClick(aEvent);
onToggle(aEvent);
}
}.bind(this), false);
// Build the twisty expand/collapse
this.matchedExpander = doc.createElementNS(HTML_NS, "div");
this.matchedExpander.className = "expander theme-twisty";
this.matchedExpander.addEventListener("click", onToggle, false);
this.element.appendChild(this.matchedExpander);
// Build the style name element
this.nameNode = doc.createElementNS(HTML_NS, "div");
this.element.appendChild(this.nameNode);
this.nameNode.setAttribute("class", "property-name theme-fg-color5");
// Reset its tabindex attribute otherwise, if an ellipsis is applied
// it will be reachable via TABing
this.nameNode.setAttribute("tabindex", "");
this.nameNode.textContent = this.nameNode.title = this.name;
this.nameNode.addEventListener("click", function(aEvent) {
this.matchedExpander.focus();
}.bind(this), false);
// Make it hand over the focus to the container
this.nameNode.addEventListener("click", () => this.element.focus(), false);
this.element.appendChild(this.nameNode);
// Build the style value element
this.valueNode = doc.createElementNS(HTML_NS, "div");
this.element.appendChild(this.valueNode);
this.valueNode.setAttribute("class", "property-value theme-fg-color1");
// Reset its tabindex attribute otherwise, if an ellipsis is applied
// it will be reachable via TABing
this.valueNode.setAttribute("tabindex", "");
this.valueNode.setAttribute("dir", "ltr");
this.valueNode.textContent = this.valueNode.title = this.value;
// Make it hand over the focus to the container
this.valueNode.addEventListener("click", () => this.element.focus(), false);
this.element.appendChild(this.valueNode);
return this.element;
},
@ -836,7 +850,7 @@ PropertyView.prototype = {
* @param {Event} aEvent Used to determine the class name of the targets click
* event.
*/
matchedExpanderClick: function PropertyView_matchedExpanderClick(aEvent)
onStyleToggle: function PropertyView_onStyleToggle(aEvent)
{
this.matchedExpanded = !this.matchedExpanded;
this.refreshMatchedSelectors();

Просмотреть файл

@ -36,6 +36,7 @@ MOCHITEST_BROWSER_FILES = \
browser_bug894376_css_value_completion_existing_property_value_pair.js \
browser_ruleview_bug_902966_revert_value_on_ESC.js \
browser_ruleview_pseudoelement.js \
browser_computedview_bug835808_keyboard_nav.js \
head.js \
$(NULL)

Просмотреть файл

@ -0,0 +1,94 @@
/* vim: set ts=2 et sw=2 tw=80: */
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
// Tests that the style inspector works properly
let doc, computedView, inspector;
function test()
{
waitForExplicitFinish();
gBrowser.selectedTab = gBrowser.addTab();
gBrowser.selectedBrowser.addEventListener("load", function onBrowserLoad(evt) {
gBrowser.selectedBrowser.removeEventListener("load", onBrowserLoad, true);
doc = content.document;
waitForFocus(createDocument, content);
}, true);
content.location = "data:text/html,computed view context menu test";
}
function createDocument()
{
doc.body.innerHTML = '<style type="text/css"> ' +
'span { font-variant: small-caps; color: #000000; } ' +
'.nomatches {color: #ff0000;}</style> <div id="first" style="margin: 10em; ' +
'font-size: 14pt; font-family: helvetica, sans-serif; color: #AAA">\n' +
'<h1>Some header text</h1>\n' +
'<p id="salutation" style="font-size: 12pt">hi.</p>\n' +
'<p id="body" style="font-size: 12pt">I am a test-case. This text exists ' +
'solely to provide some things to <span style="color: yellow">' +
'highlight</span> and <span style="font-weight: bold">count</span> ' +
'style list-items in the box at right. If you are reading this, ' +
'you should go do something else instead. Maybe read a book. Or better ' +
'yet, write some test-cases for another bit of code. ' +
'<span style="font-style: italic">some text</span></p>\n' +
'<p id="closing">more text</p>\n' +
'<p>even more text</p>' +
'</div>';
doc.title = "Computed view keyboard navigation test";
openComputedView(startTests);
}
function startTests(aInspector, aComputedView)
{
computedView = aComputedView;
inspector = aInspector;
testTabThrougStyles();
}
function endTests()
{
computedView = inspector = doc = null;
gBrowser.removeCurrentTab();
finish();
}
function testTabThrougStyles()
{
let span = doc.querySelector("span");
inspector.once("computed-view-refreshed", () => {
// Selecting the first computed style in the list
let firstStyle = computedView.styleDocument.querySelector(".property-view");
ok(firstStyle, "First computed style found in panel");
firstStyle.focus();
// Tab to select the 2nd style, press return
EventUtils.synthesizeKey("VK_TAB", {});
EventUtils.synthesizeKey("VK_RETURN", {});
inspector.once("computed-view-property-expanded", () => {
// Verify the 2nd style has been expanded
let secondStyleSelectors = computedView.styleDocument.querySelectorAll(
".property-content .matchedselectors")[1];
ok(secondStyleSelectors.childNodes.length > 0, "Matched selectors expanded");
// Tab back up and test the same thing, with space
EventUtils.synthesizeKey("VK_TAB", {shiftKey: true});
EventUtils.synthesizeKey("VK_SPACE", {});
inspector.once("computed-view-property-expanded", () => {
// Verify the 1st style has been expanded too
let firstStyleSelectors = computedView.styleDocument.querySelectorAll(
".property-content .matchedselectors")[0];
ok(firstStyleSelectors.childNodes.length > 0, "Matched selectors expanded");
endTests();
});
});
});
inspector.selection.setNode(span);
}

Просмотреть файл

@ -45,6 +45,7 @@ body {
overflow-x: hidden;
text-overflow: ellipsis;
white-space: nowrap;
outline: 0;
}
.property-value {
@ -58,6 +59,7 @@ body {
background-size: 5px 8px;
background-position: 2px center;
padding-left: 10px;
outline: 0;
}
.other-property-value {

Просмотреть файл

@ -63,6 +63,7 @@ body {
overflow-x: hidden;
text-overflow: ellipsis;
white-space: nowrap;
outline: 0;
}
.property-value {
@ -76,6 +77,7 @@ body {
background-size: 5px 8px;
background-position: 2px center;
padding-left: 10px;
outline: 0;
}
.other-property-value {

Просмотреть файл

@ -63,6 +63,7 @@ body {
overflow-x: hidden;
text-overflow: ellipsis;
white-space: nowrap;
outline: 0;
}
.property-value {
@ -76,6 +77,7 @@ body {
background-size: 5px 8px;
background-position: 2px center;
padding-left: 10px;
outline: 0;
}
.other-property-value {

Просмотреть файл

@ -2257,7 +2257,7 @@ public class GeckoAppShell
sEventDispatcher.registerEventListener(event, listener);
}
static EventDispatcher getEventDispatcher() {
public static EventDispatcher getEventDispatcher() {
return sEventDispatcher;
}

Просмотреть файл

@ -220,14 +220,12 @@ FENNEC_JAVA_FILES = \
home/BookmarkThumbnailView.java \
home/BrowserSearch.java \
home/HistoryPage.java \
home/HomeCursorLoaderCallbacks.java \
home/HomeFragment.java \
home/HomeListView.java \
home/HomePager.java \
home/HomePagerTabStrip.java \
home/HomeBanner.java \
home/FadedTextView.java \
home/FaviconsLoader.java \
home/LastTabsPage.java \
home/MostRecentPage.java \
home/MostVisitedPage.java \

Просмотреть файл

@ -25,6 +25,7 @@ SYNC_JAVA_FILES := \
background/bagheera/BagheeraRequestDelegate.java \
background/bagheera/BoundedByteArrayEntity.java \
background/bagheera/DeflateHelper.java \
background/common/DateUtils.java \
background/common/log/Logger.java \
background/common/log/writers/AndroidLevelCachingLogWriter.java \
background/common/log/writers/AndroidLogWriter.java \

Просмотреть файл

@ -160,12 +160,17 @@ public class PropertyAnimator implements Runnable {
}
// Try to start animation after any on-going layout round
// in the current view tree.
if (treeObserver != null && treeObserver.isAlive()) {
// in the current view tree. OnPreDrawListener seems broken
// on pre-Honeycomb devices, start animation immediatelly
// in this case.
if (Build.VERSION.SDK_INT >= 11 && treeObserver != null && treeObserver.isAlive()) {
treeObserver.addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
@Override
public boolean onPreDraw() {
treeObserver.removeOnPreDrawListener(this);
if (treeObserver.isAlive()) {
treeObserver.removeOnPreDrawListener(this);
}
mFramePoster.postFirstAnimationFrame();
return true;
}

Просмотреть файл

@ -0,0 +1,43 @@
/* 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.background.common;
import java.util.Calendar;
import java.util.Formatter;
import java.util.TimeZone;
public class DateUtils {
private static final TimeZone UTC = TimeZone.getTimeZone("UTC");
public static final class DateFormatter {
private final Calendar calendar;
private final Formatter formatter;
private final StringBuilder builder;
public DateFormatter() {
this.calendar = Calendar.getInstance(UTC);
this.builder = new StringBuilder(); // So we can reset it.
this.formatter = new Formatter(this.builder, null);
}
public String getDateString(long time) {
calendar.setTimeInMillis(time);
builder.setLength(0);
return formatter.format("%04d-%02d-%02d",
calendar.get(Calendar.YEAR),
calendar.get(Calendar.MONTH) + 1, // 0-indexed.
calendar.get(Calendar.DAY_OF_MONTH))
.toString();
}
public String getDateStringForDay(long day) {
return getDateString(GlobalConstants.MILLISECONDS_PER_DAY * day);
}
}
public static int getDay(final long time) {
return (int) Math.floor(time / GlobalConstants.MILLISECONDS_PER_DAY);
}
}

Просмотреть файл

@ -47,4 +47,8 @@ public class GlobalConstants {
public static String GECKO_PREFERENCES_CLASS = "org.mozilla.gecko.GeckoPreferences";
public static String GECKO_BROADCAST_ANNOUNCEMENTS_PREF_METHOD = "broadcastAnnouncementsPref";
public static String GECKO_BROADCAST_HEALTHREPORT_UPLOAD_PREF_METHOD = "broadcastHealthReportUploadPref";
// Common time values.
public static final long MILLISECONDS_PER_DAY = 24 * 60 * 60 * 1000;
public static final long MILLISECONDS_PER_SIX_MONTHS = 180 * MILLISECONDS_PER_DAY;
}

Просмотреть файл

@ -9,6 +9,7 @@ import java.util.Iterator;
import org.json.JSONObject;
import org.mozilla.gecko.AppConstants;
import org.mozilla.gecko.SysInfo;
import org.mozilla.gecko.background.common.GlobalConstants;
import org.mozilla.gecko.background.common.log.Logger;
import android.content.ContentProvider;
@ -77,7 +78,7 @@ public class EnvironmentBuilder {
e.sysName = SysInfo.getName();
e.sysVersion = SysInfo.getReleaseVersion();
e.profileCreation = (int) (info.getProfileCreationTime() / HealthReportConstants.MILLISECONDS_PER_DAY);
e.profileCreation = (int) (info.getProfileCreationTime() / GlobalConstants.MILLISECONDS_PER_DAY);
// Corresponds to Gecko pref "extensions.blocklist.enabled".
e.isBlocklistEnabled = (info.isBlocklistEnabled() ? 1 : 0);

Просмотреть файл

@ -5,6 +5,8 @@
package org.mozilla.gecko.background.healthreport;
import org.mozilla.gecko.background.common.GlobalConstants;
public class HealthReportConstants {
public static final String HEALTH_AUTHORITY = "@ANDROID_PACKAGE_NAME@.health";
public static final String GLOBAL_LOG_TAG = "GeckoHealth";
@ -15,9 +17,6 @@ public class HealthReportConstants {
*/
public static final long EARLIEST_LAST_PING = 1367500000000L;
public static final long MILLISECONDS_PER_DAY = 24 * 60 * 60 * 1000;
public static final long MILLISECONDS_PER_SIX_MONTHS = 180 * MILLISECONDS_PER_DAY;
// Not `final` so we have the option to turn this on at runtime with a magic addon.
public static boolean UPLOAD_FEATURE_DISABLED = false;
@ -29,15 +28,15 @@ public class HealthReportConstants {
// intent is scheduled to be called by the Android Alarm Manager, not how
// frequently we actually submit.
public static final String PREF_SUBMISSION_INTENT_INTERVAL_MSEC = "healthreport_submission_intent_interval_msec";
public static final long DEFAULT_SUBMISSION_INTENT_INTERVAL_MSEC = MILLISECONDS_PER_DAY / 24;
public static final long DEFAULT_SUBMISSION_INTENT_INTERVAL_MSEC = GlobalConstants.MILLISECONDS_PER_DAY / 24;
public static final String ACTION_HEALTHREPORT_UPLOAD_PREF = "@ANDROID_PACKAGE_NAME@.HEALTHREPORT_UPLOAD_PREF";
public static final String PREF_MINIMUM_TIME_BETWEEN_UPLOADS = "healthreport_time_between_uploads";
public static final long DEFAULT_MINIMUM_TIME_BETWEEN_UPLOADS = MILLISECONDS_PER_DAY;
public static final long DEFAULT_MINIMUM_TIME_BETWEEN_UPLOADS = GlobalConstants.MILLISECONDS_PER_DAY;
public static final String PREF_MINIMUM_TIME_BEFORE_FIRST_SUBMISSION = "healthreport_time_before_first_submission";
public static final long DEFAULT_MINIMUM_TIME_BEFORE_FIRST_SUBMISSION = MILLISECONDS_PER_DAY;
public static final long DEFAULT_MINIMUM_TIME_BEFORE_FIRST_SUBMISSION = GlobalConstants.MILLISECONDS_PER_DAY;
public static final String PREF_MINIMUM_TIME_AFTER_FAILURE = "healthreport_time_after_failure";
public static final long DEFAULT_MINIMUM_TIME_AFTER_FAILURE = DEFAULT_SUBMISSION_INTENT_INTERVAL_MSEC;

Просмотреть файл

@ -12,6 +12,7 @@ import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import org.json.JSONObject;
import org.mozilla.gecko.background.common.DateUtils;
import org.mozilla.gecko.background.common.log.Logger;
import org.mozilla.gecko.background.healthreport.HealthReportStorage.MeasurementFields.FieldSpec;
@ -1029,7 +1030,7 @@ public class HealthReportDatabaseStorage implements HealthReportStorage {
@Override
public int getDay(long time) {
return HealthReportUtils.getDay(time);
return DateUtils.getDay(time);
}
@Override

Просмотреть файл

@ -10,6 +10,7 @@ import java.util.Set;
import org.json.JSONException;
import org.json.JSONObject;
import org.mozilla.gecko.background.common.DateUtils.DateFormatter;
import org.mozilla.gecko.background.common.log.Logger;
import org.mozilla.gecko.background.healthreport.HealthReportStorage.Field;
@ -22,9 +23,11 @@ public class HealthReportGenerator {
private static final String LOG_TAG = "GeckoHealthGen";
private final HealthReportStorage storage;
private final DateFormatter dateFormatter;
public HealthReportGenerator(HealthReportStorage storage) {
this.storage = storage;
this.dateFormatter = new DateFormatter();
}
@SuppressWarnings("static-method")
@ -76,10 +79,10 @@ public class HealthReportGenerator {
JSONObject document = new JSONObject();
if (lastPingTime >= HealthReportConstants.EARLIEST_LAST_PING) {
document.put("lastPingDate", HealthReportUtils.getDateString(lastPingTime));
document.put("lastPingDate", dateFormatter.getDateString(lastPingTime));
}
document.put("thisPingDate", HealthReportUtils.getDateString(now()));
document.put("thisPingDate", dateFormatter.getDateString(now()));
document.put("version", PAYLOAD_VERSION);
document.put("environments", getEnvironmentsJSON(currentEnvironment, envs));
@ -147,7 +150,7 @@ public class HealthReportGenerator {
if (dateChanged) {
if (dateObject != null) {
days.put(HealthReportUtils.getDateStringForDay(lastDate), dateObject);
days.put(dateFormatter.getDateStringForDay(lastDate), dateObject);
}
dateObject = new JSONObject();
lastDate = cDate;
@ -179,7 +182,7 @@ public class HealthReportGenerator {
cursor.moveToNext();
continue;
}
days.put(HealthReportUtils.getDateStringForDay(lastDate), dateObject);
days.put(dateFormatter.getDateStringForDay(lastDate), dateObject);
} finally {
cursor.close();
}

Просмотреть файл

@ -4,13 +4,10 @@
package org.mozilla.gecko.background.healthreport;
import java.text.SimpleDateFormat;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Locale;
import java.util.Set;
import java.util.SortedSet;
import java.util.TimeZone;
import java.util.TreeSet;
import java.util.UUID;
@ -25,24 +22,10 @@ import android.net.Uri;
public class HealthReportUtils {
public static final String LOG_TAG = HealthReportUtils.class.getSimpleName();
public static int getDay(final long time) {
return (int) Math.floor(time / HealthReportConstants.MILLISECONDS_PER_DAY);
}
public static String getEnvironmentHash(final String input) {
return DigestUtils.shaHex(input);
}
public static String getDateStringForDay(long day) {
return getDateString(HealthReportConstants.MILLISECONDS_PER_DAY * day);
}
public static String getDateString(long time) {
final SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd", Locale.US);
format.setTimeZone(TimeZone.getTimeZone("UTC"));
return format.format(time);
}
/**
* Take an environment URI (one that identifies an environment) and produce an
* event URI.

Просмотреть файл

@ -10,6 +10,7 @@ import java.util.Collection;
import org.json.JSONObject;
import org.mozilla.gecko.background.bagheera.BagheeraClient;
import org.mozilla.gecko.background.bagheera.BagheeraRequestDelegate;
import org.mozilla.gecko.background.common.GlobalConstants;
import org.mozilla.gecko.background.common.log.Logger;
import org.mozilla.gecko.background.healthreport.EnvironmentBuilder;
import org.mozilla.gecko.background.healthreport.HealthReportConstants;
@ -103,7 +104,7 @@ public class AndroidSubmissionClient implements SubmissionClient {
return;
}
long since = localTime - HealthReportConstants.MILLISECONDS_PER_SIX_MONTHS;
long since = localTime - GlobalConstants.MILLISECONDS_PER_SIX_MONTHS;
long last = Math.max(getLastUploadLocalTime(), HealthReportConstants.EARLIEST_LAST_PING);
if (!storage.hasEventSince(last)) {

Просмотреть файл

@ -14,6 +14,7 @@ import org.mozilla.gecko.GeckoEvent;
import org.mozilla.gecko.GeckoProfile;
import org.mozilla.gecko.background.healthreport.EnvironmentBuilder;
import org.mozilla.gecko.background.common.GlobalConstants;
import org.mozilla.gecko.background.healthreport.HealthReportConstants;
import org.mozilla.gecko.background.healthreport.HealthReportDatabaseStorage;
import org.mozilla.gecko.background.healthreport.HealthReportGenerator;
@ -124,7 +125,7 @@ public class BrowserHealthReporter implements GeckoEventListener {
GeckoProfile profile = GeckoAppShell.getGeckoInterface().getProfile();
String profilePath = profile.getDir().getAbsolutePath();
long since = System.currentTimeMillis() - HealthReportConstants.MILLISECONDS_PER_SIX_MONTHS;
long since = System.currentTimeMillis() - GlobalConstants.MILLISECONDS_PER_SIX_MONTHS;
long lastPingTime = Math.max(getLastUploadLocalTime(), HealthReportConstants.EARLIEST_LAST_PING);
return generateReport(since, lastPingTime, profilePath);

Просмотреть файл

@ -179,7 +179,7 @@ public class BookmarksPage extends HomeFragment {
BrowserDB.invalidateCachedState();
// Create callbacks before the initial loader is started.
mLoaderCallbacks = new CursorLoaderCallbacks(activity, getLoaderManager());
mLoaderCallbacks = new CursorLoaderCallbacks();
mThumbnailsLoaderCallbacks = new ThumbnailsLoaderCallbacks();
loadIfVisible();
}
@ -453,11 +453,7 @@ public class BookmarksPage extends HomeFragment {
/**
* Loader callbacks for the LoaderManager of this fragment.
*/
private class CursorLoaderCallbacks extends HomeCursorLoaderCallbacks {
public CursorLoaderCallbacks(Context context, LoaderManager loaderManager) {
super(context, loaderManager);
}
private class CursorLoaderCallbacks implements LoaderCallbacks<Cursor> {
@Override
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
switch(id) {
@ -472,11 +468,9 @@ public class BookmarksPage extends HomeFragment {
case LOADER_ID_TOP_BOOKMARKS: {
return new TopBookmarksLoader(getActivity());
}
default: {
return super.onCreateLoader(id, args);
}
}
return null;
}
@Override
@ -485,7 +479,6 @@ public class BookmarksPage extends HomeFragment {
switch(loaderId) {
case LOADER_ID_BOOKMARKS_LIST: {
mListAdapter.swapCursor(c);
loadFavicons(c);
mList.setHeaderDividersEnabled(c != null && c.getCount() > 0);
break;
}
@ -509,11 +502,6 @@ public class BookmarksPage extends HomeFragment {
}
break;
}
default: {
super.onLoadFinished(loader, c);
break;
}
}
}
@ -534,18 +522,8 @@ public class BookmarksPage extends HomeFragment {
break;
}
}
default: {
super.onLoaderReset(loader);
break;
}
}
}
@Override
public void onFaviconsLoaded() {
mListAdapter.notifyDataSetChanged();
}
}
/**

Просмотреть файл

@ -104,7 +104,7 @@ public class BrowserSearch extends HomeFragment
// Whether search suggestions are enabled or not
private boolean mSuggestionsEnabled;
// Callbacks used for the search and favicon cursor loaders
// Callbacks used for the search loader
private CursorLoaderCallbacks mCursorLoaderCallbacks;
// Callbacks used for the search suggestion loader
@ -286,17 +286,15 @@ public class BrowserSearch extends HomeFragment
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
final Activity activity = getActivity();
// Intialize the search adapter
mAdapter = new SearchAdapter(activity);
mAdapter = new SearchAdapter(getActivity());
mList.setAdapter(mAdapter);
// Only create an instance when we need it
mSuggestionLoaderCallbacks = null;
// Create callbacks before the initial loader is started
mCursorLoaderCallbacks = new CursorLoaderCallbacks(activity, getLoaderManager());
mCursorLoaderCallbacks = new CursorLoaderCallbacks();
loadIfVisible();
}
@ -772,49 +770,26 @@ public class BrowserSearch extends HomeFragment
}
}
private class CursorLoaderCallbacks extends HomeCursorLoaderCallbacks {
public CursorLoaderCallbacks(Context context, LoaderManager loaderManager) {
super(context, loaderManager);
}
private class CursorLoaderCallbacks implements LoaderCallbacks<Cursor> {
@Override
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
if (id == LOADER_ID_SEARCH) {
return SearchLoader.createInstance(getActivity(), args);
} else {
return super.onCreateLoader(id, args);
}
return SearchLoader.createInstance(getActivity(), args);
}
@Override
public void onLoadFinished(Loader<Cursor> loader, Cursor c) {
if (loader.getId() == LOADER_ID_SEARCH) {
mAdapter.swapCursor(c);
mAdapter.swapCursor(c);
// We should handle autocompletion based on the search term
// associated with the currently loader that has just provided
// the results.
SearchCursorLoader searchLoader = (SearchCursorLoader) loader;
handleAutocomplete(searchLoader.getSearchTerm(), c);
loadFavicons(c);
} else {
super.onLoadFinished(loader, c);
}
// We should handle autocompletion based on the search term
// associated with the currently loader that has just provided
// the results.
SearchCursorLoader searchLoader = (SearchCursorLoader) loader;
handleAutocomplete(searchLoader.getSearchTerm(), c);
}
@Override
public void onLoaderReset(Loader<Cursor> loader) {
if (loader.getId() == LOADER_ID_SEARCH) {
mAdapter.swapCursor(null);
} else {
super.onLoaderReset(loader);
}
}
@Override
public void onFaviconsLoaded() {
mAdapter.notifyDataSetChanged();
mAdapter.swapCursor(null);
}
}

Просмотреть файл

@ -1,115 +0,0 @@
/* -*- 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.favicons.Favicons;
import org.mozilla.gecko.db.BrowserDB;
import org.mozilla.gecko.db.BrowserDB.URLColumns;
import org.mozilla.gecko.gfx.BitmapUtils;
import android.content.ContentResolver;
import android.content.Context;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.os.Bundle;
import android.support.v4.app.LoaderManager;
import android.support.v4.app.LoaderManager.LoaderCallbacks;
import android.support.v4.content.Loader;
import java.util.ArrayList;
/**
* Encapsulates the implementation of the favicons cursorloader.
*/
class FaviconsLoader {
// Argument containing list of urls for the favicons loader
private static final String FAVICONS_LOADER_URLS_ARG = "urls";
private FaviconsLoader() {
}
private static ArrayList<String> getUrlsWithoutFavicon(Cursor c) {
ArrayList<String> urls = new ArrayList<String>();
if (c == null || !c.moveToFirst()) {
return urls;
}
do {
final String url = c.getString(c.getColumnIndexOrThrow(URLColumns.URL));
// We only want to load favicons from DB if they are not in the
// memory cache yet. The url is null for bookmark folders.
if (url == null || Favicons.getFaviconFromMemCache(url) != null) {
continue;
}
urls.add(url);
} while (c.moveToNext());
return urls;
}
private static void storeFaviconsInMemCache(Cursor c) {
if (c == null || !c.moveToFirst()) {
return;
}
do {
final String url = c.getString(c.getColumnIndexOrThrow(URLColumns.URL));
final byte[] b = c.getBlob(c.getColumnIndexOrThrow(URLColumns.FAVICON));
if (b == null) {
continue;
}
Bitmap favicon = BitmapUtils.decodeByteArray(b);
if (favicon == null) {
continue;
}
favicon = Favicons.scaleImage(favicon);
Favicons.putFaviconInMemCache(url, favicon);
} while (c.moveToNext());
}
public static void restartFromCursor(LoaderManager manager, int loaderId,
LoaderCallbacks<Cursor> callbacks, Cursor c) {
// If there urls without in-memory favicons, trigger a new loader
// to load the images from disk to memory.
ArrayList<String> urls = getUrlsWithoutFavicon(c);
if (urls.size() > 0) {
Bundle args = new Bundle();
args.putStringArrayList(FAVICONS_LOADER_URLS_ARG, urls);
manager.restartLoader(loaderId, args, callbacks);
}
}
public static Loader<Cursor> createInstance(Context context, Bundle args) {
final ArrayList<String> urls = args.getStringArrayList(FAVICONS_LOADER_URLS_ARG);
return new FaviconsCursorLoader(context, urls);
}
private static class FaviconsCursorLoader extends SimpleCursorLoader {
private final ArrayList<String> mUrls;
public FaviconsCursorLoader(Context context, ArrayList<String> urls) {
super(context);
mUrls = urls;
}
@Override
public Cursor loadCursor() {
final ContentResolver cr = getContext().getContentResolver();
Cursor c = BrowserDB.getFaviconsForUrls(cr, mUrls);
storeFaviconsInMemCache(c);
return c;
}
}
}

Просмотреть файл

@ -5,16 +5,31 @@
package org.mozilla.gecko.home;
import org.mozilla.gecko.GeckoAppShell;
import org.mozilla.gecko.GeckoEvent;
import org.mozilla.gecko.R;
import org.mozilla.gecko.gfx.BitmapUtils;
import org.mozilla.gecko.util.GeckoEventListener;
import org.mozilla.gecko.util.ThreadUtils;
import org.json.JSONException;
import org.json.JSONObject;
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
public class HomeBanner extends LinearLayout {
public class HomeBanner extends LinearLayout
implements GeckoEventListener {
private static final String LOGTAG = "GeckoHomeBanner";
public HomeBanner(Context context) {
this(context, null);
@ -43,9 +58,78 @@ public class HomeBanner extends LinearLayout {
HomeBanner.this.setVisibility(View.GONE);
}
});
setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// Send the current message id back to JS.
GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("HomeBanner:Click", (String) getTag()));
}
});
GeckoAppShell.getEventDispatcher().registerEventListener("HomeBanner:Data", this);
GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("HomeBanner:Get", null));
}
@Override
public void onDetachedFromWindow() {
super.onDetachedFromWindow();
GeckoAppShell.getEventDispatcher().unregisterEventListener("HomeBanner:Data", this);
}
public boolean isDismissed() {
return (getVisibility() == View.GONE);
}
@Override
public void handleMessage(String event, JSONObject message) {
try {
// Store the current message id to pass back to JS in the view's OnClickListener.
setTag(message.getString("id"));
final String text = message.getString("text");
final TextView textView = (TextView) findViewById(R.id.text);
// Update the banner message on the UI thread.
ThreadUtils.postToUiThread(new Runnable() {
@Override
public void run() {
textView.setText(text);
setVisibility(View.VISIBLE);
}
});
} catch (JSONException e) {
Log.e(LOGTAG, "Exception handling " + event + " message", e);
return;
}
final String iconURI = message.optString("iconURI");
final ImageView iconView = (ImageView) findViewById(R.id.icon);
if (TextUtils.isEmpty(iconURI)) {
// Hide the image view if we don't have an icon to show.
iconView.setVisibility(View.GONE);
return;
}
BitmapUtils.getDrawable(getContext(), iconURI, new BitmapUtils.BitmapLoader() {
@Override
public void onBitmapFound(final Drawable d) {
// Bail if getDrawable doesn't find anything.
if (d == null) {
iconView.setVisibility(View.GONE);
return;
}
// Update the banner icon on the UI thread.
ThreadUtils.postToUiThread(new Runnable() {
@Override
public void run() {
iconView.setImageDrawable(d);
}
});
}
});
}
}

Просмотреть файл

@ -1,58 +0,0 @@
/* -*- 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 android.content.Context;
import android.database.Cursor;
import android.os.Bundle;
import android.support.v4.app.LoaderManager;
import android.support.v4.app.LoaderManager.LoaderCallbacks;
import android.support.v4.content.Loader;
/**
* Cursor loader callbacks that takes care loading favicons into memory.
*/
abstract class HomeCursorLoaderCallbacks implements LoaderCallbacks<Cursor> {
// Cursor loader ID for favicons query
private static final int LOADER_ID_FAVICONS = 100;
private final Context mContext;
private final LoaderManager mLoaderManager;
public HomeCursorLoaderCallbacks(Context context, LoaderManager loaderManager) {
mContext = context;
mLoaderManager = loaderManager;
}
public void loadFavicons(Cursor cursor) {
FaviconsLoader.restartFromCursor(mLoaderManager, LOADER_ID_FAVICONS, this, cursor);
}
@Override
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
if (id == LOADER_ID_FAVICONS) {
return FaviconsLoader.createInstance(mContext, args);
}
return null;
}
@Override
public void onLoadFinished(Loader<Cursor> loader, Cursor c) {
if (loader.getId() == LOADER_ID_FAVICONS) {
onFaviconsLoaded();
}
}
@Override
public void onLoaderReset(Loader<Cursor> loader) {
// Do nothing by default.
}
// Callback for favicons loaded in memory.
public abstract void onFaviconsLoaded();
}

Просмотреть файл

@ -142,14 +142,12 @@ public class LastTabsPage extends HomeFragment {
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
final Activity activity = getActivity();
// Intialize adapter
mAdapter = new LastTabsAdapter(activity);
mAdapter = new LastTabsAdapter(getActivity());
mList.setAdapter(mAdapter);
// Create callbacks before the initial loader is started
mCursorLoaderCallbacks = new CursorLoaderCallbacks(activity, getLoaderManager());
mCursorLoaderCallbacks = new CursorLoaderCallbacks();
loadIfVisible();
}
@ -262,43 +260,21 @@ public class LastTabsPage extends HomeFragment {
}
}
private class CursorLoaderCallbacks extends HomeCursorLoaderCallbacks {
public CursorLoaderCallbacks(Context context, LoaderManager loaderManager) {
super(context, loaderManager);
}
private class CursorLoaderCallbacks implements LoaderCallbacks<Cursor> {
@Override
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
if (id == LOADER_ID_LAST_TABS) {
return new LastTabsCursorLoader(getActivity());
} else {
return super.onCreateLoader(id, args);
}
return new LastTabsCursorLoader(getActivity());
}
@Override
public void onLoadFinished(Loader<Cursor> loader, Cursor c) {
if (loader.getId() == LOADER_ID_LAST_TABS) {
mAdapter.swapCursor(c);
updateUiFromCursor(c);
loadFavicons(c);
} else {
super.onLoadFinished(loader, c);
}
mAdapter.swapCursor(c);
updateUiFromCursor(c);
}
@Override
public void onLoaderReset(Loader<Cursor> loader) {
if (loader.getId() == LOADER_ID_LAST_TABS) {
mAdapter.swapCursor(null);
} else {
super.onLoaderReset(loader);
}
}
@Override
public void onFaviconsLoaded() {
mAdapter.notifyDataSetChanged();
mAdapter.swapCursor(null);
}
}
}

Просмотреть файл

@ -17,6 +17,7 @@ import android.content.Context;
import android.database.Cursor;
import android.os.Bundle;
import android.support.v4.app.LoaderManager;
import android.support.v4.app.LoaderManager.LoaderCallbacks;
import android.support.v4.content.Loader;
import android.util.SparseArray;
import android.view.LayoutInflater;
@ -127,14 +128,12 @@ public class MostRecentPage extends HomeFragment {
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
final Activity activity = getActivity();
// Intialize adapter
mAdapter = new MostRecentAdapter(activity);
mAdapter = new MostRecentAdapter(getActivity());
mList.setAdapter(mAdapter);
// Create callbacks before the initial loader is started
mCursorLoaderCallbacks = new CursorLoaderCallbacks(activity, getLoaderManager());
mCursorLoaderCallbacks = new CursorLoaderCallbacks();
loadIfVisible();
}
@ -360,43 +359,21 @@ public class MostRecentPage extends HomeFragment {
}
}
private class CursorLoaderCallbacks extends HomeCursorLoaderCallbacks {
public CursorLoaderCallbacks(Context context, LoaderManager loaderManager) {
super(context, loaderManager);
}
private class CursorLoaderCallbacks implements LoaderCallbacks<Cursor> {
@Override
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
if (id == LOADER_ID_HISTORY) {
return new MostRecentCursorLoader(getActivity());
} else {
return super.onCreateLoader(id, args);
}
return new MostRecentCursorLoader(getActivity());
}
@Override
public void onLoadFinished(Loader<Cursor> loader, Cursor c) {
if (loader.getId() == LOADER_ID_HISTORY) {
mAdapter.swapCursor(c);
updateUiFromCursor(c);
loadFavicons(c);
} else {
super.onLoadFinished(loader, c);
}
mAdapter.swapCursor(c);
updateUiFromCursor(c);
}
@Override
public void onLoaderReset(Loader<Cursor> loader) {
if (loader.getId() == LOADER_ID_HISTORY) {
mAdapter.swapCursor(null);
} else {
super.onLoaderReset(loader);
}
}
@Override
public void onFaviconsLoaded() {
mAdapter.notifyDataSetChanged();
mAdapter.swapCursor(null);
}
}
}

Просмотреть файл

@ -16,6 +16,7 @@ import android.content.Context;
import android.database.Cursor;
import android.os.Bundle;
import android.support.v4.app.LoaderManager;
import android.support.v4.app.LoaderManager.LoaderCallbacks;
import android.support.v4.content.Loader;
import android.support.v4.widget.CursorAdapter;
import android.view.LayoutInflater;
@ -129,14 +130,12 @@ public class MostVisitedPage extends HomeFragment {
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
final Activity activity = getActivity();
// Intialize the search adapter
mAdapter = new VisitedAdapter(activity, null);
mAdapter = new VisitedAdapter(getActivity(), null);
mList.setAdapter(mAdapter);
// Create callbacks before the initial loader is started
mCursorLoaderCallbacks = new CursorLoaderCallbacks(activity, getLoaderManager());
mCursorLoaderCallbacks = new CursorLoaderCallbacks();
loadIfVisible();
}
@ -206,43 +205,21 @@ public class MostVisitedPage extends HomeFragment {
}
}
private class CursorLoaderCallbacks extends HomeCursorLoaderCallbacks {
public CursorLoaderCallbacks(Context context, LoaderManager loaderManager) {
super(context, loaderManager);
}
private class CursorLoaderCallbacks implements LoaderCallbacks<Cursor> {
@Override
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
if (id == LOADER_ID_FRECENCY) {
return new FrecencyCursorLoader(getActivity());
} else {
return super.onCreateLoader(id, args);
}
return new FrecencyCursorLoader(getActivity());
}
@Override
public void onLoadFinished(Loader<Cursor> loader, Cursor c) {
if (loader.getId() == LOADER_ID_FRECENCY) {
mAdapter.swapCursor(c);
updateUiFromCursor(c);
loadFavicons(c);
} else {
super.onLoadFinished(loader, c);
}
mAdapter.swapCursor(c);
updateUiFromCursor(c);
}
@Override
public void onLoaderReset(Loader<Cursor> loader) {
if (loader.getId() == LOADER_ID_FRECENCY) {
mAdapter.swapCursor(null);
} else {
super.onLoaderReset(loader);
}
}
@Override
public void onFaviconsLoaded() {
mAdapter.notifyDataSetChanged();
mAdapter.swapCursor(null);
}
}
}

Просмотреть файл

@ -14,6 +14,7 @@ import android.database.Cursor;
import android.os.Bundle;
import android.support.v4.app.DialogFragment;
import android.support.v4.app.LoaderManager;
import android.support.v4.app.LoaderManager.LoaderCallbacks;
import android.support.v4.content.Loader;
import android.support.v4.widget.CursorAdapter;
import android.text.Editable;
@ -38,9 +39,6 @@ class PinBookmarkDialog extends DialogFragment {
// Cursor loader ID for search query
private static final int LOADER_ID_SEARCH = 0;
// Cursor loader ID for favicons query
private static final int LOADER_ID_FAVICONS = 1;
// Holds the current search term to use in the query
private String mSearchTerm;
@ -53,7 +51,7 @@ class PinBookmarkDialog extends DialogFragment {
// Search results
private ListView mList;
// Callbacks used for the search and favicon cursor loaders
// Callbacks used for the search loader
private CursorLoaderCallbacks mLoaderCallbacks;
// Bookmark selected listener
@ -125,15 +123,14 @@ class PinBookmarkDialog extends DialogFragment {
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
final Activity activity = getActivity();
final LoaderManager manager = getLoaderManager();
// Initialize the search adapter
mAdapter = new SearchAdapter(activity);
mAdapter = new SearchAdapter(getActivity());
mList.setAdapter(mAdapter);
// Create callbacks before the initial loader is started
mLoaderCallbacks = new CursorLoaderCallbacks(activity, manager);
mLoaderCallbacks = new CursorLoaderCallbacks();
// Reconnect to the loader only if present
manager.initLoader(LOADER_ID_SEARCH, null, mLoaderCallbacks);
@ -179,42 +176,20 @@ class PinBookmarkDialog extends DialogFragment {
}
}
private class CursorLoaderCallbacks extends HomeCursorLoaderCallbacks {
public CursorLoaderCallbacks(Context context, LoaderManager loaderManager) {
super(context, loaderManager);
}
private class CursorLoaderCallbacks implements LoaderCallbacks<Cursor> {
@Override
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
if (id == LOADER_ID_SEARCH) {
return SearchLoader.createInstance(getActivity(), args);
} else {
return super.onCreateLoader(id, args);
}
return SearchLoader.createInstance(getActivity(), args);
}
@Override
public void onLoadFinished(Loader<Cursor> loader, Cursor c) {
if (loader.getId() == LOADER_ID_SEARCH) {
mAdapter.swapCursor(c);
loadFavicons(c);
} else {
super.onLoadFinished(loader, c);
}
mAdapter.swapCursor(c);
}
@Override
public void onLoaderReset(Loader<Cursor> loader) {
if (loader.getId() == LOADER_ID_SEARCH) {
mAdapter.swapCursor(null);
} else {
super.onLoaderReset(loader);
}
}
@Override
public void onFaviconsLoaded() {
mAdapter.notifyDataSetChanged();
mAdapter.swapCursor(null);
}
}
}

Просмотреть файл

@ -219,33 +219,18 @@ public class ReadingListPage extends HomeFragment {
private class CursorLoaderCallbacks implements LoaderCallbacks<Cursor> {
@Override
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
switch(id) {
case LOADER_ID_READING_LIST:
return new ReadingListLoader(getActivity());
}
return null;
return new ReadingListLoader(getActivity());
}
@Override
public void onLoadFinished(Loader<Cursor> loader, Cursor c) {
final int loaderId = loader.getId();
switch(loaderId) {
case LOADER_ID_READING_LIST:
mAdapter.swapCursor(c);
break;
}
updateUiFromCursor(c);
mAdapter.swapCursor(c);
updateUiFromCursor(c);
}
@Override
public void onLoaderReset(Loader<Cursor> loader) {
final int loaderId = loader.getId();
switch(loaderId) {
case LOADER_ID_READING_LIST:
mAdapter.swapCursor(null);
break;
}
mAdapter.swapCursor(null);
}
}
}

Просмотреть файл

@ -10,14 +10,19 @@ import org.mozilla.gecko.R;
import org.mozilla.gecko.Tab;
import org.mozilla.gecko.Tabs;
import org.mozilla.gecko.db.BrowserContract.Combined;
import org.mozilla.gecko.db.BrowserDB;
import org.mozilla.gecko.db.BrowserDB.URLColumns;
import org.mozilla.gecko.gfx.BitmapUtils;
import org.mozilla.gecko.util.ThreadUtils;
import org.mozilla.gecko.util.UiAsyncTask;
import org.mozilla.gecko.widget.FaviconView;
import android.content.ContentResolver;
import android.content.Context;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.os.AsyncTask;
import android.os.Build;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.view.Gravity;
@ -40,6 +45,8 @@ public class TwoLinePageRow extends LinearLayout
// The URL for the page corresponding to this view.
private String mPageUrl;
private LoadFaviconTask mLoadFaviconTask;
public TwoLinePageRow(Context context) {
this(context, null);
}
@ -74,6 +81,8 @@ public class TwoLinePageRow extends LinearLayout
Tabs.unregisterOnTabsChangedListener(TwoLinePageRow.this);
}
});
cancelLoadFaviconTask();
}
@Override
@ -130,6 +139,16 @@ public class TwoLinePageRow extends LinearLayout
updateDisplayedUrl();
}
/**
* Cancels any pending favicon loading task associated with this view.
*/
private void cancelLoadFaviconTask() {
if (mLoadFaviconTask != null) {
mLoadFaviconTask.cancel(true);
mLoadFaviconTask = null;
}
}
/**
* Replaces the page URL with "Switch to tab" if there is already a tab open with that URL.
*/
@ -163,24 +182,33 @@ public class TwoLinePageRow extends LinearLayout
// bar view - this is the equivalent of getDisplayTitle() in Tab.java
setTitle(TextUtils.isEmpty(title) ? url : title);
// No need to do extra work if the URL associated with this view
// hasn't changed.
if (TextUtils.equals(mPageUrl, url)) {
return;
}
updateDisplayedUrl(url);
cancelLoadFaviconTask();
int faviconIndex = cursor.getColumnIndex(URLColumns.FAVICON);
if (faviconIndex != -1) {
byte[] b = cursor.getBlob(faviconIndex);
Bitmap favicon = null;
if (b != null) {
Bitmap bitmap = BitmapUtils.decodeByteArray(b);
if (bitmap != null) {
favicon = Favicons.scaleImage(bitmap);
}
}
// First, try to find the favicon in the memory cache. If it's not
// cached yet, try to load it from the database, off main thread.
final Bitmap favicon = Favicons.getFaviconFromMemCache(url);
if (favicon != null) {
setFaviconWithUrl(favicon, url);
} else {
// If favicons is not on the cursor, try to fetch it from the memory cache
setFaviconWithUrl(Favicons.getFaviconFromMemCache(url), url);
// Show blank image until the new favicon finishes loading
mFavicon.clearImage();
mLoadFaviconTask = new LoadFaviconTask(url);
// Try to use a thread pool instead of serial execution of tasks
// to add more throughput to the favicon loading routines.
if (Build.VERSION.SDK_INT >= 11) {
mLoadFaviconTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
} else {
mLoadFaviconTask.execute();
}
}
// Don't show bookmark/reading list icon, if not needed.
@ -213,4 +241,37 @@ public class TwoLinePageRow extends LinearLayout
setBookmarkIcon(NO_ICON);
}
}
private class LoadFaviconTask extends AsyncTask<Void, Void, Bitmap> {
private final String mUrl;
public LoadFaviconTask(String url) {
mUrl = url;
}
@Override
public Bitmap doInBackground(Void... params) {
Bitmap favicon = Favicons.getFaviconFromMemCache(mUrl);
if (favicon == null) {
final ContentResolver cr = getContext().getContentResolver();
final Bitmap faviconFromDb = BrowserDB.getFaviconForUrl(cr, mUrl);
if (faviconFromDb != null) {
favicon = Favicons.scaleImage(faviconFromDb);
Favicons.putFaviconInMemCache(mUrl, favicon);
}
}
return favicon;
}
@Override
public void onPostExecute(Bitmap favicon) {
if (TextUtils.equals(mPageUrl, mUrl)) {
setFaviconWithUrl(favicon, mUrl);
}
mLoadFaviconTask = null;
}
}
}

Просмотреть файл

@ -9,13 +9,13 @@
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:layout_marginLeft="10dp"
android:paddingTop="7dp"
android:paddingBottom="7dp"
android:textAppearance="@style/TextAppearance.Widget.HomeBanner"

Просмотреть файл

@ -64,7 +64,7 @@ public class FaviconView extends ImageView {
private void formatImage() {
// If we're called before bitmap is set, just show the default.
if (mIconBitmap == null) {
setImageResource(0);
setImageResource(R.drawable.favicon);
hideBackground();
return;
}
@ -72,6 +72,7 @@ public class FaviconView extends ImageView {
// If we're called before size set, abort.
if (mActualWidth == 0 || mActualHeight == 0) {
hideBackground();
setImageResource(R.drawable.favicon);
return;
}
@ -154,6 +155,14 @@ public class FaviconView extends ImageView {
formatImage();
}
/**
* Clear image and background shown by this view.
*/
public void clearImage() {
setImageResource(0);
hideBackground();
}
/**
* Update the displayed image and apply the scaling logic.
* The scaling logic will attempt to resize the image to fit correctly inside the view in a way

Просмотреть файл

@ -0,0 +1,132 @@
// -*- Mode: js2; tab-width: 2; indent-tabs-mode: nil; js2-basic-offset: 2; js2-skip-preprocessor-directives: t; -*-
/* 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/. */
"use strict";
this.EXPORTED_SYMBOLS = ["Home"];
const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
Cu.import("resource://gre/modules/Services.jsm");
// See bug 915424
function resolveGeckoURI(aURI) {
if (!aURI)
throw "Can't resolve an empty uri";
if (aURI.startsWith("chrome://")) {
let registry = Cc['@mozilla.org/chrome/chrome-registry;1'].getService(Ci["nsIChromeRegistry"]);
return registry.convertChromeURL(Services.io.newURI(aURI, null, null)).spec;
} else if (aURI.startsWith("resource://")) {
let handler = Services.io.getProtocolHandler("resource").QueryInterface(Ci.nsIResProtocolHandler);
return handler.resolveURI(Services.io.newURI(aURI, null, null));
}
return aURI;
}
function sendMessageToJava(message) {
return Services.androidBridge.handleGeckoMessage(JSON.stringify(message));
}
function BannerMessage(options) {
let uuidgen = Cc["@mozilla.org/uuid-generator;1"].getService(Ci.nsIUUIDGenerator);
this.id = uuidgen.generateUUID().toString();
if ("text" in options && options.text != null)
this.text = options.text;
if ("icon" in options && options.icon != null)
this.iconURI = resolveGeckoURI(options.icon);
if ("onclick" in options && typeof options.onclick === "function")
this.onclick = options.onclick;
}
let HomeBanner = {
// Holds the messages that will rotate through the banner.
_messages: {},
// A queue used to keep track of which message to show next.
_queue: [],
observe: function(subject, topic, data) {
switch(topic) {
case "HomeBanner:Get":
this._handleGet();
break;
case "HomeBanner:Click":
this._handleClick(data);
break;
}
},
_handleGet: function() {
// Get the message from the front of the queue, then add it back
// to the end of the queue to show it again later.
let id = this._queue.shift();
this._queue.push(id);
let message = this._messages[id];
sendMessageToJava({
type: "HomeBanner:Data",
id: message.id,
text: message.text,
iconURI: message.iconURI
});
},
_handleClick: function(id) {
let message = this._messages[id];
if (message.onclick)
message.onclick();
},
/**
* Adds a new banner message to the rotation.
*
* @return id Unique identifer for the message.
*/
add: function(options) {
let message = new BannerMessage(options);
this._messages[message.id] = message;
// Add the new message to the end of the queue.
this._queue.push(message.id);
// If this is the first message we're adding, add
// observers to listen for requests from the Java UI.
if (Object.keys(this._messages).length == 1) {
Services.obs.addObserver(this, "HomeBanner:Get", false);
Services.obs.addObserver(this, "HomeBanner:Click", false);
}
return message.id;
},
/**
* Removes a banner message from the rotation.
*
* @param id The id of the message to remove.
*/
remove: function(id) {
delete this._messages[id];
// Remove the message from the queue.
let index = this._queue.indexOf(id);
this._queue.splice(index, 1);
// If there are no more messages, remove the observers.
if (Object.keys(this._messages).length == 0) {
Services.obs.removeObserver(this, "HomeBanner:Get");
Services.obs.removeObserver(this, "HomeBanner:Click");
}
}
};
// Public API
this.Home = {
banner: HomeBanner
}

Просмотреть файл

@ -6,6 +6,7 @@
EXTRA_JS_MODULES += [
'ContactService.jsm',
'Home.jsm',
'JNI.jsm',
'LightweightThemeConsumer.jsm',
'OrderedBroadcast.jsm',

Просмотреть файл

@ -12,6 +12,7 @@ background/bagheera/BagheeraClient.java
background/bagheera/BagheeraRequestDelegate.java
background/bagheera/BoundedByteArrayEntity.java
background/bagheera/DeflateHelper.java
background/common/DateUtils.java
background/common/log/Logger.java
background/common/log/writers/AndroidLevelCachingLogWriter.java
background/common/log/writers/AndroidLogWriter.java

Просмотреть файл

@ -288,9 +288,17 @@ EventLoopStack.prototype = {
* The URL of the debuggee who pushed the event loop on top of the stack.
*/
get lastPausedUrl() {
return this.size > 0
? this._inspector.lastNestRequestor.url
: null;
let url = null;
if (this.size > 0) {
try {
url = this._inspector.lastNestRequestor.url
} catch (e) {
// The tab's URL getter may throw if the tab is destroyed by the time
// this code runs, but we don't really care at this point.
dumpn(e);
}
}
return url;
},
/**
@ -936,7 +944,7 @@ ThreadActor.prototype = {
// In case of multiple nested event loops (due to multiple debuggers open in
// different tabs or multiple debugger clients connected to the same tab)
// only allow resumption in a LIFO order.
if (this._nestedEventLoops.size
if (this._nestedEventLoops.size && this._nestedEventLoops.lastPausedUrl
&& this._nestedEventLoops.lastPausedUrl !== this._hooks.url) {
return {
error: "wrongOrder",

Просмотреть файл

@ -249,7 +249,7 @@ nsLookAndFeel::NativeGetColor(ColorID aID, nscolor &aColor)
aColor = GetColorFromNSColor([NSColor selectedMenuItemTextColor]);
break;
case eColorID__moz_mac_disabledtoolbartext:
aColor = NS_RGB(0x3F,0x3F,0x3F);
aColor = GetColorFromNSColor([NSColor disabledControlTextColor]);
break;
case eColorID__moz_mac_menuselect:
aColor = GetColorFromNSColor([NSColor alternateSelectedControlColor]);

Просмотреть файл

@ -75,7 +75,7 @@ var colors = {
"-moz-mac-menushadow": ["rgb(163, 163, 163)"],
"-moz-mac-menutextdisable": ["rgb(152, 152, 152)", "rgb(136, 136, 136)"],
"-moz-mac-menutextselect": ["rgb(255, 255, 255)"],
"-moz-mac-disabledtoolbartext": ["rgb(63, 63, 63)"],
"-moz-mac-disabledtoolbartext": ["rgb(127, 127, 127)"],
"-moz-mac-secondaryhighlight": ["rgb(212, 212, 212)"],
"-moz-menuhover": ["rgb(115, 132, 153)", "rgb(127, 127, 127)", "rgb(56, 117, 215)", "rgb(255, 193, 31)", "rgb(243, 70, 72)", "rgb(255, 138, 34)", "rgb(102, 197, 71)", "rgb(140, 78, 184)"],
"-moz-menuhovertext": ["rgb(255, 255, 255)", "rgb(255, 254, 254)", "rgb(254, 255, 254)"],