mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-28 23:31:56 +00:00
Bug 1077652 - Simplify about:newtab page update mechanism and correct behavior to work better with preloading r=gijs
This commit is contained in:
parent
b584f990fc
commit
e2f56217e8
@ -4,6 +4,9 @@
|
|||||||
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
// The amount of time we wait while coalescing updates for hidden pages.
|
||||||
|
const SCHEDULE_UPDATE_TIMEOUT_MS = 1000;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This singleton represents the whole 'New Tab Page' and takes care of
|
* This singleton represents the whole 'New Tab Page' and takes care of
|
||||||
* initializing all its components.
|
* initializing all its components.
|
||||||
@ -69,16 +72,39 @@ let gPage = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Updates the whole page and the grid when the storage has changed.
|
* Updates the page's grid right away for visible pages. If the page is
|
||||||
* @param aOnlyIfHidden If true, the page is updated only if it's hidden in
|
* currently hidden, i.e. in a background tab or in the preloader, then we
|
||||||
* the preloader.
|
* batch multiple update requests and refresh the grid once after a short
|
||||||
|
* delay. Accepts a single parameter the specifies the reason for requesting
|
||||||
|
* a page update. The page may decide to delay or prevent a requested updated
|
||||||
|
* based on the given reason.
|
||||||
*/
|
*/
|
||||||
update: function Page_update(aOnlyIfHidden=false) {
|
update(reason = "") {
|
||||||
let skipUpdate = aOnlyIfHidden && !document.hidden;
|
// Update immediately if we're visible.
|
||||||
// The grid might not be ready yet as we initialize it asynchronously.
|
if (!document.hidden) {
|
||||||
if (gGrid.ready && !skipUpdate) {
|
// Ignore updates where reason=links-changed as those signal that the
|
||||||
gGrid.refresh();
|
// provider's set of links changed. We don't want to update visible pages
|
||||||
|
// in that case, it is ok to wait until the user opens the next tab.
|
||||||
|
if (reason != "links-changed" && gGrid.ready) {
|
||||||
|
gGrid.refresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Bail out if we scheduled before.
|
||||||
|
if (this._scheduleUpdateTimeout) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this._scheduleUpdateTimeout = setTimeout(() => {
|
||||||
|
// Refresh if the grid is ready.
|
||||||
|
if (gGrid.ready) {
|
||||||
|
gGrid.refresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
this._scheduleUpdateTimeout = null;
|
||||||
|
}, SCHEDULE_UPDATE_TIMEOUT_MS);
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -170,6 +196,15 @@ let gPage = {
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case "visibilitychange":
|
case "visibilitychange":
|
||||||
|
// Cancel any delayed updates for hidden pages now that we're visible.
|
||||||
|
if (this._scheduleUpdateTimeout) {
|
||||||
|
clearTimeout(this._scheduleUpdateTimeout);
|
||||||
|
this._scheduleUpdateTimeout = null;
|
||||||
|
|
||||||
|
// An update was pending so force an update now.
|
||||||
|
this.update();
|
||||||
|
}
|
||||||
|
|
||||||
setTimeout(() => this.onPageFirstVisible());
|
setTimeout(() => this.onPageFirstVisible());
|
||||||
removeEventListener("visibilitychange", this);
|
removeEventListener("visibilitychange", this);
|
||||||
break;
|
break;
|
||||||
|
@ -44,6 +44,10 @@ function runTests() {
|
|||||||
"New page grid is updated correctly.");
|
"New page grid is updated correctly.");
|
||||||
|
|
||||||
gBrowser.removeTab(newTab);
|
gBrowser.removeTab(newTab);
|
||||||
|
|
||||||
|
// Wait until the original tab is visible again.
|
||||||
|
let doc = existingTab.linkedBrowser.contentDocument;
|
||||||
|
yield waitForCondition(() => !doc.hidden).then(TestRunner.next);
|
||||||
}
|
}
|
||||||
|
|
||||||
gBrowser.removeTab(existingTab);
|
gBrowser.removeTab(existingTab);
|
||||||
|
@ -1,44 +1,32 @@
|
|||||||
/* Any copyright is dedicated to the Public Domain.
|
/* Any copyright is dedicated to the Public Domain.
|
||||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||||
|
|
||||||
|
"use strict";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks that newtab is updated as its links change.
|
* Checks that newtab is updated as its links change.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
function runTests() {
|
function runTests() {
|
||||||
// First, start with an empty page. setLinks will trigger a hidden page
|
// 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
|
// 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
|
// 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.
|
// right update and not the one triggered by setLinks.
|
||||||
//
|
yield whenPagesUpdatedAnd(resolve => setLinks([], resolve));
|
||||||
// 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.
|
// Strategy: Add some visits, open a new page, check the grid, repeat.
|
||||||
fillHistory([link(1)]);
|
yield fillHistoryAndWaitForPageUpdate([1]);
|
||||||
yield whenPagesUpdated(null, true);
|
|
||||||
yield addNewTabPageTab();
|
yield addNewTabPageTab();
|
||||||
checkGrid("1,,,,,,,,");
|
checkGrid("1,,,,,,,,");
|
||||||
|
|
||||||
fillHistory([link(2)]);
|
yield fillHistoryAndWaitForPageUpdate([2]);
|
||||||
yield whenPagesUpdated(null, true);
|
|
||||||
yield addNewTabPageTab();
|
yield addNewTabPageTab();
|
||||||
checkGrid("2,1,,,,,,,");
|
checkGrid("2,1,,,,,,,");
|
||||||
|
|
||||||
fillHistory([link(1)]);
|
yield fillHistoryAndWaitForPageUpdate([1]);
|
||||||
yield whenPagesUpdated(null, true);
|
|
||||||
yield addNewTabPageTab();
|
yield addNewTabPageTab();
|
||||||
checkGrid("1,2,,,,,,,");
|
checkGrid("1,2,,,,,,,");
|
||||||
|
|
||||||
// Wait for fillHistory to add all links before waiting for an update
|
yield fillHistoryAndWaitForPageUpdate([2, 3, 4]);
|
||||||
yield fillHistory([link(2), link(3), link(4)], TestRunner.next);
|
|
||||||
yield whenPagesUpdated(null, true);
|
|
||||||
yield addNewTabPageTab();
|
yield addNewTabPageTab();
|
||||||
checkGrid("2,1,3,4,,,,,");
|
checkGrid("2,1,3,4,,,,,");
|
||||||
|
|
||||||
@ -46,6 +34,16 @@ function runTests() {
|
|||||||
is(getCell(1).site.link.type, "history", "added link is history");
|
is(getCell(1).site.link.type, "history", "added link is history");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function fillHistoryAndWaitForPageUpdate(links) {
|
||||||
|
return whenPagesUpdatedAnd(resolve => fillHistory(links.map(link), resolve));
|
||||||
|
}
|
||||||
|
|
||||||
|
function whenPagesUpdatedAnd(promiseConstructor) {
|
||||||
|
let promise1 = new Promise(whenPagesUpdated);
|
||||||
|
let promise2 = new Promise(promiseConstructor);
|
||||||
|
return Promise.all([promise1, promise2]).then(TestRunner.next);
|
||||||
|
}
|
||||||
|
|
||||||
function link(id) {
|
function link(id) {
|
||||||
return { url: "http://example" + id + ".com/", title: "site#" + id };
|
return { url: "http://example" + id + ".com/", title: "site#" + id };
|
||||||
}
|
}
|
||||||
|
@ -214,7 +214,7 @@ function getCell(aIndex) {
|
|||||||
* {url: "http://example2.com/", title: "site#2"},
|
* {url: "http://example2.com/", title: "site#2"},
|
||||||
* {url: "http://example3.com/", title: "site#3"}]
|
* {url: "http://example3.com/", title: "site#3"}]
|
||||||
*/
|
*/
|
||||||
function setLinks(aLinks) {
|
function setLinks(aLinks, aCallback = TestRunner.next) {
|
||||||
let links = aLinks;
|
let links = aLinks;
|
||||||
|
|
||||||
if (typeof links == "string") {
|
if (typeof links == "string") {
|
||||||
@ -233,7 +233,7 @@ function setLinks(aLinks) {
|
|||||||
fillHistory(links, function () {
|
fillHistory(links, function () {
|
||||||
NewTabUtils.links.populateCache(function () {
|
NewTabUtils.links.populateCache(function () {
|
||||||
NewTabUtils.allPages.update();
|
NewTabUtils.allPages.update();
|
||||||
TestRunner.next();
|
aCallback();
|
||||||
}, true);
|
}, true);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -249,7 +249,7 @@ function clearHistory(aCallback) {
|
|||||||
PlacesUtils.history.removeAllPages();
|
PlacesUtils.history.removeAllPages();
|
||||||
}
|
}
|
||||||
|
|
||||||
function fillHistory(aLinks, aCallback) {
|
function fillHistory(aLinks, aCallback = TestRunner.next) {
|
||||||
let numLinks = aLinks.length;
|
let numLinks = aLinks.length;
|
||||||
if (!numLinks) {
|
if (!numLinks) {
|
||||||
if (aCallback)
|
if (aCallback)
|
||||||
@ -323,6 +323,33 @@ function restore() {
|
|||||||
NewTabUtils.restore();
|
NewTabUtils.restore();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wait until a given condition becomes true.
|
||||||
|
*/
|
||||||
|
function waitForCondition(aConditionFn, aMaxTries=50, aCheckInterval=100) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
let tries = 0;
|
||||||
|
|
||||||
|
function tryNow() {
|
||||||
|
tries++;
|
||||||
|
|
||||||
|
if (aConditionFn()) {
|
||||||
|
resolve();
|
||||||
|
} else if (tries < aMaxTries) {
|
||||||
|
tryAgain();
|
||||||
|
} else {
|
||||||
|
reject("Condition timed out: " + aConditionFn.toSource());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function tryAgain() {
|
||||||
|
setTimeout(tryNow, aCheckInterval);
|
||||||
|
}
|
||||||
|
|
||||||
|
tryAgain();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new tab containing 'about:newtab'.
|
* Creates a new tab containing 'about:newtab'.
|
||||||
*/
|
*/
|
||||||
@ -349,7 +376,7 @@ function addNewTabPageTabPromise() {
|
|||||||
|
|
||||||
// The new tab page might have been preloaded in the background.
|
// The new tab page might have been preloaded in the background.
|
||||||
if (browser.contentDocument.readyState == "complete") {
|
if (browser.contentDocument.readyState == "complete") {
|
||||||
whenNewTabLoaded();
|
waitForCondition(() => !browser.contentDocument.hidden).then(whenNewTabLoaded);
|
||||||
return deferred.promise;
|
return deferred.promise;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -617,18 +644,14 @@ function createDragEvent(aEventType, aData) {
|
|||||||
/**
|
/**
|
||||||
* Resumes testing when all pages have been updated.
|
* Resumes testing when all pages have been updated.
|
||||||
* @param aCallback Called when done. If not specified, TestRunner.next is used.
|
* @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, aOnlyIfHidden=false) {
|
function whenPagesUpdated(aCallback = TestRunner.next) {
|
||||||
let page = {
|
let page = {
|
||||||
observe: _ => _,
|
observe: _ => _,
|
||||||
update: function (onlyIfHidden=false) {
|
|
||||||
if (onlyIfHidden == aOnlyIfHidden) {
|
update() {
|
||||||
NewTabUtils.allPages.unregister(this);
|
NewTabUtils.allPages.unregister(this);
|
||||||
executeSoon(aCallback || TestRunner.next);
|
executeSoon(aCallback);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -22,10 +22,6 @@ XPCOMUtils.defineLazyModuleGetter(this, "PageThumbs",
|
|||||||
XPCOMUtils.defineLazyModuleGetter(this, "BinarySearch",
|
XPCOMUtils.defineLazyModuleGetter(this, "BinarySearch",
|
||||||
"resource://gre/modules/BinarySearch.jsm");
|
"resource://gre/modules/BinarySearch.jsm");
|
||||||
|
|
||||||
XPCOMUtils.defineLazyGetter(this, "Timer", () => {
|
|
||||||
return Cu.import("resource://gre/modules/Timer.jsm", {});
|
|
||||||
});
|
|
||||||
|
|
||||||
XPCOMUtils.defineLazyGetter(this, "gPrincipal", function () {
|
XPCOMUtils.defineLazyGetter(this, "gPrincipal", function () {
|
||||||
let uri = Services.io.newURI("about:newtab", null, null);
|
let uri = Services.io.newURI("about:newtab", null, null);
|
||||||
return Services.scriptSecurityManager.getNoAppCodebasePrincipal(uri);
|
return Services.scriptSecurityManager.getNoAppCodebasePrincipal(uri);
|
||||||
@ -61,9 +57,6 @@ const LINKS_GET_LINKS_LIMIT = 100;
|
|||||||
// The gather telemetry topic.
|
// The gather telemetry topic.
|
||||||
const TOPIC_GATHER_TELEMETRY = "gather-telemetry";
|
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.
|
* Calculate the MD5 hash for a string.
|
||||||
* @param aValue
|
* @param aValue
|
||||||
@ -281,30 +274,16 @@ let AllPages = {
|
|||||||
/**
|
/**
|
||||||
* Updates all currently active pages but the given one.
|
* Updates all currently active pages but the given one.
|
||||||
* @param aExceptPage The page to exclude from updating.
|
* @param aExceptPage The page to exclude from updating.
|
||||||
* @param aHiddenPagesOnly If true, only pages hidden in the preloader are
|
* @param aReason The reason for updating all pages.
|
||||||
* updated.
|
|
||||||
*/
|
*/
|
||||||
update: function AllPages_update(aExceptPage, aHiddenPagesOnly=false) {
|
update(aExceptPage, aReason = "") {
|
||||||
this._pages.forEach(function (aPage) {
|
this._pages.forEach(function (aPage) {
|
||||||
if (aExceptPage != aPage)
|
if (aExceptPage != aPage) {
|
||||||
aPage.update(aHiddenPagesOnly);
|
aPage.update(aReason);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
|
||||||
* 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);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Implements the nsIObserver interface to get notified when the preference
|
* Implements the nsIObserver interface to get notified when the preference
|
||||||
* value changes or when a new copy of a page thumbnail is available.
|
* value changes or when a new copy of a page thumbnail is available.
|
||||||
@ -1016,8 +995,9 @@ let Links = {
|
|||||||
updatePages = true;
|
updatePages = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (updatePages)
|
if (updatePages) {
|
||||||
AllPages.scheduleUpdateForHiddenPages();
|
AllPages.update(null, "links-changed");
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -1025,7 +1005,7 @@ let Links = {
|
|||||||
*/
|
*/
|
||||||
onManyLinksChanged: function Links_onManyLinksChanged(aProvider) {
|
onManyLinksChanged: function Links_onManyLinksChanged(aProvider) {
|
||||||
this._populateProviderCache(aProvider, () => {
|
this._populateProviderCache(aProvider, () => {
|
||||||
AllPages.scheduleUpdateForHiddenPages();
|
AllPages.update(null, "links-changed");
|
||||||
}, true);
|
}, true);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user