This commit is contained in:
Phil Ringnalda 2015-05-10 18:43:50 -07:00
Родитель da16d18ba9 064b393601
Коммит 7a8f8886f7
5 изменённых файлов: 156 добавлений и 70 удалений

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

@ -11,80 +11,59 @@ thisTestLeaksUncaughtRejectionsAndShouldBeFixed("TypeError: aValue.content is un
/**
* Bug 863102 - Automatically scroll down upon new network requests.
*/
function test() {
add_task(function*() {
requestLongerTimeout(2);
let monitor, debuggee, requestsContainer, scrollTop;
initNetMonitor(INFINITE_GET_URL).then(([aTab, aDebuggee, aMonitor]) => {
monitor = aMonitor;
debuggee = aDebuggee;
let win = monitor.panelWin;
let topNode = win.document.getElementById("requests-menu-contents");
requestsContainer = topNode.getElementsByTagName("scrollbox")[0];
ok(!!requestsContainer, "Container element exists as expected.");
})
let [aTab, aDebuggee, aMonitor] = yield initNetMonitor(INFINITE_GET_URL);
monitor = aMonitor;
debuggee = aDebuggee;
let win = monitor.panelWin;
let topNode = win.document.getElementById("requests-menu-contents");
requestsContainer = topNode.getElementsByTagName("scrollbox")[0];
ok(!!requestsContainer, "Container element exists as expected.");
// (1) Check that the scroll position is maintained at the bottom
// when the requests overflow the vertical size of the container.
.then(() => {
return waitForRequestsToOverflowContainer(monitor, requestsContainer);
})
.then(() => {
ok(scrolledToBottom(requestsContainer), "Scrolled to bottom on overflow.");
})
yield waitForRequestsToOverflowContainer(monitor, requestsContainer);
yield waitForScroll(monitor);
ok(scrolledToBottom(requestsContainer), "Scrolled to bottom on overflow.");
// (2) Now set the scroll position somewhere in the middle and check
// that additional requests do not change the scroll position.
.then(() => {
let children = requestsContainer.childNodes;
let middleNode = children.item(children.length / 2);
middleNode.scrollIntoView();
ok(!scrolledToBottom(requestsContainer), "Not scrolled to bottom.");
scrollTop = requestsContainer.scrollTop; // save for comparison later
return waitForNetworkEvents(monitor, 8);
})
.then(() => {
is(requestsContainer.scrollTop, scrollTop, "Did not scroll.");
})
let children = requestsContainer.childNodes;
let middleNode = children.item(children.length / 2);
middleNode.scrollIntoView();
ok(!scrolledToBottom(requestsContainer), "Not scrolled to bottom.");
scrollTop = requestsContainer.scrollTop; // save for comparison later
yield waitForNetworkEvents(monitor, 8);
yield waitSomeTime();
is(requestsContainer.scrollTop, scrollTop, "Did not scroll.");
// (3) Now set the scroll position back at the bottom and check that
// additional requests *do* cause the container to scroll down.
.then(() => {
requestsContainer.scrollTop = requestsContainer.scrollHeight;
ok(scrolledToBottom(requestsContainer), "Set scroll position to bottom.");
return waitForNetworkEvents(monitor, 8);
})
.then(() => {
ok(scrolledToBottom(requestsContainer), "Still scrolled to bottom.");
})
requestsContainer.scrollTop = requestsContainer.scrollHeight;
ok(scrolledToBottom(requestsContainer), "Set scroll position to bottom.");
yield waitForNetworkEvents(monitor, 8);
yield waitForScroll(monitor);
ok(scrolledToBottom(requestsContainer), "Still scrolled to bottom.");
// (4) Now select an item in the list and check that additional requests
// do not change the scroll position.
.then(() => {
monitor.panelWin.NetMonitorView.RequestsMenu.selectedIndex = 0;
return waitForNetworkEvents(monitor, 8);
})
.then(() => {
is(requestsContainer.scrollTop, 0, "Did not scroll.");
})
monitor.panelWin.NetMonitorView.RequestsMenu.selectedIndex = 0;
yield waitForNetworkEvents(monitor, 8);
yield waitSomeTime();
is(requestsContainer.scrollTop, 0, "Did not scroll.");
// Done; clean up.
.then(() => {
return teardown(monitor).then(finish);
})
// Done: clean up.
yield teardown(monitor);
// Handle exceptions in the chain of promises.
.then(null, (err) => {
ok(false, err);
finish();
});
finish();
function waitForRequestsToOverflowContainer (aMonitor, aContainer) {
function waitForRequestsToOverflowContainer(aMonitor, aContainer) {
return waitForNetworkEvents(aMonitor, 1).then(() => {
if (aContainer.scrollHeight > aContainer.clientHeight) {
// Wait for some more just for good measure.
return waitForNetworkEvents(aMonitor, 8);
return promise.resolve();
} else {
return waitForRequestsToOverflowContainer(aMonitor, aContainer);
}
@ -94,4 +73,14 @@ function test() {
function scrolledToBottom(aElement) {
return aElement.scrollTop + aElement.clientHeight >= aElement.scrollHeight;
}
}
function waitSomeTime() {
let waitSomeTime = promise.defer();
setTimeout(waitSomeTime.resolve, 50); // Wait to make sure no scrolls happen
return waitSomeTime.promise;
}
function waitForScroll(aMonitor) {
return aMonitor._view.RequestsMenu.widget.once("scroll-to-bottom");
}
});

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

@ -10,9 +10,12 @@ const Cu = Components.utils;
Cu.import("resource:///modules/devtools/ViewHelpers.jsm");
Cu.import("resource://gre/modules/devtools/event-emitter.js");
const {DeferredTask} = Cu.import("resource://gre/modules/DeferredTask.jsm", {});
this.EXPORTED_SYMBOLS = ["SideMenuWidget"];
const SCROLL_FREQUENCY = 16;
/**
* A simple side menu, with the ability of grouping menu items.
*
@ -113,20 +116,74 @@ SideMenuWidget.prototype = {
!this._selectedItem &&
// 3. The new item should be appended at the end of the list.
(aIndex < 0 || aIndex >= this._orderedMenuElementsArray.length) &&
// 4. The list should already be scrolled at the bottom.
(this._list.scrollTop + this._list.clientHeight >= this._list.scrollHeight);
// 4. We aren't waiting for a scroll to happen.
(!this._scrollToBottomTask || !this._scrollToBottomTask.isArmed) &&
// 5. The list should already be scrolled at the bottom.
this.isScrolledToBottom();
let group = this._getMenuGroupForName(aAttachment.group);
let item = this._getMenuItemForGroup(group, aContents, aAttachment);
let element = item.insertSelfAt(aIndex);
if (maintainScrollAtBottom) {
this._list.scrollTop = this._list.scrollHeight;
this.scrollToBottom();
}
return element;
},
/**
* Checks to see if the list is scrolled all the way to the bottom.
* Uses getBoundsWithoutFlushing to limit the performance impact
* of this function.
*
* @return bool
*/
isScrolledToBottom: function() {
if (this._list.lastElementChild) {
let utils = this.window.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDOMWindowUtils);
let childRect = utils.getBoundsWithoutFlushing(this._list.lastElementChild);
let listRect = utils.getBoundsWithoutFlushing(this._list);
// Cheap way to check if it's scrolled all the way to the bottom.
return (childRect.height + childRect.top) <= listRect.bottom;
}
return false;
},
/**
* Scroll the list to the bottom after a timeout.
* If the user scrolls in the meantime, cancel this operation.
*/
scrollToBottom: function() {
// Lazily attach this functionality to the object, so it won't get
// created unless if this scrollToBottom behavior is needed.
if (!this._scrollToBottomTask) {
// The scroll event fires asynchronously, so we need to keep a bit to
// distinguish between user-initiated events and scrollTop assignment.
let ignoreNextScroll = false;
this._scrollToBottomTask = new DeferredTask(() => {
ignoreNextScroll = true;
this._list.scrollTop = this._list.scrollHeight;
this.emit("scroll-to-bottom");
}, SCROLL_FREQUENCY);
// On a user scroll, cancel any pending calls to the scroll function.
this._list.addEventListener("scroll", () => {
if (!ignoreNextScroll && this._scrollToBottomTask.isArmed &&
!this.isScrolledToBottom()) {
this._scrollToBottomTask.disarm();
}
ignoreNextScroll = false;
}, true);
}
this._scrollToBottomTask.arm();
},
/**
* Returns the child node in this container situated at the specified index.
*

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

@ -74,10 +74,10 @@ inspector.collapsePane=Collapse pane
inspector.expandPane=Expand pane
# LOCALIZATION NOTE (inspector.searchResultsCount): This is the label that
# will show up next to the inspector search box showing the current result
# index alongside the total number of search results. For example, "3 of 9".
# will show up next to the inspector search box. %1$S is the current result
# index and %2$S is the total number of search results. For example: "3 of 9".
# This won't be visible until the search box is updated in Bug 835896.
inspector.searchResultsCount=%S of %S
inspector.searchResultsCount2=%1$S of %2$S
# LOCALIZATION NOTE (inspector.searchResultsNone): This is the label that
# will show up next to the inspector search box when no matches were found

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

@ -43,5 +43,36 @@ newtab.intro.paragraph2=In order to provide this service, Mozilla collects and u
# LOCALIZATION NOTE(newtab.intro.paragraph3): %1$S will be replaced inline by
# the gear icon used to customize the new tab window.
newtab.intro.paragraph3=You can turn off the tiles feature by clicking the %1$S button for your preferences.
# LOCALIZATION NOTE(newtab.intro.paragraph4): %1$S will be replaced inline by
# the gear icon used to customize the new tab window. %2$S will be replaced by
# newtab.intro.controls as text
newtab.intro.paragraph4=You can turn off this feature by clicking the gear (%1$S) button and selecting "Show blank page" in the %2$S menu.
newtab.intro.paragraph5=New Tab will show the sites you visit most frequently, along with sites we think might be of interest to you. To get started, you'll see several sites from Mozilla.
# LOCALIZATION NOTE(newtab.intro.paragraph6): %1$S will be replaced by
# newtab.intro.paragraph6.remove as bold text. %2$S will be replaced by
# newtab.intro.paragraph6.pin as bold text
newtab.intro.paragraph6=You can %1$S or %2$S any site by using the controls available on rollover.
newtab.intro.paragraph6.remove=remove
newtab.intro.paragraph6.pin=pin
newtab.intro.paragraph7=Some of the sites you will see may be suggested by Mozilla and may be sponsored by a Mozilla partner. We'll always indicate which sites are sponsored.
# LOCALIZATION NOTE(newtab.intro.paragraph8): %1$S will be replaced by
# brandShortName as text. %2$S will be replaced inline by an active link using
# string newtab.learn.link as text.
newtab.intro.paragraph8=%1$S will only show sites that most closely match your interests on the Web. %2$S
newtab.intro.paragraph9=Now when you open New Tab, you'll also see sites we think might be interesting to you.
# LOCALIZATION NOTE(newtab.intro.controls): the controls in the gear icon
# menu for customizing the new tab window. Used in newtab.intro.paragraph4
newtab.intro.controls=New Tab Controls
newtab.learn.link=Learn more…
newtab.privacy.link=Privacy Notice
newtab.learn.link2=More about New Tab
newtab.privacy.link2=About your privacy
# LOCALIZATION NOTE(newtab.intro.header.welcome): %1$S will be replaced by
# brandShortName as bold text.
newtab.intro.header.welcome=Welcome to New Tab on %1$S!
newtab.intro.header.update=New Tab got an update!
newtab.intro.skip=Skip this
newtab.intro.continue=Continue tour
newtab.intro.back=Back
newtab.intro.next=Next
newtab.intro.gotit=Got it!

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

@ -145,20 +145,26 @@ function saveDocument(aDocument, aSkipPrompt)
// Failure to get a content-disposition is ok
}
var cacheKey = null;
let cacheKey = null;
try {
cacheKey =
let shEntry =
ifreq.getInterface(Components.interfaces.nsIWebNavigation)
.QueryInterface(Components.interfaces.nsIWebPageDescriptor);
.QueryInterface(Components.interfaces.nsIWebPageDescriptor)
.currentDescriptor
.QueryInterface(Components.interfaces.nsISHEntry);
shEntry.cacheKey.QueryInterface(Components.interfaces.nsISupportsPRUint32);
// In the event that the cacheKey is a CPOW, we cannot pass it to
// nsIWebBrowserPersist, so we create a new one and copy the value
// over. This is a workaround until bug 1101100 is fixed.
cacheKey = Cc["@mozilla.org/supports-PRUint32;1"]
.createInstance(Ci.nsISupportsPRUint32);
cacheKey.data = shEntry.cacheKey.data;
} catch (ex) {
// We might not find it in the cache. Oh, well.
}
if (cacheKey && Components.utils.isCrossProcessWrapper(cacheKey)) {
// Don't use a cache key from another process. See bug 1128050.
cacheKey = null;
}
internalSave(aDocument.location.href, aDocument, null, contentDisposition,
aDocument.contentType, false, null, null,
aDocument.referrer ? makeURI(aDocument.referrer) : null,
@ -339,6 +345,9 @@ function internalSave(aURL, aDocument, aDefaultFileName, aContentDisposition,
// If we're saving a document, and are saving either in complete mode or
// as converted text, pass the document to the web browser persist component.
// If we're just saving the HTML (second option in the list), send only the URI.
let nonCPOWDocument =
aDocument && !Components.utils.isCrossProcessWrapper(aDocument);
var persistArgs = {
sourceURI : sourceURI,
sourceReferrer : aReferrer,
@ -346,7 +355,7 @@ function internalSave(aURL, aDocument, aDefaultFileName, aContentDisposition,
targetContentType : (saveAsType == kSaveAsType_Text) ? "text/plain" : null,
targetFile : file,
sourceCacheKey : aCacheKey,
sourcePostData : aDocument ? getPostData(aDocument) : null,
sourcePostData : nonCPOWDocument ? getPostData(aDocument) : null,
bypassCache : aShouldBypassCache,
initiatingWindow : aInitiatingDocument.defaultView
};