Skip to content

Commit

Permalink
Merge f-t to m-c, a=merge
Browse files Browse the repository at this point in the history
  • Loading branch information
philor committed May 11, 2015
2 parents 1741acb + 54033c1 commit 8606422
Show file tree
Hide file tree
Showing 5 changed files with 156 additions and 70 deletions.
99 changes: 44 additions & 55 deletions browser/devtools/netmonitor/test/browser_net_autoscroll.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand All @@ -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");
}
});
63 changes: 60 additions & 3 deletions browser/devtools/shared/widgets/SideMenuWidget.jsm
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*
Expand Down Expand Up @@ -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.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
31 changes: 31 additions & 0 deletions browser/locales/en-US/chrome/browser/newTab.properties
Original file line number Diff line number Diff line change
Expand Up @@ -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!
27 changes: 18 additions & 9 deletions toolkit/content/contentAreaUtils.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -339,14 +345,17 @@ 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,
sourceDocument : useSaveDocument ? aDocument : null,
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
};
Expand Down

0 comments on commit 8606422

Please sign in to comment.