зеркало из https://github.com/mozilla/gecko-dev.git
Merge fx-team to m-c
This commit is contained in:
Коммит
4b896261ba
|
@ -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)"],
|
||||
|
|
Загрузка…
Ссылка в новой задаче