mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
Merge f-t to m-c
This commit is contained in:
commit
8d0a796f41
@ -43,7 +43,8 @@
|
||||
<button id="home-button"></button>
|
||||
<button id="rotate-button"></button>
|
||||
</footer>
|
||||
#elifdef MOZ_WIDGET_COCOA
|
||||
#endif
|
||||
#ifdef MOZ_WIDGET_COCOA
|
||||
<!--
|
||||
If the document is empty at startup, we don't display the window
|
||||
at all on Mac OS...
|
||||
|
@ -1226,7 +1226,7 @@ pref("devtools.webconsole.filter.cssparser", false);
|
||||
pref("devtools.webconsole.filter.csslog", false);
|
||||
pref("devtools.webconsole.filter.exception", true);
|
||||
pref("devtools.webconsole.filter.jswarn", false);
|
||||
pref("devtools.webconsole.filter.jslog", true);
|
||||
pref("devtools.webconsole.filter.jslog", false);
|
||||
pref("devtools.webconsole.filter.error", true);
|
||||
pref("devtools.webconsole.filter.warn", true);
|
||||
pref("devtools.webconsole.filter.info", true);
|
||||
@ -1236,10 +1236,10 @@ pref("devtools.webconsole.filter.secwarn", true);
|
||||
|
||||
// Remember the Browser Console filters
|
||||
pref("devtools.browserconsole.filter.network", true);
|
||||
pref("devtools.browserconsole.filter.networkinfo", true);
|
||||
pref("devtools.browserconsole.filter.networkinfo", false);
|
||||
pref("devtools.browserconsole.filter.netwarn", true);
|
||||
pref("devtools.browserconsole.filter.csserror", true);
|
||||
pref("devtools.browserconsole.filter.cssparser", true);
|
||||
pref("devtools.browserconsole.filter.cssparser", false);
|
||||
pref("devtools.browserconsole.filter.csslog", false);
|
||||
pref("devtools.browserconsole.filter.exception", true);
|
||||
pref("devtools.browserconsole.filter.jswarn", true);
|
||||
|
@ -65,10 +65,13 @@ let gPage = {
|
||||
|
||||
/**
|
||||
* Updates the whole page and the grid when the storage has changed.
|
||||
* @param aOnlyIfHidden If true, the page is updated only if it's hidden in
|
||||
* the preloader.
|
||||
*/
|
||||
update: function Page_update() {
|
||||
update: function Page_update(aOnlyIfHidden=false) {
|
||||
let skipUpdate = aOnlyIfHidden && this.allowBackgroundCaptures;
|
||||
// The grid might not be ready yet as we initialize it asynchronously.
|
||||
if (gGrid.ready) {
|
||||
if (gGrid.ready && !skipUpdate) {
|
||||
gGrid.refresh();
|
||||
}
|
||||
},
|
||||
|
@ -24,3 +24,4 @@ skip-if = os == "mac" # Intermittent failures, bug 898317
|
||||
[browser_newtab_tabsync.js]
|
||||
[browser_newtab_undo.js]
|
||||
[browser_newtab_unpin.js]
|
||||
[browser_newtab_update.js]
|
||||
|
52
browser/base/content/test/newtab/browser_newtab_update.js
Normal file
52
browser/base/content/test/newtab/browser_newtab_update.js
Normal file
@ -0,0 +1,52 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
/**
|
||||
* Checks that newtab is updated as its links change.
|
||||
*/
|
||||
|
||||
function runTests() {
|
||||
if (NewTabUtils.allPages.updateScheduledForHiddenPages) {
|
||||
// Wait for dynamic updates triggered by the previous test to finish.
|
||||
yield whenPagesUpdated(null, true);
|
||||
}
|
||||
|
||||
// First, start with an empty page. setLinks will trigger a hidden page
|
||||
// update because it calls clearHistory. We need to wait for that update to
|
||||
// happen so that the next time we wait for a page update below, we catch the
|
||||
// right update and not the one triggered by setLinks.
|
||||
//
|
||||
// Why this weird way of yielding? First, these two functions don't return
|
||||
// promises, they call TestRunner.next when done. Second, the point at which
|
||||
// setLinks is done is independent of when the page update will happen, so
|
||||
// calling whenPagesUpdated cannot wait until that time.
|
||||
setLinks([]);
|
||||
whenPagesUpdated(null, true);
|
||||
yield null;
|
||||
yield null;
|
||||
|
||||
// Strategy: Add some visits, open a new page, check the grid, repeat.
|
||||
fillHistory([link(1)]);
|
||||
yield whenPagesUpdated(null, true);
|
||||
yield addNewTabPageTab();
|
||||
checkGrid("1,,,,,,,,");
|
||||
|
||||
fillHistory([link(2)]);
|
||||
yield whenPagesUpdated(null, true);
|
||||
yield addNewTabPageTab();
|
||||
checkGrid("2,1,,,,,,,");
|
||||
|
||||
fillHistory([link(1)]);
|
||||
yield whenPagesUpdated(null, true);
|
||||
yield addNewTabPageTab();
|
||||
checkGrid("1,2,,,,,,,");
|
||||
|
||||
fillHistory([link(2), link(3), link(4)]);
|
||||
yield whenPagesUpdated(null, true);
|
||||
yield addNewTabPageTab();
|
||||
checkGrid("2,1,3,4,,,,,");
|
||||
}
|
||||
|
||||
function link(id) {
|
||||
return { url: "http://example.com/#" + id, title: "site#" + id };
|
||||
}
|
@ -159,20 +159,34 @@ function clearHistory(aCallback) {
|
||||
|
||||
function fillHistory(aLinks, aCallback) {
|
||||
let numLinks = aLinks.length;
|
||||
if (!numLinks) {
|
||||
if (aCallback)
|
||||
executeSoon(aCallback);
|
||||
return;
|
||||
}
|
||||
|
||||
let transitionLink = Ci.nsINavHistoryService.TRANSITION_LINK;
|
||||
|
||||
for (let link of aLinks.reverse()) {
|
||||
// Important: To avoid test failures due to clock jitter on Windows XP, call
|
||||
// Date.now() once here, not each time through the loop.
|
||||
let now = Date.now() * 1000;
|
||||
|
||||
for (let i = 0; i < aLinks.length; i++) {
|
||||
let link = aLinks[i];
|
||||
let place = {
|
||||
uri: makeURI(link.url),
|
||||
title: link.title,
|
||||
visits: [{visitDate: Date.now() * 1000, transitionType: transitionLink}]
|
||||
// Links are secondarily sorted by visit date descending, so decrease the
|
||||
// visit date as we progress through the array so that links appear in the
|
||||
// grid in the order they're present in the array.
|
||||
visits: [{visitDate: now - i, transitionType: transitionLink}]
|
||||
};
|
||||
|
||||
PlacesUtils.asyncHistory.updatePlaces(place, {
|
||||
handleError: function () ok(false, "couldn't add visit to history"),
|
||||
handleResult: function () {},
|
||||
handleCompletion: function () {
|
||||
if (--numLinks == 0)
|
||||
if (--numLinks == 0 && aCallback)
|
||||
aCallback();
|
||||
}
|
||||
});
|
||||
@ -503,12 +517,18 @@ function createDragEvent(aEventType, aData) {
|
||||
|
||||
/**
|
||||
* Resumes testing when all pages have been updated.
|
||||
* @param aCallback Called when done. If not specified, TestRunner.next is used.
|
||||
* @param aOnlyIfHidden If true, this resumes testing only when an update that
|
||||
* applies to pre-loaded, hidden pages is observed. If
|
||||
* false, this resumes testing when any update is observed.
|
||||
*/
|
||||
function whenPagesUpdated(aCallback) {
|
||||
function whenPagesUpdated(aCallback, aOnlyIfHidden=false) {
|
||||
let page = {
|
||||
update: function () {
|
||||
NewTabUtils.allPages.unregister(this);
|
||||
executeSoon(aCallback || TestRunner.next);
|
||||
update: function (onlyIfHidden=false) {
|
||||
if (onlyIfHidden == aOnlyIfHidden) {
|
||||
NewTabUtils.allPages.unregister(this);
|
||||
executeSoon(aCallback || TestRunner.next);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -394,6 +394,9 @@ PlacesViewBase.prototype = {
|
||||
// Add "Open (Feed Name)" menuitem.
|
||||
aPopup._siteURIMenuitem = document.createElement("menuitem");
|
||||
aPopup._siteURIMenuitem.className = "openlivemarksite-menuitem";
|
||||
if (typeof this.options.extraClasses.entry == "string") {
|
||||
aPopup._siteURIMenuitem.classList.add(this.options.extraClasses.entry);
|
||||
}
|
||||
aPopup._siteURIMenuitem.setAttribute("targetURI", siteUrl);
|
||||
aPopup._siteURIMenuitem.setAttribute("oncommand",
|
||||
"openUILink(this.getAttribute('targetURI'), event);");
|
||||
@ -429,6 +432,9 @@ PlacesViewBase.prototype = {
|
||||
// Create the status menuitem and cache it in the popup object.
|
||||
statusMenuitem = document.createElement("menuitem");
|
||||
statusMenuitem.className = "livemarkstatus-menuitem";
|
||||
if (typeof this.options.extraClasses.entry == "string") {
|
||||
statusMenuitem.classList.add(this.options.extraClasses.entry);
|
||||
}
|
||||
statusMenuitem.setAttribute("disabled", true);
|
||||
aPopup._statusMenuitem = statusMenuitem;
|
||||
}
|
||||
|
@ -30,9 +30,5 @@ function test() {
|
||||
function newWindow(callback) {
|
||||
let opts = "chrome,all,dialog=no,height=800,width=800";
|
||||
let win = window.openDialog(getBrowserURL(), "_blank", opts);
|
||||
|
||||
win.addEventListener("load", function onLoad() {
|
||||
win.removeEventListener("load", onLoad, false);
|
||||
callback(win);
|
||||
}, false);
|
||||
whenDelayedStartupFinished(win, () => callback(win));
|
||||
}
|
||||
|
@ -5,3 +5,4 @@ support-files =
|
||||
manifest.webapp
|
||||
|
||||
[browser_manifest_editor.js]
|
||||
skip-if = os == "linux"
|
||||
|
@ -110,6 +110,7 @@ support-files =
|
||||
test-bug-952277-highlight-nodes-in-vview.html
|
||||
test-bug-609872-cd-iframe-parent.html
|
||||
test-bug-609872-cd-iframe-child.html
|
||||
test-bug-989025-iframe-parent.html
|
||||
|
||||
[browser_bug664688_sandbox_update_after_navigation.js]
|
||||
[browser_bug_638949_copy_link_location.js]
|
||||
@ -277,3 +278,4 @@ run-if = os == "mac"
|
||||
[browser_webconsole_start_netmon_first.js]
|
||||
[browser_webconsole_console_trace_duplicates.js]
|
||||
[browser_webconsole_cd_iframe.js]
|
||||
[browser_webconsole_autocomplete_crossdomain_iframe.js]
|
||||
|
@ -11,74 +11,63 @@ const TEST_URI = "data:text/html;charset=utf8,<title>bug859756</title>\n" +
|
||||
|
||||
function test()
|
||||
{
|
||||
addTab(TEST_URI);
|
||||
browser.addEventListener("load", function onLoad() {
|
||||
browser.removeEventListener("load", onLoad, true);
|
||||
const FILTER_PREF = "devtools.browserconsole.filter.jslog";
|
||||
Services.prefs.setBoolPref(FILTER_PREF, true);
|
||||
|
||||
registerCleanupFunction(() => {
|
||||
Services.prefs.clearUserPref(FILTER_PREF);
|
||||
});
|
||||
|
||||
Task.spawn(function*() {
|
||||
const {tab} = yield loadTab(TEST_URI);
|
||||
|
||||
// Test for cached nsIConsoleMessages.
|
||||
Services.console.logStringMessage("test1 for bug859756");
|
||||
|
||||
info("open web console");
|
||||
openConsole(null, consoleOpened);
|
||||
}, true);
|
||||
}
|
||||
let hud = yield openConsole(tab);
|
||||
|
||||
function consoleOpened(hud)
|
||||
{
|
||||
ok(hud, "web console opened");
|
||||
Services.console.logStringMessage("do-not-show-me");
|
||||
content.console.log("foobarz");
|
||||
ok(hud, "web console opened");
|
||||
Services.console.logStringMessage("do-not-show-me");
|
||||
content.console.log("foobarz");
|
||||
|
||||
waitForMessages({
|
||||
webconsole: hud,
|
||||
messages: [
|
||||
{
|
||||
yield waitForMessages({
|
||||
webconsole: hud,
|
||||
messages: [{
|
||||
text: "foobarz",
|
||||
category: CATEGORY_WEBDEV,
|
||||
severity: SEVERITY_LOG,
|
||||
},
|
||||
],
|
||||
}).then(() => {
|
||||
}],
|
||||
});
|
||||
|
||||
let text = hud.outputNode.textContent;
|
||||
is(text.indexOf("do-not-show-me"), -1,
|
||||
"nsIConsoleMessages are not displayed");
|
||||
is(text.indexOf("test1 for bug859756"), -1,
|
||||
"nsIConsoleMessages are not displayed (confirmed)");
|
||||
closeConsole(null, onWebConsoleClose);
|
||||
});
|
||||
}
|
||||
|
||||
function onWebConsoleClose()
|
||||
{
|
||||
info("web console closed");
|
||||
HUDService.toggleBrowserConsole().then(onBrowserConsoleOpen);
|
||||
}
|
||||
yield closeConsole(tab);
|
||||
|
||||
function onBrowserConsoleOpen(hud)
|
||||
{
|
||||
ok(hud, "browser console opened");
|
||||
Services.console.logStringMessage("test2 for bug859756");
|
||||
info("web console closed");
|
||||
hud = yield HUDService.toggleBrowserConsole();
|
||||
ok(hud, "browser console opened");
|
||||
|
||||
waitForMessages({
|
||||
webconsole: hud,
|
||||
messages: [
|
||||
{
|
||||
Services.console.logStringMessage("test2 for bug859756");
|
||||
|
||||
let results = yield waitForMessages({
|
||||
webconsole: hud,
|
||||
messages: [{
|
||||
text: "test1 for bug859756",
|
||||
category: CATEGORY_JS,
|
||||
},
|
||||
{
|
||||
}, {
|
||||
text: "test2 for bug859756",
|
||||
category: CATEGORY_JS,
|
||||
},
|
||||
{
|
||||
}, {
|
||||
text: "do-not-show-me",
|
||||
category: CATEGORY_JS,
|
||||
},
|
||||
],
|
||||
}).then(testFiltering);
|
||||
}],
|
||||
});
|
||||
|
||||
function testFiltering(results)
|
||||
{
|
||||
let msg = [...results[2].matched][0];
|
||||
ok(msg, "message element for do-not-show-me (nsIConsoleMessage)");
|
||||
isnot(msg.textContent.indexOf("do-not-show"), -1, "element content is correct");
|
||||
@ -87,9 +76,5 @@ function onBrowserConsoleOpen(hud)
|
||||
hud.setFilterState("jslog", false);
|
||||
|
||||
ok(msg.classList.contains("filtered-by-type"), "element is filtered");
|
||||
|
||||
hud.setFilterState("jslog", true);
|
||||
|
||||
finishTest();
|
||||
}
|
||||
}).then(finishTest);
|
||||
}
|
||||
|
@ -0,0 +1,59 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
// Test that autocomplete doesn't break when trying to reach into objects from
|
||||
// a different domain, bug 989025.
|
||||
|
||||
function test() {
|
||||
let hud;
|
||||
|
||||
const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test/test-bug-989025-iframe-parent.html";
|
||||
|
||||
Task.spawn(function*() {
|
||||
const {tab} = yield loadTab(TEST_URI);
|
||||
hud = yield openConsole(tab);
|
||||
|
||||
hud.jsterm.execute('document.title');
|
||||
|
||||
yield waitForMessages({
|
||||
webconsole: hud,
|
||||
messages: [{
|
||||
text: "989025 - iframe parent",
|
||||
category: CATEGORY_OUTPUT,
|
||||
}],
|
||||
});
|
||||
|
||||
let autocompleteUpdated = hud.jsterm.once("autocomplete-updated");
|
||||
|
||||
hud.jsterm.setInputValue("window[0].document");
|
||||
executeSoon(() => {
|
||||
EventUtils.synthesizeKey(".", {});
|
||||
});
|
||||
|
||||
yield autocompleteUpdated;
|
||||
|
||||
hud.jsterm.setInputValue("window[0].document.title");
|
||||
EventUtils.synthesizeKey("VK_RETURN", {});
|
||||
|
||||
yield waitForMessages({
|
||||
webconsole: hud,
|
||||
messages: [{
|
||||
text: "Permission denied",
|
||||
category: CATEGORY_OUTPUT,
|
||||
severity: SEVERITY_ERROR,
|
||||
}],
|
||||
});
|
||||
|
||||
hud.jsterm.execute("window.location");
|
||||
|
||||
yield waitForMessages({
|
||||
webconsole: hud,
|
||||
messages: [{
|
||||
text: "test-bug-989025-iframe-parent.html",
|
||||
category: CATEGORY_OUTPUT,
|
||||
}],
|
||||
});
|
||||
|
||||
yield closeConsole(tab);
|
||||
}).then(finishTest);
|
||||
}
|
@ -0,0 +1,13 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>test for bug 989025 - iframe parent</title>
|
||||
<!-- Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ -->
|
||||
</head>
|
||||
<body>
|
||||
<p>test for bug 989025 - iframe parent</p>
|
||||
<iframe src="http://mochi.test:8888/browser/browser/devtools/webconsole/test/test-bug-609872-cd-iframe-child.html"></iframe>
|
||||
</body>
|
||||
</html>
|
@ -645,15 +645,15 @@ FunctionEnd
|
||||
${WriteRegStr2} $1 "$0" "Publisher" "Mozilla" 0
|
||||
${WriteRegStr2} $1 "$0" "UninstallString" "$\"$8\uninstall\helper.exe$\"" 0
|
||||
DeleteRegValue SHCTX "$0" "URLInfoAbout"
|
||||
; Don't add URLInfoAbout which is the release notes url except for the release
|
||||
; Don't add URLUpdateInfo which is the release notes url except for the release
|
||||
; and esr channels since nightly, aurora, and beta do not have release notes.
|
||||
; Note: URLInfoAbout is only defined in the official branding.nsi.
|
||||
!ifdef URLInfoAbout
|
||||
; Note: URLUpdateInfo is only defined in the official branding.nsi.
|
||||
!ifdef URLUpdateInfo
|
||||
!ifndef BETA_UPDATE_CHANNEL
|
||||
${WriteRegStr2} $1 "$0" "URLInfoAbout" "${URLInfoAbout}" 0
|
||||
!endif
|
||||
!endif
|
||||
${WriteRegStr2} $1 "$0" "URLUpdateInfo" "${URLUpdateInfo}" 0
|
||||
!endif
|
||||
!endif
|
||||
${WriteRegStr2} $1 "$0" "URLInfoAbout" "${URLInfoAbout}" 0
|
||||
${WriteRegDWORD2} $1 "$0" "NoModify" 1 0
|
||||
${WriteRegDWORD2} $1 "$0" "NoRepair" 1 0
|
||||
|
||||
|
@ -86,7 +86,10 @@ paste-button.tooltiptext2 = Paste (%S)
|
||||
feed-button.label = Subscribe
|
||||
feed-button.tooltiptext = Subscribe to this page…
|
||||
|
||||
characterencoding-button.label = Character Encoding
|
||||
# LOCALIZATION NOTE (characterencoding-button.label): The \u00ad character at the beginning
|
||||
# of the string is used to disable auto hyphenation on the button text when it is displayed
|
||||
# in the menu panel.
|
||||
characterencoding-button.label = \u00adCharacter Encoding
|
||||
characterencoding-button.tooltiptext2 = Show Character Encoding options
|
||||
|
||||
email-link-button.label = Email Link
|
||||
|
@ -197,8 +197,7 @@ toolbarbutton.bookmark-item:not(.subviewbutton):not(#bookmarks-menu-button),
|
||||
margin: 0 !important;
|
||||
}
|
||||
|
||||
toolbarbutton.bookmark-item:not(.subviewbutton):not(#bookmarks-menu-button):hover,
|
||||
toolbarbutton.bookmark-item:not(#bookmarks-menu-button)[open="true"] {
|
||||
toolbarbutton.bookmark-item:not(.subviewbutton):not(#bookmarks-menu-button):hover {
|
||||
background-color: rgba(0, 0, 0, .205);
|
||||
}
|
||||
|
||||
@ -221,7 +220,7 @@ toolbarbutton.bookmark-item[open="true"]:not(.subviewbutton) {
|
||||
}
|
||||
|
||||
toolbarbutton.bookmark-item:not(.subviewbutton):not(#bookmarks-menu-button):active:hover,
|
||||
toolbarbutton.bookmark-item:not(#bookmarks-menu-button)[open="true"] {
|
||||
toolbarbutton.bookmark-item:not(.subviewbutton):not(#bookmarks-menu-button)[open="true"] {
|
||||
box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.4), 0 1px rgba(255, 255, 255, 0.4);
|
||||
background-color: rgba(0, 0, 0, .5);
|
||||
}
|
||||
|
@ -147,19 +147,19 @@ menulist {
|
||||
background-image: linear-gradient(#FFFFFF, rgba(255,255,255,0.1));
|
||||
}
|
||||
|
||||
button:not([disabled]):hover,
|
||||
menulist:not([disabled]):hover {
|
||||
button:not([disabled="true"]):hover,
|
||||
menulist:not([disabled="true"]):hover {
|
||||
background-image: linear-gradient(#FFFFFF, rgba(255,255,255,0.6));
|
||||
}
|
||||
|
||||
button:not([disabled]):hover:active,
|
||||
menulist[open="true"]:not([disabled]) {
|
||||
button:not([disabled="true"]):hover:active,
|
||||
menulist[open="true"]:not([disabled="true"]) {
|
||||
background-image: linear-gradient(rgba(255,255,255,0.1),
|
||||
rgba(255,255,255,0.6));
|
||||
}
|
||||
|
||||
button[disabled],
|
||||
menulist[disabled] {
|
||||
button[disabled="true"],
|
||||
menulist[disabled="true"] {
|
||||
background-image: linear-gradient(rgba(255,255,255,0.5),
|
||||
rgba(255,255,255,0.1));
|
||||
border-color: rgba(23,50,77,0.25);
|
||||
@ -208,7 +208,7 @@ button[type="menu"] > .button-box > .button-menu-dropmarker {
|
||||
list-style-image: url("chrome://global/skin/arrow/arrow-up.gif");
|
||||
}
|
||||
|
||||
.spinbuttons-up[disabled] > .button-box > .button-icon {
|
||||
.spinbuttons-up[disabled="true"] > .button-box > .button-icon {
|
||||
list-style-image: url("chrome://global/skin/arrow/arrow-up-dis.gif");
|
||||
}
|
||||
|
||||
@ -216,7 +216,7 @@ button[type="menu"] > .button-box > .button-menu-dropmarker {
|
||||
list-style-image: url("chrome://global/skin/arrow/arrow-dn.gif");
|
||||
}
|
||||
|
||||
.spinbuttons-down[disabled] > .button-box > .button-icon {
|
||||
.spinbuttons-down[disabled="true"] > .button-box > .button-icon {
|
||||
list-style-image: url("chrome://global/skin/arrow/arrow-dn-dis.gif");
|
||||
}
|
||||
|
||||
@ -229,7 +229,7 @@ menulist:not([editable="true"]) > .menulist-dropmarker {
|
||||
list-style-image: url("chrome://browser/skin/preferences/in-content/dropdown.png")
|
||||
}
|
||||
|
||||
menulist[disabled]:not([editable="true"]) > .menulist-dropmarker {
|
||||
menulist[disabled="true"]:not([editable="true"]) > .menulist-dropmarker {
|
||||
list-style-image: url("chrome://browser/skin/preferences/in-content/dropdown-disabled.png")
|
||||
}
|
||||
|
||||
@ -297,7 +297,7 @@ textbox[focused] {
|
||||
box-shadow: 0 0 2px 2px rgba(0,150,220,0.35), inset 0 0 2px 0 #0096DC;
|
||||
}
|
||||
|
||||
textbox[disabled] {
|
||||
textbox[disabled="true"] {
|
||||
color: rgba(115,121,128,0.5);
|
||||
border-color: rgba(23,50,77,0.25);
|
||||
background-image: linear-gradient(rgba(255,255,255,0.5), rgba(255,255,255,0.4));
|
||||
|
@ -270,18 +270,21 @@ case "$target" in
|
||||
fi
|
||||
|
||||
# Get the api level from "$android_sdk"/source.properties.
|
||||
android_api_level=`$AWK -F = changequote(<<, >>)'<<$>>1 == "AndroidVersion.ApiLevel" {print <<$>>2}'changequote([, ]) "$android_sdk"/source.properties`
|
||||
ANDROID_TARGET_SDK=`$AWK -F = changequote(<<, >>)'<<$>>1 == "AndroidVersion.ApiLevel" {print <<$>>2}'changequote([, ]) "$android_sdk"/source.properties`
|
||||
|
||||
if test -z "$android_api_level" ; then
|
||||
if test -z "$ANDROID_TARGET_SDK" ; then
|
||||
AC_MSG_ERROR([Unexpected error: no AndroidVersion.ApiLevel field has been found in source.properties.])
|
||||
fi
|
||||
|
||||
if ! test "$android_api_level" -eq "$android_api_level" ; then
|
||||
AC_MSG_ERROR([Unexpected error: the found android api value isn't a number! (found $android_api_level)])
|
||||
AC_DEFINE_UNQUOTED(ANDROID_TARGET_SDK,$ANDROID_TARGET_SDK)
|
||||
AC_SUBST(ANDROID_TARGET_SDK)
|
||||
|
||||
if ! test "$ANDROID_TARGET_SDK" -eq "$ANDROID_TARGET_SDK" ; then
|
||||
AC_MSG_ERROR([Unexpected error: the found android api value isn't a number! (found $ANDROID_TARGET_SDK)])
|
||||
fi
|
||||
|
||||
if test $android_api_level -lt $1 ; then
|
||||
AC_MSG_ERROR([The given Android SDK provides API level $android_api_level ($1 or higher required).])
|
||||
if test $ANDROID_TARGET_SDK -lt $1 ; then
|
||||
AC_MSG_ERROR([The given Android SDK provides API level $ANDROID_TARGET_SDK ($1 or higher required).])
|
||||
fi
|
||||
fi
|
||||
|
||||
|
34
configure.in
34
configure.in
@ -6250,31 +6250,41 @@ dnl minimum minor version of Unicode NSIS isn't in the path
|
||||
dnl (unless in case of cross compiling, for which Unicode
|
||||
dnl is not yet sufficient).
|
||||
if test "$OS_ARCH" = "WINNT"; then
|
||||
REQ_NSIS_MAJOR_VER=2
|
||||
MIN_NSIS_MAJOR_VER=2
|
||||
MIN_NSIS_MINOR_VER=46
|
||||
MOZ_PATH_PROGS(MAKENSISU, $MAKENSISU makensisu-2.46 makensis)
|
||||
MOZ_PATH_PROGS(MAKENSISU, $MAKENSISU makensisu-3.0a2.exe makensisu-2.46.exe makensis)
|
||||
if test -n "$MAKENSISU" -a "$MAKENSISU" != ":"; then
|
||||
AC_MSG_RESULT([yes])
|
||||
MAKENSISU_VER=`"$MAKENSISU" -version 2>/dev/null`
|
||||
changequote(,)
|
||||
MAKENSISU_VER=`"$MAKENSISU" -version 2>/dev/null | sed -e '/-Unicode/!s/.*//g' -e 's/^v\([0-9]\+\.[0-9]\+\).*\-Unicode$/\1/g'`
|
||||
MAKENSISU_PARSED_VER=`echo "$MAKENSISU_VER" | sed -e '/-Unicode/!s/.*//g' -e 's/^v\([0-9]\+\.[0-9]\+\).*\-Unicode$/\1/g'`
|
||||
changequote([,])
|
||||
if test ! "$MAKENSISU_VER" = ""; then
|
||||
MAKENSISU_MAJOR_VER=`echo $MAKENSISU_VER | $AWK -F\. '{ print $1 }'`
|
||||
MAKENSISU_MINOR_VER=`echo $MAKENSISU_VER | $AWK -F\. '{ print $2 }'`
|
||||
if test "$MAKENSISU_PARSED_VER" = ""; then
|
||||
changequote(,)
|
||||
MAKENSISU_PARSED_VER=`echo "$MAKENSISU_VER" | sed -e 's/^v\([0-9]\+\.[0-9]\+\).*$/\1/g'`
|
||||
changequote([,])
|
||||
fi
|
||||
AC_MSG_CHECKING([for Unicode NSIS with major version == $REQ_NSIS_MAJOR_VER and minor version >= $MIN_NSIS_MINOR_VER])
|
||||
if test "$MAKENSISU_VER" = "" || \
|
||||
test ! "$MAKENSISU_MAJOR_VER" = "$REQ_NSIS_MAJOR_VER" -o \
|
||||
! "$MAKENSISU_MINOR_VER" -ge $MIN_NSIS_MINOR_VER; then
|
||||
MAKENSISU_MAJOR_VER=0
|
||||
MAKENSISU_MINOR_VER=0
|
||||
if test ! "$MAKENSISU_PARSED_VER" = ""; then
|
||||
MAKENSISU_MAJOR_VER=`echo $MAKENSISU_PARSED_VER | $AWK -F\. '{ print $1 }'`
|
||||
MAKENSISU_MINOR_VER=`echo $MAKENSISU_PARSED_VER | $AWK -F\. '{ print $2 }'`
|
||||
fi
|
||||
AC_MSG_CHECKING([for Unicode NSIS version $MIN_NSIS_MAJOR_VER.$MIN_NSIS_MINOR_VER or greater])
|
||||
if test "$MAKENSISU_MAJOR_VER" -eq $MIN_NSIS_MAJOR_VER -a \
|
||||
"$MAKENSISU_MINOR_VER" -ge $MIN_NSIS_MINOR_VER ||
|
||||
test "$MAKENSISU_MAJOR_VER" -gt $MIN_NSIS_MAJOR_VER; then
|
||||
AC_MSG_RESULT([yes])
|
||||
else
|
||||
AC_MSG_RESULT([no])
|
||||
if test -z "$CROSS_COMPILE"; then
|
||||
AC_MSG_ERROR([To build the installer you must have the latest MozillaBuild or Unicode NSIS with a major version of $REQ_NSIS_MAJOR_VER and a minimum minor version of $MIN_NSIS_MINOR_VER in your path.])
|
||||
AC_MSG_ERROR([To build the installer you must have the latest MozillaBuild or Unicode NSIS version $REQ_NSIS_MAJOR_VER.$MIN_NSIS_MINOR_VER or greater in your path.])
|
||||
else
|
||||
MAKENSISU=
|
||||
fi
|
||||
fi
|
||||
elif test -z "$CROSS_COMPILE"; then
|
||||
AC_MSG_ERROR([To build the installer you must have the latest MozillaBuild or Unicode NSIS with a major version of $REQ_NSIS_MAJOR_VER and a minimum minor version of $MIN_NSIS_MINOR_VER in your path.])
|
||||
AC_MSG_ERROR([To build the installer you must have the latest MozillaBuild or Unicode NSIS version $REQ_NSIS_MAJOR_VER.$MIN_NSIS_MINOR_VER or greater in your path.])
|
||||
else
|
||||
MAKENSISU=
|
||||
fi
|
||||
|
@ -1,10 +1,11 @@
|
||||
#filter substitution
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="org.mozilla.geckoviewexample"
|
||||
android:versionCode="1"
|
||||
android:versionName="1.0">
|
||||
<uses-sdk android:minSdkVersion="8"
|
||||
android:targetSdkVersion="16"/>
|
||||
android:targetSdkVersion="@ANDROID_TARGET_SDK@"/>
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
|
||||
<uses-permission android:name="android.permission.INTERNET"/>
|
||||
<uses-feature android:glEsVersion="0x00020000" android:required="true" />
|
@ -1,3 +1,7 @@
|
||||
PP_TARGETS = properties manifest
|
||||
|
||||
manifest = AndroidManifest.xml.in
|
||||
|
||||
include $(topsrcdir)/config/rules.mk
|
||||
|
||||
GARBAGE = \
|
||||
@ -22,7 +26,7 @@ GARBAGE_DIRS = \
|
||||
|
||||
ANDROID=$(ANDROID_SDK)/../../tools/android
|
||||
|
||||
TARGET= $(notdir $(ANDROID_SDK))
|
||||
TARGET="android-$(ANDROID_TARGET_SDK)"
|
||||
|
||||
PACKAGE_DEPS = \
|
||||
assets/libxul.so \
|
||||
@ -35,22 +39,20 @@ PACKAGE_DEPS = \
|
||||
$(CURDIR)/res/layout/main.xml: $(srcdir)/main.xml
|
||||
$(NSINSTALL) $(srcdir)/main.xml res/layout/
|
||||
|
||||
$(CURDIR)/AndroidManifest.xml: $(srcdir)/AndroidManifest.xml
|
||||
$(NSINSTALL) $(srcdir)/AndroidManifest.xml $(CURDIR)
|
||||
|
||||
src/org/mozilla/geckoviewexample/GeckoViewExample.java: $(srcdir)/GeckoViewExample.java
|
||||
$(NSINSTALL) $(srcdir)/GeckoViewExample.java src/org/mozilla/geckoviewexample/
|
||||
|
||||
assets/libxul.so: $(DIST)/geckoview_library/geckoview_assets.zip FORCE
|
||||
$(UNZIP) -o $(DIST)/geckoview_library/geckoview_assets.zip
|
||||
|
||||
build.xml:
|
||||
build.xml: $(CURDIR)/AndroidManifest.xml
|
||||
mv AndroidManifest.xml AndroidManifest.xml.save
|
||||
$(ANDROID) create project --name GeckoViewExample --target $(TARGET) --path $(CURDIR) --activity GeckoViewExample --package org.mozilla.geckoviewexample
|
||||
$(ANDROID) update project --target $(TARGET) --path $(CURDIR) --library $(DEPTH)/mobile/android/geckoview_library
|
||||
$(RM) $(CURDIR)/res/layout/main.xml
|
||||
$(NSINSTALL) $(srcdir)/main.xml res/layout/
|
||||
$(RM) $(CURDIR)/AndroidManifest.xml
|
||||
$(NSINSTALL) $(srcdir)/AndroidManifest.xml $(CURDIR)
|
||||
$(RM) AndroidManifest.xml
|
||||
mv AndroidManifest.xml.save AndroidManifest.xml
|
||||
echo jar.libs.dir=libs >> project.properties
|
||||
|
||||
package: $(PACKAGE_DEPS) FORCE
|
||||
|
@ -10,7 +10,7 @@
|
||||
#endif
|
||||
>
|
||||
<uses-sdk android:minSdkVersion="8"
|
||||
android:targetSdkVersion="16"/>
|
||||
android:targetSdkVersion="@ANDROID_TARGET_SDK@"/>
|
||||
|
||||
#include ../services/manifests/AnnouncementsAndroidManifest_permissions.xml.in
|
||||
#include ../services/manifests/FxAccountAndroidManifest_permissions.xml.in
|
||||
|
@ -19,6 +19,8 @@ import java.util.concurrent.CopyOnWriteArrayList;
|
||||
public final class EventDispatcher {
|
||||
private static final String LOGTAG = "GeckoEventDispatcher";
|
||||
private static final String GUID = "__guid__";
|
||||
private static final String SUFFIX_RETURN = "Return";
|
||||
private static final String SUFFIX_ERROR = "Error";
|
||||
|
||||
private final Map<String, CopyOnWriteArrayList<GeckoEventListener>> mEventListeners
|
||||
= new HashMap<String, CopyOnWriteArrayList<GeckoEventListener>>();
|
||||
@ -102,21 +104,22 @@ public final class EventDispatcher {
|
||||
|
||||
}
|
||||
|
||||
public static void sendResponse(JSONObject message, JSONObject response) {
|
||||
try {
|
||||
response.put(GUID, message.getString(GUID));
|
||||
GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent(message.getString("type") + ":Return", response.toString()));
|
||||
} catch (Exception ex) {
|
||||
Log.e(LOGTAG, "Unable to send response", ex);
|
||||
}
|
||||
public static void sendResponse(JSONObject message, Object response) {
|
||||
sendResponseHelper(SUFFIX_RETURN, message, response);
|
||||
}
|
||||
|
||||
public static void sendError(JSONObject message, JSONObject error) {
|
||||
public static void sendError(JSONObject message, Object response) {
|
||||
sendResponseHelper(SUFFIX_ERROR, message, response);
|
||||
}
|
||||
|
||||
private static void sendResponseHelper(String suffix, JSONObject message, Object response) {
|
||||
try {
|
||||
error.put(GUID, message.getString(GUID));
|
||||
GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent(message.getString("type") + ":Error", error.toString()));
|
||||
} catch (Exception ex) {
|
||||
Log.e(LOGTAG, "Unable to send error", ex);
|
||||
final JSONObject wrapper = new JSONObject();
|
||||
wrapper.put(GUID, message.getString(GUID));
|
||||
wrapper.put("response", response);
|
||||
GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent(message.getString("type") + ":" + suffix, wrapper.toString()));
|
||||
} catch (Exception e) {
|
||||
Log.e(LOGTAG, "Unable to send " + suffix, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -401,7 +401,7 @@ public class AndroidSubmissionClient implements SubmissionClient {
|
||||
String generationProfilePath) throws JSONException {
|
||||
final JSONObject document;
|
||||
// If the given profilePath matches the one we cached for the tracker, use the cached env.
|
||||
if (generationProfilePath == profilePath) {
|
||||
if (profilePath != null && profilePath.equals(generationProfilePath)) {
|
||||
final Environment environment = getCurrentEnvironment();
|
||||
document = super.generateDocument(since, lastPingTime, environment);
|
||||
} else {
|
||||
|
@ -51,15 +51,4 @@
|
||||
<item name="android:paddingTop">30dp</item>
|
||||
</style>
|
||||
|
||||
<!--
|
||||
The content of the banner should align with the Grid/List views
|
||||
in BookmarksPanel. BookmarksListView has a 120dp padding and
|
||||
the TwoLinePageRows have a 50dp padding. Hence HomeBanner should
|
||||
have 170dp padding.
|
||||
-->
|
||||
<style name="Widget.HomeBanner">
|
||||
<item name="android:paddingLeft">170dp</item>
|
||||
<item name="android:paddingRight">170dp</item>
|
||||
</style>
|
||||
|
||||
</resources>
|
||||
|
@ -8,7 +8,6 @@ import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Set;
|
||||
|
||||
import org.mozilla.gecko.background.common.log.Logger;
|
||||
|
||||
@ -28,18 +27,17 @@ public class InfoCollections {
|
||||
*/
|
||||
final Map<String, Long> timestamps;
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public InfoCollections() {
|
||||
this(new ExtendedJSONObject());
|
||||
}
|
||||
|
||||
public InfoCollections(final ExtendedJSONObject record) {
|
||||
Logger.debug(LOG_TAG, "info/collections is " + record.toJSONString());
|
||||
HashMap<String, Long> map = new HashMap<String, Long>();
|
||||
|
||||
Set<Entry<String, Object>> entrySet = record.object.entrySet();
|
||||
|
||||
String key;
|
||||
Object value;
|
||||
for (Entry<String, Object> entry : entrySet) {
|
||||
key = entry.getKey();
|
||||
value = entry.getValue();
|
||||
for (Entry<String, Object> entry : record.entrySet()) {
|
||||
final String key = entry.getKey();
|
||||
final Object value = entry.getValue();
|
||||
|
||||
// These objects are most likely going to be Doubles. Regardless, we
|
||||
// want to get them in a more sane time format.
|
||||
|
@ -161,4 +161,24 @@ public abstract class MiddlewareRepositorySession extends RepositorySession {
|
||||
public void storeDone(long storeEnd) {
|
||||
inner.storeDone(storeEnd);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean shouldSkip() {
|
||||
return inner.shouldSkip();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean dataAvailable() {
|
||||
return inner.dataAvailable();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unbundle(RepositorySessionBundle bundle) {
|
||||
inner.unbundle(bundle);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getLastSyncTimestamp() {
|
||||
return inner.getLastSyncTimestamp();
|
||||
}
|
||||
}
|
||||
|
@ -6,6 +6,7 @@ package org.mozilla.gecko.sync.repositories;
|
||||
|
||||
import java.net.URISyntaxException;
|
||||
|
||||
import org.mozilla.gecko.sync.InfoCollections;
|
||||
import org.mozilla.gecko.sync.net.AuthHeaderProvider;
|
||||
|
||||
/**
|
||||
@ -19,8 +20,8 @@ public class ConstrainedServer11Repository extends Server11Repository {
|
||||
private String sort = null;
|
||||
private long limit = -1;
|
||||
|
||||
public ConstrainedServer11Repository(String collection, String storageURL, AuthHeaderProvider authHeaderProvider, long limit, String sort) throws URISyntaxException {
|
||||
super(collection, storageURL, authHeaderProvider);
|
||||
public ConstrainedServer11Repository(String collection, String storageURL, AuthHeaderProvider authHeaderProvider, InfoCollections infoCollections, long limit, String sort) throws URISyntaxException {
|
||||
super(collection, storageURL, authHeaderProvider, infoCollections);
|
||||
this.limit = limit;
|
||||
this.sort = sort;
|
||||
}
|
||||
|
@ -70,7 +70,11 @@ public abstract class RepositorySession {
|
||||
protected ExecutorService storeWorkQueue = Executors.newSingleThreadExecutor();
|
||||
|
||||
// The time that the last sync on this collection completed, in milliseconds since epoch.
|
||||
public long lastSyncTimestamp;
|
||||
private long lastSyncTimestamp = 0;
|
||||
|
||||
public long getLastSyncTimestamp() {
|
||||
return lastSyncTimestamp;
|
||||
}
|
||||
|
||||
public static long now() {
|
||||
return System.currentTimeMillis();
|
||||
@ -142,10 +146,6 @@ public abstract class RepositorySession {
|
||||
|
||||
public abstract void wipe(RepositorySessionWipeDelegate delegate);
|
||||
|
||||
public void unbundle(RepositorySessionBundle bundle) {
|
||||
this.lastSyncTimestamp = bundle == null ? 0 : bundle.getTimestamp();
|
||||
}
|
||||
|
||||
/**
|
||||
* Synchronously perform the shared work of beginning. Throws on failure.
|
||||
* @throws InvalidSessionTransitionException
|
||||
@ -174,8 +174,8 @@ public abstract class RepositorySession {
|
||||
delegate.deferredBeginDelegate(delegateQueue).onBeginSucceeded(this);
|
||||
}
|
||||
|
||||
protected RepositorySessionBundle getBundle() {
|
||||
return this.getBundle(null);
|
||||
public void unbundle(RepositorySessionBundle bundle) {
|
||||
this.lastSyncTimestamp = bundle == null ? 0 : bundle.getTimestamp();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -187,10 +187,11 @@ public abstract class RepositorySession {
|
||||
* The Synchronizer most likely wants to bump the bundle timestamp to be a value
|
||||
* return from a fetch call.
|
||||
*/
|
||||
protected RepositorySessionBundle getBundle(RepositorySessionBundle optional) {
|
||||
protected RepositorySessionBundle getBundle() {
|
||||
// Why don't we just persist the old bundle?
|
||||
RepositorySessionBundle bundle = (optional == null) ? new RepositorySessionBundle(this.lastSyncTimestamp) : optional;
|
||||
Logger.debug(LOG_TAG, "Setting bundle timestamp to " + this.lastSyncTimestamp + ".");
|
||||
long timestamp = getLastSyncTimestamp();
|
||||
RepositorySessionBundle bundle = new RepositorySessionBundle(timestamp);
|
||||
Logger.debug(LOG_TAG, "Setting bundle timestamp to " + timestamp + ".");
|
||||
|
||||
return bundle;
|
||||
}
|
||||
@ -201,7 +202,7 @@ public abstract class RepositorySession {
|
||||
*/
|
||||
public void abort(RepositorySessionFinishDelegate delegate) {
|
||||
this.abort();
|
||||
delegate.deferredFinishDelegate(delegateQueue).onFinishSucceeded(this, this.getBundle(null));
|
||||
delegate.deferredFinishDelegate(delegateQueue).onFinishSucceeded(this, this.getBundle());
|
||||
}
|
||||
|
||||
/**
|
||||
@ -233,7 +234,7 @@ public abstract class RepositorySession {
|
||||
public void finish(final RepositorySessionFinishDelegate delegate) throws InactiveSessionException {
|
||||
try {
|
||||
this.transitionFrom(SessionStatus.ACTIVE, SessionStatus.DONE);
|
||||
delegate.deferredFinishDelegate(delegateQueue).onFinishSucceeded(this, this.getBundle(null));
|
||||
delegate.deferredFinishDelegate(delegateQueue).onFinishSucceeded(this, this.getBundle());
|
||||
} catch (InvalidSessionTransitionException e) {
|
||||
Logger.error(LOG_TAG, "Tried to finish() an unstarted or already finished session");
|
||||
throw new InactiveSessionException(e);
|
||||
|
@ -8,6 +8,7 @@ import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.util.ArrayList;
|
||||
|
||||
import org.mozilla.gecko.sync.InfoCollections;
|
||||
import org.mozilla.gecko.sync.Utils;
|
||||
import org.mozilla.gecko.sync.net.AuthHeaderProvider;
|
||||
import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionCreationDelegate;
|
||||
@ -24,19 +25,31 @@ public class Server11Repository extends Repository {
|
||||
protected String collection;
|
||||
protected URI collectionURI;
|
||||
protected final AuthHeaderProvider authHeaderProvider;
|
||||
protected final InfoCollections infoCollections;
|
||||
|
||||
/**
|
||||
* Construct a new repository that fetches and stores against the Sync 1.1. API.
|
||||
*
|
||||
* @param collection name.
|
||||
* @param storageURL full URL to storage endpoint.
|
||||
* @param authHeaderProvider to use in requests.
|
||||
* @param authHeaderProvider to use in requests; may be null.
|
||||
* @param infoCollections instance; must not be null.
|
||||
* @throws URISyntaxException
|
||||
*/
|
||||
public Server11Repository(String collection, String storageURL, AuthHeaderProvider authHeaderProvider) throws URISyntaxException {
|
||||
public Server11Repository(String collection, String storageURL, AuthHeaderProvider authHeaderProvider, InfoCollections infoCollections) throws URISyntaxException {
|
||||
if (collection == null) {
|
||||
throw new IllegalArgumentException("collection must not be null");
|
||||
}
|
||||
if (storageURL == null) {
|
||||
throw new IllegalArgumentException("storageURL must not be null");
|
||||
}
|
||||
if (infoCollections == null) {
|
||||
throw new IllegalArgumentException("infoCollections must not be null");
|
||||
}
|
||||
this.collection = collection;
|
||||
this.collectionURI = new URI(storageURL + (storageURL.endsWith("/") ? collection : "/" + collection));
|
||||
this.authHeaderProvider = authHeaderProvider;
|
||||
this.infoCollections = infoCollections;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -102,4 +115,8 @@ public class Server11Repository extends Repository {
|
||||
public AuthHeaderProvider getAuthHeaderProvider() {
|
||||
return authHeaderProvider;
|
||||
}
|
||||
|
||||
public boolean updateNeeded(long lastSyncTimestamp) {
|
||||
return infoCollections.updateNeeded(collection, lastSyncTimestamp);
|
||||
}
|
||||
}
|
||||
|
@ -411,6 +411,7 @@ public class Server11RepositorySession extends RepositorySession {
|
||||
*/
|
||||
protected volatile boolean recordUploadFailed;
|
||||
|
||||
@Override
|
||||
public void begin(RepositorySessionBeginDelegate delegate) throws InvalidSessionTransitionException {
|
||||
recordUploadFailed = false;
|
||||
super.begin(delegate);
|
||||
@ -608,4 +609,9 @@ public class Server11RepositorySession extends RepositorySession {
|
||||
request.post(body);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean dataAvailable() {
|
||||
return serverRepository.updateNeeded(getLastSyncTimestamp());
|
||||
}
|
||||
}
|
||||
|
@ -40,6 +40,7 @@ public class WebViewActivity extends SyncActivity {
|
||||
// Add a progress bar.
|
||||
final Activity activity = this;
|
||||
wv.setWebChromeClient(new WebChromeClient() {
|
||||
@Override
|
||||
public void onProgressChanged(WebView view, int progress) {
|
||||
// Activities and WebViews measure progress with different scales.
|
||||
// The progress meter will automatically disappear when we reach 100%
|
||||
|
@ -46,12 +46,13 @@ public class AndroidBrowserBookmarksServerSyncStage extends ServerSyncStage {
|
||||
final JSONRecordFetcher countsFetcher = new JSONRecordFetcher(session.config.infoCollectionCountsURL(), authHeaderProvider);
|
||||
String collection = getCollection();
|
||||
return new SafeConstrainedServer11Repository(
|
||||
collection,
|
||||
session.config.storageURL(),
|
||||
session.getAuthHeaderProvider(),
|
||||
BOOKMARKS_REQUEST_LIMIT,
|
||||
BOOKMARKS_SORT,
|
||||
countsFetcher);
|
||||
collection,
|
||||
session.config.storageURL(),
|
||||
session.getAuthHeaderProvider(),
|
||||
session.config.infoCollections,
|
||||
BOOKMARKS_REQUEST_LIMIT,
|
||||
BOOKMARKS_SORT,
|
||||
countsFetcher);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -49,6 +49,7 @@ public class AndroidBrowserHistoryServerSyncStage extends ServerSyncStage {
|
||||
collection,
|
||||
session.config.storageURL(),
|
||||
session.getAuthHeaderProvider(),
|
||||
session.config.infoCollections,
|
||||
HISTORY_REQUEST_LIMIT,
|
||||
HISTORY_SORT);
|
||||
}
|
||||
|
@ -212,12 +212,7 @@ public class EnsureClusterURLStage extends AbstractNonRepositorySyncStage {
|
||||
callback.informNodeAssigned(session, oldClusterURL, url); // No matter what, we're getting a new node/weave clusterURL.
|
||||
session.config.setClusterURL(url);
|
||||
|
||||
ThreadPool.run(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
session.advance();
|
||||
}
|
||||
});
|
||||
session.advance();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -41,11 +41,12 @@ public class FormHistoryServerSyncStage extends ServerSyncStage {
|
||||
protected Repository getRemoteRepository() throws URISyntaxException {
|
||||
String collection = getCollection();
|
||||
return new ConstrainedServer11Repository(
|
||||
collection,
|
||||
session.config.storageURL(),
|
||||
session.getAuthHeaderProvider(),
|
||||
FORM_HISTORY_REQUEST_LIMIT,
|
||||
FORM_HISTORY_SORT);
|
||||
collection,
|
||||
session.config.storageURL(),
|
||||
session.getAuthHeaderProvider(),
|
||||
session.config.infoCollections,
|
||||
FORM_HISTORY_REQUEST_LIMIT,
|
||||
FORM_HISTORY_SORT);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -7,6 +7,7 @@ package org.mozilla.gecko.sync.stage;
|
||||
import java.net.URISyntaxException;
|
||||
|
||||
import org.mozilla.gecko.background.common.log.Logger;
|
||||
import org.mozilla.gecko.sync.InfoCollections;
|
||||
import org.mozilla.gecko.sync.InfoCounts;
|
||||
import org.mozilla.gecko.sync.JSONRecordFetcher;
|
||||
import org.mozilla.gecko.sync.net.AuthHeaderProvider;
|
||||
@ -35,11 +36,15 @@ public class SafeConstrainedServer11Repository extends ConstrainedServer11Reposi
|
||||
public SafeConstrainedServer11Repository(String collection,
|
||||
String storageURL,
|
||||
AuthHeaderProvider authHeaderProvider,
|
||||
InfoCollections infoCollections,
|
||||
long limit,
|
||||
String sort,
|
||||
JSONRecordFetcher countFetcher)
|
||||
throws URISyntaxException {
|
||||
super(collection, storageURL, authHeaderProvider, limit, sort);
|
||||
super(collection, storageURL, authHeaderProvider, infoCollections, limit, sort);
|
||||
if (countFetcher == null) {
|
||||
throw new IllegalArgumentException("countFetcher must not be null");
|
||||
}
|
||||
this.countFetcher = countFetcher;
|
||||
}
|
||||
|
||||
@ -50,6 +55,8 @@ public class SafeConstrainedServer11Repository extends ConstrainedServer11Reposi
|
||||
}
|
||||
|
||||
public class CountCheckingServer11RepositorySession extends Server11RepositorySession {
|
||||
private static final String LOG_TAG = "CountCheckingServer11RepositorySession";
|
||||
|
||||
/**
|
||||
* The session will report no data available if this is a first sync
|
||||
* and the server has more data available than this limit.
|
||||
@ -64,7 +71,13 @@ public class SafeConstrainedServer11Repository extends ConstrainedServer11Reposi
|
||||
@Override
|
||||
public boolean shouldSkip() {
|
||||
// If this is a first sync, verify that we aren't going to blow through our limit.
|
||||
if (this.lastSyncTimestamp <= 0) {
|
||||
final long lastSyncTimestamp = getLastSyncTimestamp();
|
||||
if (lastSyncTimestamp > 0) {
|
||||
Logger.info(LOG_TAG, "Collection " + collection + " has already had a first sync: " +
|
||||
"timestamp is " + lastSyncTimestamp + "; " +
|
||||
"ignoring any updated counts and syncing as usual.");
|
||||
} else {
|
||||
Logger.info(LOG_TAG, "Collection " + collection + " is starting a first sync; checking counts.");
|
||||
|
||||
final InfoCounts counts;
|
||||
try {
|
||||
@ -77,6 +90,7 @@ public class SafeConstrainedServer11Repository extends ConstrainedServer11Reposi
|
||||
|
||||
Integer c = counts.getCount(collection);
|
||||
if (c == null) {
|
||||
Logger.info(LOG_TAG, "Fetched counts does not include collection " + collection + "; syncing as usual.");
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -148,7 +148,8 @@ public abstract class ServerSyncStage extends AbstractSessionManagingSyncStage i
|
||||
String collection = getCollection();
|
||||
return new Server11Repository(collection,
|
||||
session.config.storageURL(),
|
||||
session.getAuthHeaderProvider());
|
||||
session.getAuthHeaderProvider(),
|
||||
session.config.infoCollections);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -70,7 +70,6 @@ public class RecordsChannel implements
|
||||
public RepositorySession source;
|
||||
public RepositorySession sink;
|
||||
private RecordsChannelDelegate delegate;
|
||||
private long timestamp;
|
||||
private long fetchEnd = -1;
|
||||
|
||||
protected final AtomicInteger numFetched = new AtomicInteger();
|
||||
@ -82,7 +81,6 @@ public class RecordsChannel implements
|
||||
this.source = source;
|
||||
this.sink = sink;
|
||||
this.delegate = delegate;
|
||||
this.timestamp = source.lastSyncTimestamp;
|
||||
}
|
||||
|
||||
/*
|
||||
@ -155,6 +153,14 @@ public class RecordsChannel implements
|
||||
this.delegate.onFlowBeginFailed(this, new SessionNotBegunException(failed));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!source.dataAvailable()) {
|
||||
Logger.info(LOG_TAG, "No data available: short-circuiting flow from source " + source);
|
||||
long now = System.currentTimeMillis();
|
||||
this.delegate.onFlowCompleted(this, now, now);
|
||||
return;
|
||||
}
|
||||
|
||||
sink.setStoreDelegate(this);
|
||||
numFetched.set(0);
|
||||
numFetchFailed.set(0);
|
||||
@ -164,12 +170,12 @@ public class RecordsChannel implements
|
||||
this.consumer = new ConcurrentRecordConsumer(this);
|
||||
ThreadPool.run(this.consumer);
|
||||
waitingForQueueDone = true;
|
||||
source.fetchSince(timestamp, this);
|
||||
source.fetchSince(source.getLastSyncTimestamp(), this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Begin both sessions, invoking flow() when done.
|
||||
* @throws InvalidSessionTransitionException
|
||||
* @throws InvalidSessionTransitionException
|
||||
*/
|
||||
public void beginAndFlow() throws InvalidSessionTransitionException {
|
||||
Logger.trace(LOG_TAG, "Beginning source.");
|
||||
|
@ -148,15 +148,6 @@ implements RecordsChannelDelegate,
|
||||
return;
|
||||
}
|
||||
|
||||
if (!sessionA.dataAvailable() &&
|
||||
!sessionB.dataAvailable()) {
|
||||
Logger.info(LOG_TAG, "Neither session reports data available. Short-circuiting sync.");
|
||||
sessionA.abort();
|
||||
sessionB.abort();
|
||||
this.delegate.onSynchronizeSkipped(this);
|
||||
return;
|
||||
}
|
||||
|
||||
final SynchronizerSession session = this;
|
||||
|
||||
// TODO: failed record handling.
|
||||
|
@ -7254,7 +7254,7 @@ var WebappsUI = {
|
||||
manifestURL: aData.app.manifestURL,
|
||||
origin: origin
|
||||
}, (data) => {
|
||||
let profilePath = JSON.parse(data).profile;
|
||||
let profilePath = data.profile;
|
||||
if (!profilePath)
|
||||
return;
|
||||
|
||||
|
@ -1,3 +1,4 @@
|
||||
#filter substitution
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="org.mozilla.gecko"
|
||||
android:versionCode="1"
|
||||
@ -7,6 +8,6 @@
|
||||
<uses-permission android:name="android.permission.INTERNET"/>
|
||||
<uses-sdk
|
||||
android:minSdkVersion="8"
|
||||
android:targetSdkVersion="17" />
|
||||
android:targetSdkVersion="@ANDROID_TARGET_SDK@" />
|
||||
|
||||
</manifest>
|
@ -7,18 +7,16 @@ GECKOVIEW_LIBRARY_DEST = $(CURDIR)
|
||||
GECKOVIEW_LIBRARY_FILES := \
|
||||
.classpath \
|
||||
.project \
|
||||
AndroidManifest.xml \
|
||||
project.properties \
|
||||
build.xml \
|
||||
$(NULL)
|
||||
|
||||
PP_TARGETS = properties
|
||||
PP_TARGETS = properties manifest project
|
||||
|
||||
properties = local.properties.in
|
||||
properties_PATH = .
|
||||
properties_deps := $(patsubst %.in,%,$(properties))
|
||||
project = project.properties.in
|
||||
manifest = AndroidManifest.xml.in
|
||||
|
||||
GARBAGE = $(GECKOVIEW_LIBRARY_FILES) $(properties_deps)
|
||||
GARBAGE = $(GECKOVIEW_LIBRARY_FILES) local.properties project.properties AndroidManifest.xml
|
||||
|
||||
GARBAGE_DIRS = \
|
||||
bin \
|
||||
@ -33,7 +31,7 @@ include $(topsrcdir)/config/rules.mk
|
||||
|
||||
_ABS_DIST = $(abspath $(DIST))
|
||||
|
||||
package: $(properties_deps) FORCE
|
||||
package: local.properties project.properties AndroidManifest.xml FORCE
|
||||
# Make directory for the zips
|
||||
$(MKDIR) -p $(_ABS_DIST)/geckoview_library
|
||||
|
||||
|
@ -1,3 +1,4 @@
|
||||
#filter substitution
|
||||
# This file is automatically generated by Android Tools.
|
||||
# Do not modify this file -- YOUR CHANGES WILL BE ERASED!
|
||||
#
|
||||
@ -11,5 +12,5 @@
|
||||
#proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt
|
||||
|
||||
# Project target.
|
||||
target=android-16
|
||||
target=android-@ANDROID_TARGET_SDK@
|
||||
android.library=true
|
@ -42,7 +42,7 @@ let Accounts = Object.freeze({
|
||||
if (error) {
|
||||
deferred.reject(error);
|
||||
} else {
|
||||
deferred.resolve(JSON.parse(data).exists);
|
||||
deferred.resolve(data.exists);
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -123,10 +123,10 @@ var HelperApps = {
|
||||
let data = this._sendMessageSync(msg);
|
||||
if (!data)
|
||||
return [];
|
||||
return parseData(JSON.parse(data));
|
||||
return parseData(data);
|
||||
} else {
|
||||
sendMessageToJava(msg, function(data) {
|
||||
callback(parseData(JSON.parse(data)));
|
||||
callback(parseData(data));
|
||||
});
|
||||
}
|
||||
},
|
||||
@ -175,7 +175,7 @@ var HelperApps = {
|
||||
});
|
||||
|
||||
sendMessageToJava(msg, function(data) {
|
||||
callback(JSON.parse(data));
|
||||
callback(data);
|
||||
});
|
||||
} else {
|
||||
let msg = this._getMessage("Intent:Open", uri, {
|
||||
|
@ -27,8 +27,8 @@ function sendMessageToJava(aMessage, aCallback) {
|
||||
Services.obs.removeObserver(obs, aMessage.type + ":Return", false);
|
||||
Services.obs.removeObserver(obs, aMessage.type + ":Error", false);
|
||||
|
||||
aCallback(aTopic == aMessage.type + ":Return" ? aData : null,
|
||||
aTopic == aMessage.type + ":Error" ? aData : null)
|
||||
aCallback(aTopic == aMessage.type + ":Return" ? data.response : null,
|
||||
aTopic == aMessage.type + ":Error" ? data.response : null);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -159,17 +159,11 @@ Prompt.prototype = {
|
||||
show: function(callback) {
|
||||
this.callback = callback;
|
||||
log("Sending message");
|
||||
Services.obs.addObserver(this, "Prompt:Return", false);
|
||||
this._innerShow();
|
||||
},
|
||||
|
||||
_innerShow: function() {
|
||||
sendMessageToJava(this.msg, (aData) => {
|
||||
log("observe " + aData);
|
||||
let data = JSON.parse(aData);
|
||||
|
||||
Services.obs.removeObserver(this, "Prompt:Return", false);
|
||||
|
||||
sendMessageToJava(this.msg, (data) => {
|
||||
if (this.callback)
|
||||
this.callback(data);
|
||||
});
|
||||
|
@ -66,7 +66,7 @@ SharedPreferences.prototype = Object.freeze({
|
||||
preferences: prefs,
|
||||
branch: this._branch,
|
||||
}, (data) => {
|
||||
result = JSON.parse(data).values;
|
||||
result = data.values;
|
||||
});
|
||||
|
||||
let thread = Services.tm.currentThread;
|
||||
|
@ -338,7 +338,7 @@ this.WebappManager = {
|
||||
sendMessageToJava({
|
||||
type: "Webapps:GetApkVersions",
|
||||
packageNames: packageNames
|
||||
}, data => deferred.resolve(JSON.parse(data).versions));
|
||||
}, data => deferred.resolve(data.versions));
|
||||
|
||||
return deferred.promise;
|
||||
},
|
||||
|
@ -7,7 +7,7 @@
|
||||
android:versionName="1.0" >
|
||||
|
||||
<uses-sdk android:minSdkVersion="8"
|
||||
android:targetSdkVersion="16" />
|
||||
android:targetSdkVersion="@ANDROID_TARGET_SDK@" />
|
||||
|
||||
<uses-permission android:name="@ANDROID_BACKGROUND_TARGET_PACKAGE_NAME@.permissions.BROWSER_PROVIDER"/>
|
||||
<uses-permission android:name="@ANDROID_BACKGROUND_TARGET_PACKAGE_NAME@.permissions.FORMHISTORY_PROVIDER"/>
|
||||
|
@ -7,7 +7,7 @@
|
||||
android:versionName="1.0" >
|
||||
|
||||
<uses-sdk android:minSdkVersion="8"
|
||||
android:targetSdkVersion="16" />
|
||||
android:targetSdkVersion="@ANDROID_TARGET_SDK@" />
|
||||
|
||||
<uses-permission android:name="@ANDROID_BROWSER_TARGET_PACKAGE_NAME@.permissions.BROWSER_PROVIDER"/>
|
||||
<uses-permission android:name="@ANDROID_BROWSER_TARGET_PACKAGE_NAME@.permissions.FORMHISTORY_PROVIDER"/>
|
||||
|
@ -2338,6 +2338,22 @@ nsDownloadManager::OnTitleChanged(nsIURI *aURI,
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsDownloadManager::OnFrecencyChanged(nsIURI* aURI,
|
||||
int32_t aNewFrecency,
|
||||
const nsACString& aGUID,
|
||||
bool aHidden,
|
||||
PRTime aLastVisitDate)
|
||||
{
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsDownloadManager::OnManyFrecenciesChanged()
|
||||
{
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsDownloadManager::OnDeleteURI(nsIURI *aURI,
|
||||
const nsACString& aGUID,
|
||||
|
@ -941,6 +941,8 @@ Database::InitFunctions()
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
rv = FixupURLFunction::create(mMainConn);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
rv = FrecencyNotificationFunction::create(mMainConn);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
@ -1185,7 +1185,10 @@ private:
|
||||
if (aPlace.placeId) {
|
||||
stmt = mHistory->GetStatement(
|
||||
"UPDATE moz_places "
|
||||
"SET frecency = CALCULATE_FRECENCY(:page_id) "
|
||||
"SET frecency = NOTIFY_FRECENCY("
|
||||
"CALCULATE_FRECENCY(:page_id), "
|
||||
"url, guid, hidden, last_visit_date"
|
||||
") "
|
||||
"WHERE id = :page_id"
|
||||
);
|
||||
NS_ENSURE_STATE(stmt);
|
||||
@ -1195,7 +1198,9 @@ private:
|
||||
else {
|
||||
stmt = mHistory->GetStatement(
|
||||
"UPDATE moz_places "
|
||||
"SET frecency = CALCULATE_FRECENCY(id) "
|
||||
"SET frecency = NOTIFY_FRECENCY("
|
||||
"CALCULATE_FRECENCY(id), url, guid, hidden, last_visit_date"
|
||||
") "
|
||||
"WHERE url = :page_url"
|
||||
);
|
||||
NS_ENSURE_STATE(stmt);
|
||||
@ -2037,13 +2042,14 @@ History::InsertPlace(const VisitData& aPlace)
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
rv = URIBinder::Bind(stmt, NS_LITERAL_CSTRING("url"), aPlace.spec);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
nsString title = aPlace.title;
|
||||
// Empty strings should have no title, just like nsNavHistory::SetPageTitle.
|
||||
if (aPlace.title.IsEmpty()) {
|
||||
if (title.IsEmpty()) {
|
||||
rv = stmt->BindNullByName(NS_LITERAL_CSTRING("title"));
|
||||
}
|
||||
else {
|
||||
rv = stmt->BindStringByName(NS_LITERAL_CSTRING("title"),
|
||||
StringHead(aPlace.title, TITLE_LENGTH_MAX));
|
||||
title.Assign(StringHead(aPlace.title, TITLE_LENGTH_MAX));
|
||||
rv = stmt->BindStringByName(NS_LITERAL_CSTRING("title"), title);
|
||||
}
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("typed"), aPlace.typed);
|
||||
@ -2065,6 +2071,13 @@ History::InsertPlace(const VisitData& aPlace)
|
||||
rv = stmt->Execute();
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
// Post an onFrecencyChanged observer notification.
|
||||
const nsNavHistory* navHistory = nsNavHistory::GetConstHistoryService();
|
||||
NS_ENSURE_STATE(navHistory);
|
||||
navHistory->DispatchFrecencyChangedNotification(aPlace.spec, frecency, guid,
|
||||
aPlace.hidden,
|
||||
aPlace.visitTime);
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
|
@ -749,5 +749,63 @@ namespace places {
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
//// Frecency Changed Notification Function
|
||||
|
||||
/* static */
|
||||
nsresult
|
||||
FrecencyNotificationFunction::create(mozIStorageConnection *aDBConn)
|
||||
{
|
||||
nsRefPtr<FrecencyNotificationFunction> function =
|
||||
new FrecencyNotificationFunction();
|
||||
nsresult rv = aDBConn->CreateFunction(
|
||||
NS_LITERAL_CSTRING("notify_frecency"), 5, function
|
||||
);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMPL_ISUPPORTS1(
|
||||
FrecencyNotificationFunction,
|
||||
mozIStorageFunction
|
||||
)
|
||||
|
||||
NS_IMETHODIMP
|
||||
FrecencyNotificationFunction::OnFunctionCall(mozIStorageValueArray *aArgs,
|
||||
nsIVariant **_result)
|
||||
{
|
||||
uint32_t numArgs;
|
||||
nsresult rv = aArgs->GetNumEntries(&numArgs);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
MOZ_ASSERT(numArgs == 5);
|
||||
|
||||
int32_t newFrecency = aArgs->AsInt32(0);
|
||||
|
||||
nsAutoCString spec;
|
||||
rv = aArgs->GetUTF8String(1, spec);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
nsAutoCString guid;
|
||||
rv = aArgs->GetUTF8String(2, guid);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
bool hidden = static_cast<bool>(aArgs->AsInt32(3));
|
||||
PRTime lastVisitDate = static_cast<PRTime>(aArgs->AsInt64(4));
|
||||
|
||||
const nsNavHistory* navHistory = nsNavHistory::GetConstHistoryService();
|
||||
NS_ENSURE_STATE(navHistory);
|
||||
navHistory->DispatchFrecencyChangedNotification(spec, newFrecency, guid,
|
||||
hidden, lastVisitDate);
|
||||
|
||||
nsCOMPtr<nsIWritableVariant> result =
|
||||
do_CreateInstance("@mozilla.org/variant;1");
|
||||
NS_ENSURE_STATE(result);
|
||||
rv = result->SetAsInt32(newFrecency);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
NS_ADDREF(*_result = result);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
} // namespace places
|
||||
} // namespace mozilla
|
||||
|
@ -280,6 +280,43 @@ public:
|
||||
static nsresult create(mozIStorageConnection *aDBConn);
|
||||
};
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
//// Frecency Changed Notification Function
|
||||
|
||||
/**
|
||||
* For a given place, posts a runnable to the main thread that calls
|
||||
* onFrecencyChanged on nsNavHistory's nsINavHistoryObservers. The passed-in
|
||||
* newFrecency value is returned unchanged.
|
||||
*
|
||||
* @param newFrecency
|
||||
* The place's new frecency.
|
||||
* @param url
|
||||
* The place's URL.
|
||||
* @param guid
|
||||
* The place's GUID.
|
||||
* @param hidden
|
||||
* The place's hidden boolean.
|
||||
* @param lastVisitDate
|
||||
* The place's last visit date.
|
||||
* @return newFrecency
|
||||
*/
|
||||
class FrecencyNotificationFunction MOZ_FINAL : public mozIStorageFunction
|
||||
{
|
||||
public:
|
||||
NS_DECL_THREADSAFE_ISUPPORTS
|
||||
NS_DECL_MOZISTORAGEFUNCTION
|
||||
|
||||
/**
|
||||
* Registers the function with the specified database connection.
|
||||
*
|
||||
* @param aDBConn
|
||||
* The database connection to register with.
|
||||
*/
|
||||
static nsresult create(mozIStorageConnection *aDBConn);
|
||||
};
|
||||
|
||||
|
||||
} // namespace places
|
||||
} // namespace storage
|
||||
|
||||
|
@ -23,7 +23,7 @@ interface nsINavHistoryQueryOptions;
|
||||
interface nsINavHistoryResult;
|
||||
interface nsINavHistoryBatchCallback;
|
||||
|
||||
[scriptable, uuid(91d104bb-17ef-404b-9f9a-d9ed8de6824c)]
|
||||
[scriptable, uuid(7daefb58-6989-4d22-a471-54f0b19b778a)]
|
||||
interface nsINavHistoryResultNode : nsISupports
|
||||
{
|
||||
/**
|
||||
@ -610,7 +610,7 @@ interface nsINavHistoryResult : nsISupports
|
||||
* DANGER! If you are in the middle of a batch transaction, there may be a
|
||||
* database transaction active. You can still access the DB, but be careful.
|
||||
*/
|
||||
[scriptable, uuid(45e2970b-9b00-4473-9938-39d6beaf4248)]
|
||||
[scriptable, uuid(0f0f45b0-13a1-44ae-a0ab-c6046ec6d4da)]
|
||||
interface nsINavHistoryObserver : nsISupports
|
||||
{
|
||||
/**
|
||||
@ -675,6 +675,37 @@ interface nsINavHistoryObserver : nsISupports
|
||||
in AString aPageTitle,
|
||||
in ACString aGUID);
|
||||
|
||||
/**
|
||||
* Called when an individual page's frecency has changed.
|
||||
*
|
||||
* This is not called for pages whose frecencies change as the result of some
|
||||
* large operation where some large or unknown number of frecencies change at
|
||||
* once. Use onManyFrecenciesChanged to detect such changes.
|
||||
*
|
||||
* @param aURI
|
||||
* The page's URI.
|
||||
* @param aNewFrecency
|
||||
* The page's new frecency.
|
||||
* @param aGUID
|
||||
* The page's GUID.
|
||||
* @param aHidden
|
||||
* True if the page is marked as hidden.
|
||||
* @param aVisitDate
|
||||
* The page's last visit date.
|
||||
*/
|
||||
void onFrecencyChanged(in nsIURI aURI,
|
||||
in long aNewFrecency,
|
||||
in ACString aGUID,
|
||||
in boolean aHidden,
|
||||
in PRTime aVisitDate);
|
||||
|
||||
/**
|
||||
* Called when the frecencies of many pages have changed at once.
|
||||
*
|
||||
* onFrecencyChanged is not called for each of those pages.
|
||||
*/
|
||||
void onManyFrecenciesChanged();
|
||||
|
||||
/**
|
||||
* Removed by the user.
|
||||
*/
|
||||
|
@ -2841,6 +2841,24 @@ nsNavBookmarks::OnTitleChanged(nsIURI* aURI,
|
||||
}
|
||||
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsNavBookmarks::OnFrecencyChanged(nsIURI* aURI,
|
||||
int32_t aNewFrecency,
|
||||
const nsACString& aGUID,
|
||||
bool aHidden,
|
||||
PRTime aLastVisitDate)
|
||||
{
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsNavBookmarks::OnManyFrecenciesChanged()
|
||||
{
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsNavBookmarks::OnPageChanged(nsIURI* aURI,
|
||||
uint32_t aChangedAttribute,
|
||||
|
@ -535,6 +535,82 @@ nsNavHistory::NotifyTitleChange(nsIURI* aURI,
|
||||
nsINavHistoryObserver, OnTitleChanged(aURI, aTitle, aGUID));
|
||||
}
|
||||
|
||||
void
|
||||
nsNavHistory::NotifyFrecencyChanged(nsIURI* aURI,
|
||||
int32_t aNewFrecency,
|
||||
const nsACString& aGUID,
|
||||
bool aHidden,
|
||||
PRTime aLastVisitDate)
|
||||
{
|
||||
MOZ_ASSERT(!aGUID.IsEmpty());
|
||||
NOTIFY_OBSERVERS(mCanNotify, mCacheObservers, mObservers,
|
||||
nsINavHistoryObserver,
|
||||
OnFrecencyChanged(aURI, aNewFrecency, aGUID, aHidden,
|
||||
aLastVisitDate));
|
||||
}
|
||||
|
||||
void
|
||||
nsNavHistory::NotifyManyFrecenciesChanged()
|
||||
{
|
||||
NOTIFY_OBSERVERS(mCanNotify, mCacheObservers, mObservers,
|
||||
nsINavHistoryObserver,
|
||||
OnManyFrecenciesChanged());
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
class FrecencyNotification : public nsRunnable
|
||||
{
|
||||
public:
|
||||
FrecencyNotification(const nsACString& aSpec,
|
||||
int32_t aNewFrecency,
|
||||
const nsACString& aGUID,
|
||||
bool aHidden,
|
||||
PRTime aLastVisitDate)
|
||||
: mSpec(aSpec)
|
||||
, mNewFrecency(aNewFrecency)
|
||||
, mGUID(aGUID)
|
||||
, mHidden(aHidden)
|
||||
, mLastVisitDate(aLastVisitDate)
|
||||
{
|
||||
}
|
||||
|
||||
NS_IMETHOD Run()
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread(), "Must be called on the main thread");
|
||||
nsNavHistory* navHistory = nsNavHistory::GetHistoryService();
|
||||
if (navHistory) {
|
||||
nsCOMPtr<nsIURI> uri;
|
||||
(void)NS_NewURI(getter_AddRefs(uri), mSpec);
|
||||
navHistory->NotifyFrecencyChanged(uri, mNewFrecency, mGUID, mHidden,
|
||||
mLastVisitDate);
|
||||
}
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
private:
|
||||
nsCString mSpec;
|
||||
int32_t mNewFrecency;
|
||||
nsCString mGUID;
|
||||
bool mHidden;
|
||||
PRTime mLastVisitDate;
|
||||
};
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
void
|
||||
nsNavHistory::DispatchFrecencyChangedNotification(const nsACString& aSpec,
|
||||
int32_t aNewFrecency,
|
||||
const nsACString& aGUID,
|
||||
bool aHidden,
|
||||
PRTime aLastVisitDate) const
|
||||
{
|
||||
nsCOMPtr<nsIRunnable> notif = new FrecencyNotification(aSpec, aNewFrecency,
|
||||
aGUID, aHidden,
|
||||
aLastVisitDate);
|
||||
(void)NS_DispatchToMainThread(notif);
|
||||
}
|
||||
|
||||
int32_t
|
||||
nsNavHistory::GetDaysOfHistory() {
|
||||
MOZ_ASSERT(NS_IsMainThread(), "This can only be called on the main thread");
|
||||
@ -928,32 +1004,68 @@ nsNavHistory::GetHasHistoryEntries(bool* aHasEntries)
|
||||
}
|
||||
|
||||
|
||||
namespace {
|
||||
|
||||
class InvalidateAllFrecenciesCallback : public AsyncStatementCallback
|
||||
{
|
||||
public:
|
||||
InvalidateAllFrecenciesCallback()
|
||||
{
|
||||
}
|
||||
|
||||
NS_IMETHOD HandleCompletion(uint16_t aReason)
|
||||
{
|
||||
if (aReason == REASON_FINISHED) {
|
||||
nsNavHistory *navHistory = nsNavHistory::GetHistoryService();
|
||||
NS_ENSURE_STATE(navHistory);
|
||||
navHistory->NotifyManyFrecenciesChanged();
|
||||
}
|
||||
return NS_OK;
|
||||
}
|
||||
};
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
nsresult
|
||||
nsNavHistory::invalidateFrecencies(const nsCString& aPlaceIdsQueryString)
|
||||
{
|
||||
// Exclude place: queries by setting their frecency to zero.
|
||||
nsAutoCString invalideFrecenciesSQLFragment(
|
||||
"UPDATE moz_places SET frecency = (CASE "
|
||||
"WHEN url BETWEEN 'place:' AND 'place;' "
|
||||
"THEN 0 "
|
||||
"ELSE -1 "
|
||||
"END) "
|
||||
nsCString invalidFrecenciesSQLFragment(
|
||||
"UPDATE moz_places SET frecency = "
|
||||
);
|
||||
if (!aPlaceIdsQueryString.IsEmpty())
|
||||
invalidFrecenciesSQLFragment.AppendLiteral("NOTIFY_FRECENCY(");
|
||||
invalidFrecenciesSQLFragment.AppendLiteral(
|
||||
"(CASE "
|
||||
"WHEN url BETWEEN 'place:' AND 'place;' "
|
||||
"THEN 0 "
|
||||
"ELSE -1 "
|
||||
"END) "
|
||||
);
|
||||
if (!aPlaceIdsQueryString.IsEmpty()) {
|
||||
invalidFrecenciesSQLFragment.AppendLiteral(
|
||||
", url, guid, hidden, last_visit_date) "
|
||||
);
|
||||
}
|
||||
invalidFrecenciesSQLFragment.AppendLiteral(
|
||||
"WHERE frecency > 0 "
|
||||
);
|
||||
|
||||
if (!aPlaceIdsQueryString.IsEmpty()) {
|
||||
invalideFrecenciesSQLFragment.AppendLiteral("AND id IN(");
|
||||
invalideFrecenciesSQLFragment.Append(aPlaceIdsQueryString);
|
||||
invalideFrecenciesSQLFragment.AppendLiteral(")");
|
||||
invalidFrecenciesSQLFragment.AppendLiteral("AND id IN(");
|
||||
invalidFrecenciesSQLFragment.Append(aPlaceIdsQueryString);
|
||||
invalidFrecenciesSQLFragment.AppendLiteral(")");
|
||||
}
|
||||
nsRefPtr<InvalidateAllFrecenciesCallback> cb =
|
||||
aPlaceIdsQueryString.IsEmpty() ? new InvalidateAllFrecenciesCallback()
|
||||
: nullptr;
|
||||
|
||||
nsCOMPtr<mozIStorageAsyncStatement> stmt = mDB->GetAsyncStatement(
|
||||
invalideFrecenciesSQLFragment
|
||||
invalidFrecenciesSQLFragment
|
||||
);
|
||||
NS_ENSURE_STATE(stmt);
|
||||
|
||||
nsCOMPtr<mozIStoragePendingStatement> ps;
|
||||
nsresult rv = stmt->ExecuteAsync(nullptr, getter_AddRefs(ps));
|
||||
nsresult rv = stmt->ExecuteAsync(cb, getter_AddRefs(ps));
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
return NS_OK;
|
||||
@ -3078,6 +3190,30 @@ nsNavHistory::Observe(nsISupports *aSubject, const char *aTopic,
|
||||
}
|
||||
|
||||
|
||||
namespace {
|
||||
|
||||
class DecayFrecencyCallback : public AsyncStatementTelemetryTimer
|
||||
{
|
||||
public:
|
||||
DecayFrecencyCallback()
|
||||
: AsyncStatementTelemetryTimer(Telemetry::PLACES_IDLE_FRECENCY_DECAY_TIME_MS)
|
||||
{
|
||||
}
|
||||
|
||||
NS_IMETHOD HandleCompletion(uint16_t aReason)
|
||||
{
|
||||
(void)AsyncStatementTelemetryTimer::HandleCompletion(aReason);
|
||||
if (aReason == REASON_FINISHED) {
|
||||
nsNavHistory *navHistory = nsNavHistory::GetHistoryService();
|
||||
NS_ENSURE_STATE(navHistory);
|
||||
navHistory->NotifyManyFrecenciesChanged();
|
||||
}
|
||||
return NS_OK;
|
||||
}
|
||||
};
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
nsresult
|
||||
nsNavHistory::DecayFrecency()
|
||||
{
|
||||
@ -3115,8 +3251,7 @@ nsNavHistory::DecayFrecency()
|
||||
deleteAdaptive.get()
|
||||
};
|
||||
nsCOMPtr<mozIStoragePendingStatement> ps;
|
||||
nsRefPtr<AsyncStatementTelemetryTimer> cb =
|
||||
new AsyncStatementTelemetryTimer(Telemetry::PLACES_IDLE_FRECENCY_DECAY_TIME_MS);
|
||||
nsRefPtr<DecayFrecencyCallback> cb = new DecayFrecencyCallback();
|
||||
rv = mDB->MainConn()->ExecuteAsync(stmts, ArrayLength(stmts), cb,
|
||||
getter_AddRefs(ps));
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
@ -4312,7 +4447,9 @@ nsNavHistory::UpdateFrecency(int64_t aPlaceId)
|
||||
{
|
||||
nsCOMPtr<mozIStorageAsyncStatement> updateFrecencyStmt = mDB->GetAsyncStatement(
|
||||
"UPDATE moz_places "
|
||||
"SET frecency = CALCULATE_FRECENCY(:page_id) "
|
||||
"SET frecency = NOTIFY_FRECENCY("
|
||||
"CALCULATE_FRECENCY(:page_id), url, guid, hidden, last_visit_date"
|
||||
") "
|
||||
"WHERE id = :page_id"
|
||||
);
|
||||
NS_ENSURE_STATE(updateFrecencyStmt);
|
||||
@ -4345,6 +4482,31 @@ nsNavHistory::UpdateFrecency(int64_t aPlaceId)
|
||||
}
|
||||
|
||||
|
||||
namespace {
|
||||
|
||||
class FixInvalidFrecenciesCallback : public AsyncStatementCallbackNotifier
|
||||
{
|
||||
public:
|
||||
FixInvalidFrecenciesCallback()
|
||||
: AsyncStatementCallbackNotifier(TOPIC_FRECENCY_UPDATED)
|
||||
{
|
||||
}
|
||||
|
||||
NS_IMETHOD HandleCompletion(uint16_t aReason)
|
||||
{
|
||||
nsresult rv = AsyncStatementCallbackNotifier::HandleCompletion(aReason);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
if (aReason == REASON_FINISHED) {
|
||||
nsNavHistory *navHistory = nsNavHistory::GetHistoryService();
|
||||
NS_ENSURE_STATE(navHistory);
|
||||
navHistory->NotifyManyFrecenciesChanged();
|
||||
}
|
||||
return NS_OK;
|
||||
}
|
||||
};
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
nsresult
|
||||
nsNavHistory::FixInvalidFrecencies()
|
||||
{
|
||||
@ -4355,8 +4517,8 @@ nsNavHistory::FixInvalidFrecencies()
|
||||
);
|
||||
NS_ENSURE_STATE(stmt);
|
||||
|
||||
nsRefPtr<AsyncStatementCallbackNotifier> callback =
|
||||
new AsyncStatementCallbackNotifier(TOPIC_FRECENCY_UPDATED);
|
||||
nsRefPtr<FixInvalidFrecenciesCallback> callback =
|
||||
new FixInvalidFrecenciesCallback();
|
||||
nsCOMPtr<mozIStoragePendingStatement> ps;
|
||||
(void)stmt->ExecuteAsync(callback, getter_AddRefs(ps));
|
||||
|
||||
|
@ -418,6 +418,29 @@ public:
|
||||
const nsString& title,
|
||||
const nsACString& aGUID);
|
||||
|
||||
/**
|
||||
* Fires onFrecencyChanged event to nsINavHistoryService observers
|
||||
*/
|
||||
void NotifyFrecencyChanged(nsIURI* aURI,
|
||||
int32_t aNewFrecency,
|
||||
const nsACString& aGUID,
|
||||
bool aHidden,
|
||||
PRTime aLastVisitDate);
|
||||
|
||||
/**
|
||||
* Fires onManyFrecenciesChanged event to nsINavHistoryService observers
|
||||
*/
|
||||
void NotifyManyFrecenciesChanged();
|
||||
|
||||
/**
|
||||
* Posts a runnable to the main thread that calls NotifyFrecencyChanged.
|
||||
*/
|
||||
void DispatchFrecencyChangedNotification(const nsACString& aSpec,
|
||||
int32_t aNewFrecency,
|
||||
const nsACString& aGUID,
|
||||
bool aHidden,
|
||||
PRTime aLastVisitDate) const;
|
||||
|
||||
bool isBatching() {
|
||||
return mBatchLevel > 0;
|
||||
}
|
||||
|
@ -2614,6 +2614,24 @@ nsNavHistoryQueryResultNode::OnTitleChanged(nsIURI* aURI,
|
||||
}
|
||||
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsNavHistoryQueryResultNode::OnFrecencyChanged(nsIURI* aURI,
|
||||
int32_t aNewFrecency,
|
||||
const nsACString& aGUID,
|
||||
bool aHidden,
|
||||
PRTime aLastVisitDate)
|
||||
{
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsNavHistoryQueryResultNode::OnManyFrecenciesChanged()
|
||||
{
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Here, we can always live update by just deleting all occurrences of
|
||||
* the given URI.
|
||||
@ -4662,6 +4680,24 @@ nsNavHistoryResult::OnTitleChanged(nsIURI* aURI,
|
||||
}
|
||||
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsNavHistoryResult::OnFrecencyChanged(nsIURI* aURI,
|
||||
int32_t aNewFrecency,
|
||||
const nsACString& aGUID,
|
||||
bool aHidden,
|
||||
PRTime aLastVisitDate)
|
||||
{
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsNavHistoryResult::OnManyFrecenciesChanged()
|
||||
{
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsNavHistoryResult::OnDeleteURI(nsIURI *aURI,
|
||||
const nsACString& aGUID,
|
||||
|
@ -64,6 +64,10 @@ private:
|
||||
NS_DECL_NSINAVBOOKMARKOBSERVER \
|
||||
NS_IMETHOD OnTitleChanged(nsIURI* aURI, const nsAString& aPageTitle, \
|
||||
const nsACString& aGUID); \
|
||||
NS_IMETHOD OnFrecencyChanged(nsIURI* aURI, int32_t aNewFrecency, \
|
||||
const nsACString& aGUID, bool aHidden, \
|
||||
PRTime aLastVisitDate); \
|
||||
NS_IMETHOD OnManyFrecenciesChanged(); \
|
||||
NS_IMETHOD OnDeleteURI(nsIURI *aURI, const nsACString& aGUID, \
|
||||
uint16_t aReason); \
|
||||
NS_IMETHOD OnClearHistory(); \
|
||||
|
@ -0,0 +1,85 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
function run_test() {
|
||||
run_next_test();
|
||||
}
|
||||
|
||||
// Each of these tests a path that triggers a frecency update. Together they
|
||||
// hit all sites that update a frecency.
|
||||
|
||||
// InsertVisitedURIs::UpdateFrecency and History::InsertPlace
|
||||
add_task(function test_InsertVisitedURIs_UpdateFrecency_and_History_InsertPlace() {
|
||||
// InsertPlace is at the end of a path that UpdateFrecency is also on, so kill
|
||||
// two birds with one stone and expect two notifications. Trigger the path by
|
||||
// adding a download.
|
||||
let uri = NetUtil.newURI("http://example.com/a");
|
||||
Cc["@mozilla.org/browser/download-history;1"].
|
||||
getService(Ci.nsIDownloadHistory).
|
||||
addDownload(uri);
|
||||
yield Promise.all([onFrecencyChanged(uri), onFrecencyChanged(uri)]);
|
||||
});
|
||||
|
||||
// nsNavHistory::UpdateFrecency
|
||||
add_task(function test_nsNavHistory_UpdateFrecency() {
|
||||
let bm = PlacesUtils.bookmarks;
|
||||
let uri = NetUtil.newURI("http://example.com/b");
|
||||
bm.insertBookmark(bm.unfiledBookmarksFolder, uri,
|
||||
Ci.nsINavBookmarksService.DEFAULT_INDEX, "test");
|
||||
yield onFrecencyChanged(uri);
|
||||
});
|
||||
|
||||
// nsNavHistory::invalidateFrecencies for particular pages
|
||||
add_task(function test_nsNavHistory_invalidateFrecencies_somePages() {
|
||||
let uri = NetUtil.newURI("http://test-nsNavHistory-invalidateFrecencies-somePages.com/");
|
||||
// Bookmarking the URI is enough to add it to moz_places, and importantly, it
|
||||
// means that removePagesFromHost doesn't remove it from moz_places, so its
|
||||
// frecency is able to be changed.
|
||||
let bm = PlacesUtils.bookmarks;
|
||||
bm.insertBookmark(bm.unfiledBookmarksFolder, uri,
|
||||
Ci.nsINavBookmarksService.DEFAULT_INDEX, "test");
|
||||
PlacesUtils.history.removePagesFromHost(uri.host, false);
|
||||
yield onFrecencyChanged(uri);
|
||||
});
|
||||
|
||||
// nsNavHistory::invalidateFrecencies for all pages
|
||||
add_task(function test_nsNavHistory_invalidateFrecencies_allPages() {
|
||||
PlacesUtils.history.removeAllPages();
|
||||
yield onManyFrecenciesChanged();
|
||||
});
|
||||
|
||||
// nsNavHistory::DecayFrecency and nsNavHistory::FixInvalidFrecencies
|
||||
add_task(function test_nsNavHistory_DecayFrecency_and_nsNavHistory_FixInvalidFrecencies() {
|
||||
// FixInvalidFrecencies is at the end of a path that DecayFrecency is also on,
|
||||
// so expect two notifications. Trigger the path by making nsNavHistory
|
||||
// observe the idle-daily notification.
|
||||
PlacesUtils.history.QueryInterface(Ci.nsIObserver).
|
||||
observe(null, "idle-daily", "");
|
||||
yield Promise.all([onManyFrecenciesChanged(), onManyFrecenciesChanged()]);
|
||||
});
|
||||
|
||||
function onFrecencyChanged(expectedURI) {
|
||||
let deferred = Promise.defer();
|
||||
let obs = new NavHistoryObserver();
|
||||
obs.onFrecencyChanged =
|
||||
(uri, newFrecency, guid, hidden, visitDate) => {
|
||||
PlacesUtils.history.removeObserver(obs);
|
||||
do_check_true(!!uri);
|
||||
do_check_true(uri.equals(expectedURI));
|
||||
deferred.resolve();
|
||||
};
|
||||
PlacesUtils.history.addObserver(obs, false);
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
function onManyFrecenciesChanged() {
|
||||
let deferred = Promise.defer();
|
||||
let obs = new NavHistoryObserver();
|
||||
obs.onManyFrecenciesChanged = () => {
|
||||
PlacesUtils.history.removeObserver(obs);
|
||||
do_check_true(true);
|
||||
deferred.resolve();
|
||||
};
|
||||
PlacesUtils.history.addObserver(obs, false);
|
||||
return deferred.promise;
|
||||
}
|
@ -113,6 +113,7 @@ skip-if = true
|
||||
[test_null_interfaces.js]
|
||||
[test_onItemChanged_tags.js]
|
||||
[test_pageGuid_bookmarkGuid.js]
|
||||
[test_frecency_observers.js]
|
||||
[test_placeURIs.js]
|
||||
[test_PlacesUtils_asyncGetBookmarkIds.js]
|
||||
[test_PlacesUtils_lazyobservers.js]
|
||||
|
@ -1098,7 +1098,7 @@ let DebuggerEnvironmentSupport = {
|
||||
};
|
||||
|
||||
|
||||
exports.JSPropertyProvider = JSPropertyProvider;
|
||||
exports.JSPropertyProvider = DevToolsUtils.makeInfallible(JSPropertyProvider);
|
||||
})(WebConsoleUtils);
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
74
toolkit/modules/BinarySearch.jsm
Normal file
74
toolkit/modules/BinarySearch.jsm
Normal file
@ -0,0 +1,74 @@
|
||||
/* 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 = [
|
||||
"BinarySearch",
|
||||
];
|
||||
|
||||
this.BinarySearch = Object.freeze({
|
||||
|
||||
/**
|
||||
* Returns the index of the given target in the given array or -1 if the
|
||||
* target is not found.
|
||||
*
|
||||
* See search() for a description of this function's parameters.
|
||||
*
|
||||
* @return The index of `target` in `array` or -1 if `target` is not found.
|
||||
*/
|
||||
indexOf: function (array, target, comparator) {
|
||||
let [found, idx] = this.search(array, target, comparator);
|
||||
return found ? idx : -1;
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns the index within the given array where the given target may be
|
||||
* inserted to keep the array ordered.
|
||||
*
|
||||
* See search() for a description of this function's parameters.
|
||||
*
|
||||
* @return The index in `array` where `target` may be inserted to keep `array`
|
||||
* ordered.
|
||||
*/
|
||||
insertionIndexOf: function (array, target, comparator) {
|
||||
return this.search(array, target, comparator)[1];
|
||||
},
|
||||
|
||||
/**
|
||||
* Searches for the given target in the given array.
|
||||
*
|
||||
* @param array
|
||||
* An array whose elements are ordered by `comparator`.
|
||||
* @param target
|
||||
* The value to search for.
|
||||
* @param comparator
|
||||
* A function that takes two arguments and compares them, returning a
|
||||
* negative number if the first should be ordered before the second,
|
||||
* zero if the first and second have the same ordering, or a positive
|
||||
* number if the second should be ordered before the first. The first
|
||||
* argument is always `target`, and the second argument is a value
|
||||
* from the array.
|
||||
* @return An array with two elements. If `target` is found, the first
|
||||
* element is true, and the second element is its index in the array.
|
||||
* If `target` is not found, the first element is false, and the
|
||||
* second element is the index where it may be inserted to keep the
|
||||
* array ordered.
|
||||
*/
|
||||
search: function (array, target, comparator) {
|
||||
let low = 0;
|
||||
let high = array.length - 1;
|
||||
while (low <= high) {
|
||||
let mid = Math.floor((low + high) / 2);
|
||||
let cmp = comparator(target, array[mid]);
|
||||
if (cmp == 0)
|
||||
return [true, mid];
|
||||
if (cmp < 0)
|
||||
high = mid - 1;
|
||||
else
|
||||
low = mid + 1;
|
||||
}
|
||||
return [false, low];
|
||||
},
|
||||
});
|
@ -19,6 +19,13 @@ XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils",
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "PageThumbs",
|
||||
"resource://gre/modules/PageThumbs.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "BinarySearch",
|
||||
"resource://gre/modules/BinarySearch.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyGetter(this, "Timer", () => {
|
||||
return Cu.import("resource://gre/modules/Timer.jsm", {});
|
||||
});
|
||||
|
||||
XPCOMUtils.defineLazyGetter(this, "gPrincipal", function () {
|
||||
let uri = Services.io.newURI("about:newtab", null, null);
|
||||
return Services.scriptSecurityManager.getNoAppCodebasePrincipal(uri);
|
||||
@ -44,12 +51,18 @@ const PREF_NEWTAB_ROWS = "browser.newtabpage.rows";
|
||||
// The preference that tells the number of columns of the newtab grid.
|
||||
const PREF_NEWTAB_COLUMNS = "browser.newtabpage.columns";
|
||||
|
||||
// The maximum number of results we want to retrieve from history.
|
||||
// The maximum number of results PlacesProvider retrieves from history.
|
||||
const HISTORY_RESULTS_LIMIT = 100;
|
||||
|
||||
// The maximum number of links Links.getLinks will return.
|
||||
const LINKS_GET_LINKS_LIMIT = 100;
|
||||
|
||||
// The gather telemetry topic.
|
||||
const TOPIC_GATHER_TELEMETRY = "gather-telemetry";
|
||||
|
||||
// The amount of time we wait while coalescing updates for hidden pages.
|
||||
const SCHEDULE_UPDATE_TIMEOUT_MS = 1000;
|
||||
|
||||
/**
|
||||
* Calculate the MD5 hash for a string.
|
||||
* @param aValue
|
||||
@ -244,14 +257,34 @@ let AllPages = {
|
||||
/**
|
||||
* Updates all currently active pages but the given one.
|
||||
* @param aExceptPage The page to exclude from updating.
|
||||
* @param aHiddenPagesOnly If true, only pages hidden in the preloader are
|
||||
* updated.
|
||||
*/
|
||||
update: function AllPages_update(aExceptPage) {
|
||||
update: function AllPages_update(aExceptPage, aHiddenPagesOnly=false) {
|
||||
this._pages.forEach(function (aPage) {
|
||||
if (aExceptPage != aPage)
|
||||
aPage.update();
|
||||
aPage.update(aHiddenPagesOnly);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Many individual link changes may happen in a small amount of time over
|
||||
* multiple turns of the event loop. This method coalesces updates by waiting
|
||||
* a small amount of time before updating hidden pages.
|
||||
*/
|
||||
scheduleUpdateForHiddenPages: function AllPages_scheduleUpdateForHiddenPages() {
|
||||
if (!this._scheduleUpdateTimeout) {
|
||||
this._scheduleUpdateTimeout = Timer.setTimeout(() => {
|
||||
delete this._scheduleUpdateTimeout;
|
||||
this.update(null, true);
|
||||
}, SCHEDULE_UPDATE_TIMEOUT_MS);
|
||||
}
|
||||
},
|
||||
|
||||
get updateScheduledForHiddenPages() {
|
||||
return !!this._scheduleUpdateTimeout;
|
||||
},
|
||||
|
||||
/**
|
||||
* Implements the nsIObserver interface to get notified when the preference
|
||||
* value changes or when a new copy of a page thumbnail is available.
|
||||
@ -504,13 +537,25 @@ let BlockedLinks = {
|
||||
* the history to retrieve the most frequently visited sites.
|
||||
*/
|
||||
let PlacesProvider = {
|
||||
/**
|
||||
* Set this to change the maximum number of links the provider will provide.
|
||||
*/
|
||||
maxNumLinks: HISTORY_RESULTS_LIMIT,
|
||||
|
||||
/**
|
||||
* Must be called before the provider is used.
|
||||
*/
|
||||
init: function PlacesProvider_init() {
|
||||
PlacesUtils.history.addObserver(this, true);
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets the current set of links delivered by this provider.
|
||||
* @param aCallback The function that the array of links is passed to.
|
||||
*/
|
||||
getLinks: function PlacesProvider_getLinks(aCallback) {
|
||||
let options = PlacesUtils.history.getNewQueryOptions();
|
||||
options.maxResults = HISTORY_RESULTS_LIMIT;
|
||||
options.maxResults = this.maxNumLinks;
|
||||
|
||||
// Sort by frecency, descending.
|
||||
options.sortingMode = Ci.nsINavHistoryQueryOptions.SORT_BY_FRECENCY_DESCENDING
|
||||
@ -525,7 +570,14 @@ let PlacesProvider = {
|
||||
let url = row.getResultByIndex(1);
|
||||
if (LinkChecker.checkLoadURI(url)) {
|
||||
let title = row.getResultByIndex(2);
|
||||
links.push({url: url, title: title});
|
||||
let frecency = row.getResultByIndex(12);
|
||||
let lastVisitDate = row.getResultByIndex(5);
|
||||
links.push({
|
||||
url: url,
|
||||
title: title,
|
||||
frecency: frecency,
|
||||
lastVisitDate: lastVisitDate,
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -536,6 +588,26 @@ let PlacesProvider = {
|
||||
},
|
||||
|
||||
handleCompletion: function (aReason) {
|
||||
// The Places query breaks ties in frecency by place ID descending, but
|
||||
// that's different from how Links.compareLinks breaks ties, because
|
||||
// compareLinks doesn't have access to place IDs. It's very important
|
||||
// that the initial list of links is sorted in the same order imposed by
|
||||
// compareLinks, because Links uses compareLinks to perform binary
|
||||
// searches on the list. So, ensure the list is so ordered.
|
||||
let i = 1;
|
||||
let outOfOrder = [];
|
||||
while (i < links.length) {
|
||||
if (Links.compareLinks(links[i - 1], links[i]) > 0)
|
||||
outOfOrder.push(links.splice(i, 1)[0]);
|
||||
else
|
||||
i++;
|
||||
}
|
||||
for (let link of outOfOrder) {
|
||||
i = BinarySearch.insertionIndexOf(links, link,
|
||||
Links.compareLinks.bind(Links));
|
||||
links.splice(i, 0, link);
|
||||
}
|
||||
|
||||
aCallback(links);
|
||||
}
|
||||
};
|
||||
@ -544,28 +616,116 @@ let PlacesProvider = {
|
||||
let query = PlacesUtils.history.getNewQuery();
|
||||
let db = PlacesUtils.history.QueryInterface(Ci.nsPIPlacesDatabase);
|
||||
db.asyncExecuteLegacyQueries([query], 1, options, callback);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Registers an object that will be notified when the provider's links change.
|
||||
* @param aObserver An object with the following optional properties:
|
||||
* * onLinkChanged: A function that's called when a single link
|
||||
* changes. It's passed the provider and the link object. Only the
|
||||
* link's `url` property is guaranteed to be present. If its `title`
|
||||
* property is present, then its title has changed, and the
|
||||
* property's value is the new title. If any sort properties are
|
||||
* present, then its position within the provider's list of links may
|
||||
* have changed, and the properties' values are the new sort-related
|
||||
* values. Note that this link may not necessarily have been present
|
||||
* in the lists returned from any previous calls to getLinks.
|
||||
* * onManyLinksChanged: A function that's called when many links
|
||||
* change at once. It's passed the provider. You should call
|
||||
* getLinks to get the provider's new list of links.
|
||||
*/
|
||||
addObserver: function PlacesProvider_addObserver(aObserver) {
|
||||
this._observers.push(aObserver);
|
||||
},
|
||||
|
||||
_observers: [],
|
||||
|
||||
/**
|
||||
* Called by the history service.
|
||||
*/
|
||||
onFrecencyChanged: function PlacesProvider_onFrecencyChanged(aURI, aNewFrecency, aGUID, aHidden, aLastVisitDate) {
|
||||
// The implementation of the query in getLinks excludes hidden and
|
||||
// unvisited pages, so it's important to exclude them here, too.
|
||||
if (!aHidden && aLastVisitDate) {
|
||||
this._callObservers("onLinkChanged", {
|
||||
url: aURI.spec,
|
||||
frecency: aNewFrecency,
|
||||
lastVisitDate: aLastVisitDate,
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Called by the history service.
|
||||
*/
|
||||
onManyFrecenciesChanged: function PlacesProvider_onManyFrecenciesChanged() {
|
||||
this._callObservers("onManyLinksChanged");
|
||||
},
|
||||
|
||||
/**
|
||||
* Called by the history service.
|
||||
*/
|
||||
onTitleChanged: function PlacesProvider_onTitleChanged(aURI, aNewTitle, aGUID) {
|
||||
this._callObservers("onLinkChanged", {
|
||||
url: aURI.spec,
|
||||
title: aNewTitle
|
||||
});
|
||||
},
|
||||
|
||||
_callObservers: function PlacesProvider__callObservers(aMethodName, aArg) {
|
||||
for (let obs of this._observers) {
|
||||
if (obs[aMethodName]) {
|
||||
try {
|
||||
obs[aMethodName](this, aArg);
|
||||
} catch (err) {
|
||||
Cu.reportError(err);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
QueryInterface: XPCOMUtils.generateQI([Ci.nsINavHistoryObserver,
|
||||
Ci.nsISupportsWeakReference]),
|
||||
};
|
||||
|
||||
/**
|
||||
* Singleton that provides access to all links contained in the grid (including
|
||||
* the ones that don't fit on the grid). A link is a plain object with title
|
||||
* and url properties.
|
||||
* the ones that don't fit on the grid). A link is a plain object that looks
|
||||
* like this:
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* {url: "http://www.mozilla.org/", title: "Mozilla"}
|
||||
* {
|
||||
* url: "http://www.mozilla.org/",
|
||||
* title: "Mozilla",
|
||||
* frecency: 1337,
|
||||
* lastVisitDate: 1394678824766431,
|
||||
* }
|
||||
*/
|
||||
let Links = {
|
||||
/**
|
||||
* The links cache.
|
||||
* The maximum number of links returned by getLinks.
|
||||
*/
|
||||
_links: null,
|
||||
maxNumLinks: LINKS_GET_LINKS_LIMIT,
|
||||
|
||||
/**
|
||||
* The default provider for links.
|
||||
* The link providers.
|
||||
*/
|
||||
_provider: PlacesProvider,
|
||||
_providers: new Set(),
|
||||
|
||||
/**
|
||||
* A mapping from each provider to an object { sortedLinks, linkMap }.
|
||||
* sortedLinks is the cached, sorted array of links for the provider. linkMap
|
||||
* is a Map from link URLs to link objects.
|
||||
*/
|
||||
_providerLinks: new Map(),
|
||||
|
||||
/**
|
||||
* The properties of link objects used to sort them.
|
||||
*/
|
||||
_sortProperties: [
|
||||
"frecency",
|
||||
"lastVisitDate",
|
||||
"url",
|
||||
],
|
||||
|
||||
/**
|
||||
* List of callbacks waiting for the cache to be populated.
|
||||
@ -573,7 +733,26 @@ let Links = {
|
||||
_populateCallbacks: [],
|
||||
|
||||
/**
|
||||
* Populates the cache with fresh links from the current provider.
|
||||
* Adds a link provider.
|
||||
* @param aProvider The link provider.
|
||||
*/
|
||||
addProvider: function Links_addProvider(aProvider) {
|
||||
this._providers.add(aProvider);
|
||||
aProvider.addObserver(this);
|
||||
},
|
||||
|
||||
/**
|
||||
* Removes a link provider.
|
||||
* @param aProvider The link provider.
|
||||
*/
|
||||
removeProvider: function Links_removeProvider(aProvider) {
|
||||
if (!this._providers.delete(aProvider))
|
||||
throw new Error("Unknown provider");
|
||||
this._providerLinks.delete(aProvider);
|
||||
},
|
||||
|
||||
/**
|
||||
* Populates the cache with fresh links from the providers.
|
||||
* @param aCallback The callback to call when finished (optional).
|
||||
* @param aForce When true, populates the cache even when it's already filled.
|
||||
*/
|
||||
@ -601,16 +780,15 @@ let Links = {
|
||||
}
|
||||
}
|
||||
|
||||
if (this._links && !aForce) {
|
||||
executeCallbacks();
|
||||
} else {
|
||||
this._provider.getLinks(function (aLinks) {
|
||||
this._links = aLinks;
|
||||
executeCallbacks();
|
||||
}.bind(this));
|
||||
|
||||
this._addObserver();
|
||||
let numProvidersRemaining = this._providers.size;
|
||||
for (let provider of this._providers) {
|
||||
this._populateProviderCache(provider, () => {
|
||||
if (--numProvidersRemaining == 0)
|
||||
executeCallbacks();
|
||||
}, aForce);
|
||||
}
|
||||
|
||||
this._addObserver();
|
||||
},
|
||||
|
||||
/**
|
||||
@ -619,9 +797,10 @@ let Links = {
|
||||
*/
|
||||
getLinks: function Links_getLinks() {
|
||||
let pinnedLinks = Array.slice(PinnedLinks.links);
|
||||
let links = this._getMergedProviderLinks();
|
||||
|
||||
// Filter blocked and pinned links.
|
||||
let links = (this._links || []).filter(function (link) {
|
||||
links = links.filter(function (link) {
|
||||
return !BlockedLinks.isBlocked(link) && !PinnedLinks.isPinned(link);
|
||||
});
|
||||
|
||||
@ -641,7 +820,186 @@ let Links = {
|
||||
* Resets the links cache.
|
||||
*/
|
||||
resetCache: function Links_resetCache() {
|
||||
this._links = null;
|
||||
this._providerLinks.clear();
|
||||
},
|
||||
|
||||
/**
|
||||
* Compares two links.
|
||||
* @param aLink1 The first link.
|
||||
* @param aLink2 The second link.
|
||||
* @return A negative number if aLink1 is ordered before aLink2, zero if
|
||||
* aLink1 and aLink2 have the same ordering, or a positive number if
|
||||
* aLink1 is ordered after aLink2.
|
||||
*/
|
||||
compareLinks: function Links_compareLinks(aLink1, aLink2) {
|
||||
for (let prop of this._sortProperties) {
|
||||
if (!(prop in aLink1) || !(prop in aLink2))
|
||||
throw new Error("Comparable link missing required property: " + prop);
|
||||
}
|
||||
return aLink2.frecency - aLink1.frecency ||
|
||||
aLink2.lastVisitDate - aLink1.lastVisitDate ||
|
||||
aLink1.url.localeCompare(aLink2.url);
|
||||
},
|
||||
|
||||
/**
|
||||
* Calls getLinks on the given provider and populates our cache for it.
|
||||
* @param aProvider The provider whose cache will be populated.
|
||||
* @param aCallback The callback to call when finished.
|
||||
* @param aForce When true, populates the provider's cache even when it's
|
||||
* already filled.
|
||||
*/
|
||||
_populateProviderCache: function Links_populateProviderCache(aProvider, aCallback, aForce) {
|
||||
if (this._providerLinks.has(aProvider) && !aForce) {
|
||||
aCallback();
|
||||
} else {
|
||||
aProvider.getLinks(links => {
|
||||
// Filter out null and undefined links so we don't have to deal with
|
||||
// them in getLinks when merging links from providers.
|
||||
links = links.filter((link) => !!link);
|
||||
this._providerLinks.set(aProvider, {
|
||||
sortedLinks: links,
|
||||
linkMap: links.reduce((map, link) => {
|
||||
map.set(link.url, link);
|
||||
return map;
|
||||
}, new Map()),
|
||||
});
|
||||
aCallback();
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Merges the cached lists of links from all providers whose lists are cached.
|
||||
* @return The merged list.
|
||||
*/
|
||||
_getMergedProviderLinks: function Links__getMergedProviderLinks() {
|
||||
// Build a list containing a copy of each provider's sortedLinks list.
|
||||
let linkLists = [];
|
||||
for (let links of this._providerLinks.values()) {
|
||||
linkLists.push(links.sortedLinks.slice());
|
||||
}
|
||||
|
||||
function getNextLink() {
|
||||
let minLinks = null;
|
||||
for (let links of linkLists) {
|
||||
if (links.length &&
|
||||
(!minLinks || Links.compareLinks(links[0], minLinks[0]) < 0))
|
||||
minLinks = links;
|
||||
}
|
||||
return minLinks ? minLinks.shift() : null;
|
||||
}
|
||||
|
||||
let finalLinks = [];
|
||||
for (let nextLink = getNextLink();
|
||||
nextLink && finalLinks.length < this.maxNumLinks;
|
||||
nextLink = getNextLink()) {
|
||||
finalLinks.push(nextLink);
|
||||
}
|
||||
|
||||
return finalLinks;
|
||||
},
|
||||
|
||||
/**
|
||||
* Called by a provider to notify us when a single link changes.
|
||||
* @param aProvider The provider whose link changed.
|
||||
* @param aLink The link that changed. If the link is new, it must have all
|
||||
* of the _sortProperties. Otherwise, it may have as few or as
|
||||
* many as is convenient.
|
||||
*/
|
||||
onLinkChanged: function Links_onLinkChanged(aProvider, aLink) {
|
||||
if (!("url" in aLink))
|
||||
throw new Error("Changed links must have a url property");
|
||||
|
||||
let links = this._providerLinks.get(aProvider);
|
||||
if (!links)
|
||||
// This is not an error, it just means that between the time the provider
|
||||
// was added and the future time we call getLinks on it, it notified us of
|
||||
// a change.
|
||||
return;
|
||||
|
||||
let { sortedLinks, linkMap } = links;
|
||||
|
||||
// Nothing to do if the list is full and the link isn't in it and shouldn't
|
||||
// be in it.
|
||||
if (!linkMap.has(aLink.url) &&
|
||||
sortedLinks.length &&
|
||||
sortedLinks.length == aProvider.maxNumLinks) {
|
||||
let lastLink = sortedLinks[sortedLinks.length - 1];
|
||||
if (this.compareLinks(lastLink, aLink) < 0)
|
||||
return;
|
||||
}
|
||||
|
||||
let updatePages = false;
|
||||
|
||||
// Update the title in O(1).
|
||||
if ("title" in aLink) {
|
||||
let link = linkMap.get(aLink.url);
|
||||
if (link && link.title != aLink.title) {
|
||||
link.title = aLink.title;
|
||||
updatePages = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Update the link's position in O(lg n).
|
||||
if (this._sortProperties.some((prop) => prop in aLink)) {
|
||||
let link = linkMap.get(aLink.url);
|
||||
if (link) {
|
||||
// The link is already in the list.
|
||||
let idx = this._indexOf(sortedLinks, link);
|
||||
if (idx < 0)
|
||||
throw new Error("Link should be in _sortedLinks if in _linkMap");
|
||||
sortedLinks.splice(idx, 1);
|
||||
for (let prop of this._sortProperties) {
|
||||
if (prop in aLink)
|
||||
link[prop] = aLink[prop];
|
||||
}
|
||||
}
|
||||
else {
|
||||
// The link is new.
|
||||
for (let prop of this._sortProperties) {
|
||||
if (!(prop in aLink))
|
||||
throw new Error("New link missing required sort property: " + prop);
|
||||
}
|
||||
// Copy the link object so that if the caller changes it, it doesn't
|
||||
// screw up our bookkeeping.
|
||||
link = {};
|
||||
for (let [prop, val] of Iterator(aLink)) {
|
||||
link[prop] = val;
|
||||
}
|
||||
linkMap.set(link.url, link);
|
||||
}
|
||||
let idx = this._insertionIndexOf(sortedLinks, link);
|
||||
sortedLinks.splice(idx, 0, link);
|
||||
if (sortedLinks.length > aProvider.maxNumLinks) {
|
||||
let lastLink = sortedLinks.pop();
|
||||
linkMap.delete(lastLink.url);
|
||||
}
|
||||
updatePages = true;
|
||||
}
|
||||
|
||||
if (updatePages)
|
||||
AllPages.scheduleUpdateForHiddenPages();
|
||||
},
|
||||
|
||||
/**
|
||||
* Called by a provider to notify us when many links change.
|
||||
*/
|
||||
onManyLinksChanged: function Links_onManyLinksChanged(aProvider) {
|
||||
this._populateProviderCache(aProvider, () => {
|
||||
AllPages.scheduleUpdateForHiddenPages();
|
||||
}, true);
|
||||
},
|
||||
|
||||
_indexOf: function Links__indexOf(aArray, aLink) {
|
||||
return this._binsearch(aArray, aLink, "indexOf");
|
||||
},
|
||||
|
||||
_insertionIndexOf: function Links__insertionIndexOf(aArray, aLink) {
|
||||
return this._binsearch(aArray, aLink, "insertionIndexOf");
|
||||
},
|
||||
|
||||
_binsearch: function Links__binsearch(aArray, aLink, aMethod) {
|
||||
return BinarySearch[aMethod](aArray, aLink, this.compareLinks.bind(this));
|
||||
},
|
||||
|
||||
/**
|
||||
@ -654,7 +1012,7 @@ let Links = {
|
||||
if (AllPages.length && AllPages.enabled)
|
||||
this.populateCache(function () { AllPages.update() }, true);
|
||||
else
|
||||
this._links = null;
|
||||
this.resetCache();
|
||||
},
|
||||
|
||||
/**
|
||||
@ -774,11 +1132,20 @@ this.NewTabUtils = {
|
||||
_initialized: false,
|
||||
|
||||
init: function NewTabUtils_init() {
|
||||
if (this.initWithoutProviders()) {
|
||||
PlacesProvider.init();
|
||||
Links.addProvider(PlacesProvider);
|
||||
}
|
||||
},
|
||||
|
||||
initWithoutProviders: function NewTabUtils_initWithoutProviders() {
|
||||
if (!this._initialized) {
|
||||
this._initialized = true;
|
||||
ExpirationFilter.init();
|
||||
Telemetry.init();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -11,6 +11,7 @@ MOCHITEST_CHROME_MANIFESTS += ['tests/chrome/chrome.ini']
|
||||
|
||||
EXTRA_JS_MODULES += [
|
||||
'AsyncShutdown.jsm',
|
||||
'BinarySearch.jsm',
|
||||
'BrowserUtils.jsm',
|
||||
'CharsetMenu.jsm',
|
||||
'debug.js',
|
||||
|
81
toolkit/modules/tests/xpcshell/test_BinarySearch.js
Normal file
81
toolkit/modules/tests/xpcshell/test_BinarySearch.js
Normal file
@ -0,0 +1,81 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
Components.utils.import("resource://gre/modules/BinarySearch.jsm");
|
||||
|
||||
function run_test() {
|
||||
// empty array
|
||||
ok([], 1, false, 0);
|
||||
|
||||
// one-element array
|
||||
ok([2], 2, true, 0);
|
||||
ok([2], 1, false, 0);
|
||||
ok([2], 3, false, 1);
|
||||
|
||||
// two-element array
|
||||
ok([2, 4], 2, true, 0);
|
||||
ok([2, 4], 4, true, 1);
|
||||
ok([2, 4], 1, false, 0);
|
||||
ok([2, 4], 3, false, 1);
|
||||
ok([2, 4], 5, false, 2);
|
||||
|
||||
// three-element array
|
||||
ok([2, 4, 6], 2, true, 0);
|
||||
ok([2, 4, 6], 4, true, 1);
|
||||
ok([2, 4, 6], 6, true, 2);
|
||||
ok([2, 4, 6], 1, false, 0);
|
||||
ok([2, 4, 6], 3, false, 1);
|
||||
ok([2, 4, 6], 5, false, 2);
|
||||
ok([2, 4, 6], 7, false, 3);
|
||||
|
||||
// duplicates
|
||||
ok([2, 2], 2, true, 0);
|
||||
ok([2, 2], 1, false, 0);
|
||||
ok([2, 2], 3, false, 2);
|
||||
|
||||
// duplicates on the left
|
||||
ok([2, 2, 4], 2, true, 1);
|
||||
ok([2, 2, 4], 4, true, 2);
|
||||
ok([2, 2, 4], 1, false, 0);
|
||||
ok([2, 2, 4], 3, false, 2);
|
||||
ok([2, 2, 4], 5, false, 3);
|
||||
|
||||
// duplicates on the right
|
||||
ok([2, 4, 4], 2, true, 0);
|
||||
ok([2, 4, 4], 4, true, 1);
|
||||
ok([2, 4, 4], 1, false, 0);
|
||||
ok([2, 4, 4], 3, false, 1);
|
||||
ok([2, 4, 4], 5, false, 3);
|
||||
|
||||
// duplicates in the middle
|
||||
ok([2, 4, 4, 6], 2, true, 0);
|
||||
ok([2, 4, 4, 6], 4, true, 1);
|
||||
ok([2, 4, 4, 6], 6, true, 3);
|
||||
ok([2, 4, 4, 6], 1, false, 0);
|
||||
ok([2, 4, 4, 6], 3, false, 1);
|
||||
ok([2, 4, 4, 6], 5, false, 3);
|
||||
ok([2, 4, 4, 6], 7, false, 4);
|
||||
|
||||
// duplicates all around
|
||||
ok([2, 2, 4, 4, 6, 6], 2, true, 0);
|
||||
ok([2, 2, 4, 4, 6, 6], 4, true, 2);
|
||||
ok([2, 2, 4, 4, 6, 6], 6, true, 4);
|
||||
ok([2, 2, 4, 4, 6, 6], 1, false, 0);
|
||||
ok([2, 2, 4, 4, 6, 6], 3, false, 2);
|
||||
ok([2, 2, 4, 4, 6, 6], 5, false, 4);
|
||||
ok([2, 2, 4, 4, 6, 6], 7, false, 6);
|
||||
}
|
||||
|
||||
function ok(array, target, expectedFound, expectedIdx) {
|
||||
let [found, idx] = BinarySearch.search(array, target, cmp);
|
||||
do_check_eq(found, expectedFound);
|
||||
do_check_eq(idx, expectedIdx);
|
||||
|
||||
idx = expectedFound ? expectedIdx : -1;
|
||||
do_check_eq(BinarySearch.indexOf(array, target, cmp), idx);
|
||||
do_check_eq(BinarySearch.insertionIndexOf(array, target, cmp), expectedIdx);
|
||||
}
|
||||
|
||||
function cmp(num1, num2) {
|
||||
return num1 - num2;
|
||||
}
|
176
toolkit/modules/tests/xpcshell/test_NewTabUtils.js
Normal file
176
toolkit/modules/tests/xpcshell/test_NewTabUtils.js
Normal file
@ -0,0 +1,176 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
// See also browser/base/content/test/newtab/.
|
||||
|
||||
const { classes: Cc, interfaces: Ci, results: Cr, utils: Cu } = Components;
|
||||
Cu.import("resource://gre/modules/NewTabUtils.jsm");
|
||||
Cu.import("resource://gre/modules/Promise.jsm");
|
||||
|
||||
function run_test() {
|
||||
run_next_test();
|
||||
}
|
||||
|
||||
add_test(function multipleProviders() {
|
||||
// Make each provider generate NewTabUtils.links.maxNumLinks links to check
|
||||
// that no more than maxNumLinks are actually returned in the merged list.
|
||||
let evenLinks = makeLinks(0, 2 * NewTabUtils.links.maxNumLinks, 2);
|
||||
let evenProvider = new TestProvider(done => done(evenLinks));
|
||||
let oddLinks = makeLinks(0, 2 * NewTabUtils.links.maxNumLinks - 1, 2);
|
||||
let oddProvider = new TestProvider(done => done(oddLinks));
|
||||
|
||||
NewTabUtils.initWithoutProviders();
|
||||
NewTabUtils.links.addProvider(evenProvider);
|
||||
NewTabUtils.links.addProvider(oddProvider);
|
||||
|
||||
// This is sync since the providers' getLinks are sync.
|
||||
NewTabUtils.links.populateCache(function () {}, false);
|
||||
|
||||
let links = NewTabUtils.links.getLinks();
|
||||
let expectedLinks = makeLinks(NewTabUtils.links.maxNumLinks,
|
||||
2 * NewTabUtils.links.maxNumLinks,
|
||||
1);
|
||||
do_check_eq(links.length, NewTabUtils.links.maxNumLinks);
|
||||
do_check_links(links, expectedLinks);
|
||||
|
||||
NewTabUtils.links.removeProvider(evenProvider);
|
||||
NewTabUtils.links.removeProvider(oddProvider);
|
||||
run_next_test();
|
||||
});
|
||||
|
||||
add_test(function changeLinks() {
|
||||
let expectedLinks = makeLinks(0, 20, 2);
|
||||
let provider = new TestProvider(done => done(expectedLinks));
|
||||
|
||||
NewTabUtils.initWithoutProviders();
|
||||
NewTabUtils.links.addProvider(provider);
|
||||
|
||||
// This is sync since the provider's getLinks is sync.
|
||||
NewTabUtils.links.populateCache(function () {}, false);
|
||||
|
||||
do_check_links(NewTabUtils.links.getLinks(), expectedLinks);
|
||||
|
||||
// Notify of a new link.
|
||||
let newLink = {
|
||||
url: "http://example.com/19",
|
||||
title: "My frecency is 19",
|
||||
frecency: 19,
|
||||
lastVisitDate: 0,
|
||||
};
|
||||
expectedLinks.splice(1, 0, newLink);
|
||||
provider.notifyLinkChanged(newLink);
|
||||
do_check_links(NewTabUtils.links.getLinks(), expectedLinks);
|
||||
|
||||
// Notify of a link that's changed sort criteria.
|
||||
newLink.frecency = 17;
|
||||
expectedLinks.splice(1, 1);
|
||||
expectedLinks.splice(2, 0, newLink);
|
||||
provider.notifyLinkChanged({
|
||||
url: newLink.url,
|
||||
frecency: 17,
|
||||
});
|
||||
do_check_links(NewTabUtils.links.getLinks(), expectedLinks);
|
||||
|
||||
// Notify of a link that's changed title.
|
||||
newLink.title = "My frecency is now 17";
|
||||
provider.notifyLinkChanged({
|
||||
url: newLink.url,
|
||||
title: newLink.title,
|
||||
});
|
||||
do_check_links(NewTabUtils.links.getLinks(), expectedLinks);
|
||||
|
||||
// Notify of a new link again, but this time make it overflow maxNumLinks.
|
||||
provider.maxNumLinks = expectedLinks.length;
|
||||
newLink = {
|
||||
url: "http://example.com/21",
|
||||
frecency: 21,
|
||||
lastVisitDate: 0,
|
||||
};
|
||||
expectedLinks.unshift(newLink);
|
||||
expectedLinks.pop();
|
||||
do_check_eq(expectedLinks.length, provider.maxNumLinks); // Sanity check.
|
||||
provider.notifyLinkChanged(newLink);
|
||||
do_check_links(NewTabUtils.links.getLinks(), expectedLinks);
|
||||
|
||||
// Notify of many links changed.
|
||||
expectedLinks = makeLinks(0, 3, 1);
|
||||
provider.notifyManyLinksChanged();
|
||||
// NewTabUtils.links will now repopulate its cache, which is sync since
|
||||
// the provider's getLinks is sync.
|
||||
do_check_links(NewTabUtils.links.getLinks(), expectedLinks);
|
||||
|
||||
NewTabUtils.links.removeProvider(provider);
|
||||
run_next_test();
|
||||
});
|
||||
|
||||
add_task(function oneProviderAlreadyCached() {
|
||||
let links1 = makeLinks(0, 10, 1);
|
||||
let provider1 = new TestProvider(done => done(links1));
|
||||
|
||||
NewTabUtils.initWithoutProviders();
|
||||
NewTabUtils.links.addProvider(provider1);
|
||||
|
||||
// This is sync since the provider's getLinks is sync.
|
||||
NewTabUtils.links.populateCache(function () {}, false);
|
||||
do_check_links(NewTabUtils.links.getLinks(), links1);
|
||||
|
||||
let links2 = makeLinks(10, 20, 1);
|
||||
let provider2 = new TestProvider(done => done(links2));
|
||||
NewTabUtils.links.addProvider(provider2);
|
||||
|
||||
NewTabUtils.links.populateCache(function () {}, false);
|
||||
do_check_links(NewTabUtils.links.getLinks(), links2.concat(links1));
|
||||
|
||||
NewTabUtils.links.removeProvider(provider1);
|
||||
NewTabUtils.links.removeProvider(provider2);
|
||||
});
|
||||
|
||||
function TestProvider(getLinksFn) {
|
||||
this.getLinks = getLinksFn;
|
||||
this._observers = new Set();
|
||||
}
|
||||
|
||||
TestProvider.prototype = {
|
||||
addObserver: function (observer) {
|
||||
this._observers.add(observer);
|
||||
},
|
||||
notifyLinkChanged: function (link) {
|
||||
this._notifyObservers("onLinkChanged", link);
|
||||
},
|
||||
notifyManyLinksChanged: function () {
|
||||
this._notifyObservers("onManyLinksChanged");
|
||||
},
|
||||
_notifyObservers: function (observerMethodName, arg) {
|
||||
for (let obs of this._observers) {
|
||||
if (obs[observerMethodName])
|
||||
obs[observerMethodName](this, arg);
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
function do_check_links(actualLinks, expectedLinks) {
|
||||
do_check_true(Array.isArray(actualLinks));
|
||||
do_check_eq(actualLinks.length, expectedLinks.length);
|
||||
for (let i = 0; i < expectedLinks.length; i++) {
|
||||
let expected = expectedLinks[i];
|
||||
let actual = actualLinks[i];
|
||||
do_check_eq(actual.url, expected.url);
|
||||
do_check_eq(actual.title, expected.title);
|
||||
do_check_eq(actual.frecency, expected.frecency);
|
||||
do_check_eq(actual.lastVisitDate, expected.lastVisitDate);
|
||||
}
|
||||
}
|
||||
|
||||
function makeLinks(frecRangeStart, frecRangeEnd, step) {
|
||||
let links = [];
|
||||
// Remember, links are ordered by frecency descending.
|
||||
for (let i = frecRangeEnd; i > frecRangeStart; i -= step) {
|
||||
links.push({
|
||||
url: "http://example.com/" + i,
|
||||
title: "My frecency is " + i,
|
||||
frecency: i,
|
||||
lastVisitDate: 0,
|
||||
});
|
||||
}
|
||||
return links;
|
||||
}
|
@ -8,12 +8,14 @@ support-files =
|
||||
zips/zen.zip
|
||||
|
||||
[test_AsyncShutdown.js]
|
||||
[test_BinarySearch.js]
|
||||
[test_DeferredTask.js]
|
||||
[test_dict.js]
|
||||
[test_DirectoryLinksProvider.js]
|
||||
[test_FileUtils.js]
|
||||
[test_Http.js]
|
||||
[test_Log.js]
|
||||
[test_NewTabUtils.js]
|
||||
[test_PermissionsUtils.js]
|
||||
[test_Preferences.js]
|
||||
[test_Promise.js]
|
||||
|
Loading…
Reference in New Issue
Block a user