Merge m-c to inbound

This commit is contained in:
Wes Kocher 2014-03-31 20:59:59 -07:00
commit 12c1f4f8b2
132 changed files with 3576 additions and 919 deletions

View File

@ -22,4 +22,4 @@
# changes to stick? As of bug 928195, this shouldn't be necessary! Please
# don't change CLOBBER for WebIDL changes any more.
Bug 957865 - Non-clobbered ASAN builds were failing all mochitests after the clang upgrade
Bug 989137 - /experiments needed clobber to build on OSX

View File

@ -19,7 +19,7 @@
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="eee8caa81a368f0feace718201ed15a423812c18"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="874fe42b82e8d819d592690e74db91c07179e68c"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="266bca6e60dad43e395f38b66edabe8bdc882334"/>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
<project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="d11f524d00cacf5ba0dfbf25e4aa2158b1c3a036"/>

View File

@ -17,7 +17,7 @@
</project>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="eee8caa81a368f0feace718201ed15a423812c18"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="874fe42b82e8d819d592690e74db91c07179e68c"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="266bca6e60dad43e395f38b66edabe8bdc882334"/>
<project name="moztt" path="external/moztt" remote="b2g" revision="5b93c7150acac5f657675b91889d828cc2b532e3"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="89c5816399e71bda92a8959b5b771c04d6672ea3"/>

View File

@ -15,7 +15,7 @@
<project name="platform_build" path="build" remote="b2g" revision="a9e08b91e9cd1f0930f16cfc49ec72f63575d5fe">
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="gaia" path="gaia" remote="mozillaorg" revision="eee8caa81a368f0feace718201ed15a423812c18"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="874fe42b82e8d819d592690e74db91c07179e68c"/>
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="266bca6e60dad43e395f38b66edabe8bdc882334"/>
<project name="moztt" path="external/moztt" remote="b2g" revision="5b93c7150acac5f657675b91889d828cc2b532e3"/>

View File

@ -19,7 +19,7 @@
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="eee8caa81a368f0feace718201ed15a423812c18"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="874fe42b82e8d819d592690e74db91c07179e68c"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="266bca6e60dad43e395f38b66edabe8bdc882334"/>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
<project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="d11f524d00cacf5ba0dfbf25e4aa2158b1c3a036"/>

View File

@ -4,6 +4,6 @@
"remote": "",
"branch": ""
},
"revision": "6c593455e3d1292120a6f3d41ec5d06bc91019f1",
"revision": "f3575a1613e6c94fbc6b2ae01fd00130ee1b3f8a",
"repo_path": "/integration/gaia-central"
}

View File

@ -17,7 +17,7 @@
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="eee8caa81a368f0feace718201ed15a423812c18"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="874fe42b82e8d819d592690e74db91c07179e68c"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="266bca6e60dad43e395f38b66edabe8bdc882334"/>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
<project name="librecovery" path="librecovery" remote="b2g" revision="1f6a1fe07f81c5bc5e1d079c9b60f7f78ca2bf4f"/>

View File

@ -15,7 +15,7 @@
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="eee8caa81a368f0feace718201ed15a423812c18"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="874fe42b82e8d819d592690e74db91c07179e68c"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="266bca6e60dad43e395f38b66edabe8bdc882334"/>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
<project name="librecovery" path="librecovery" remote="b2g" revision="1f6a1fe07f81c5bc5e1d079c9b60f7f78ca2bf4f"/>

View File

@ -19,7 +19,7 @@
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="eee8caa81a368f0feace718201ed15a423812c18"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="874fe42b82e8d819d592690e74db91c07179e68c"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="266bca6e60dad43e395f38b66edabe8bdc882334"/>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
<project name="librecovery" path="librecovery" remote="b2g" revision="1f6a1fe07f81c5bc5e1d079c9b60f7f78ca2bf4f"/>

View File

@ -17,7 +17,7 @@
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="eee8caa81a368f0feace718201ed15a423812c18"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="874fe42b82e8d819d592690e74db91c07179e68c"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="266bca6e60dad43e395f38b66edabe8bdc882334"/>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
<project name="librecovery" path="librecovery" remote="b2g" revision="1f6a1fe07f81c5bc5e1d079c9b60f7f78ca2bf4f"/>

View File

@ -17,7 +17,7 @@
</project>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="eee8caa81a368f0feace718201ed15a423812c18"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="874fe42b82e8d819d592690e74db91c07179e68c"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="266bca6e60dad43e395f38b66edabe8bdc882334"/>
<project name="moztt" path="external/moztt" remote="b2g" revision="5b93c7150acac5f657675b91889d828cc2b532e3"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="89c5816399e71bda92a8959b5b771c04d6672ea3"/>

View File

@ -17,7 +17,7 @@
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="eee8caa81a368f0feace718201ed15a423812c18"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="874fe42b82e8d819d592690e74db91c07179e68c"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="266bca6e60dad43e395f38b66edabe8bdc882334"/>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
<project name="librecovery" path="librecovery" remote="b2g" revision="1f6a1fe07f81c5bc5e1d079c9b60f7f78ca2bf4f"/>

View File

@ -1187,22 +1187,20 @@ var gBrowserInit = {
let windows8WindowFrameColor = Cu.import("resource:///modules/Windows8WindowFrameColor.jsm", {}).Windows8WindowFrameColor;
let windowFrameColor = windows8WindowFrameColor.get();
// Formula from W3C Techniques For Accessibility Evaluation And
// Repair Tools, Section 2.2 http://www.w3.org/TR/AERT#color
let brightnessThreshold = 125;
let colorThreshold = 500;
let bY = windowFrameColor[0] * .299 +
windowFrameColor[1] * .587 +
windowFrameColor[2] * .114;
let fY = 0; // Default to black for foreground text.
let brightnessDifference = Math.abs(bY - fY);
// Color difference calculation is simplified because black is 0 for R,G,B.
let colorDifference = windowFrameColor[0] + windowFrameColor[1] + windowFrameColor[2];
// Brightness is defined within {0, 255}. Set an attribute
// if the window frame color doesn't reach these thresholds
// so the theme can be adjusted for readability.
if (brightnessDifference < brightnessThreshold && colorDifference < colorThreshold) {
// Formula from W3C's WCAG 2.0 spec's color ratio and relative luminance,
// section 1.3.4, http://www.w3.org/TR/WCAG20/ .
windowFrameColor = windowFrameColor.map((color) => {
if (color <= 10) {
return color / 255 / 12.92;
}
return Math.pow(((color / 255) + 0.055) / 1.055, 2.4);
});
let backgroundLuminance = windowFrameColor[0] * 0.2126 +
windowFrameColor[1] * 0.7152 +
windowFrameColor[2] * 0.0722;
let foregroundLuminance = 0; // Default to black for foreground text.
let contrastRatio = (backgroundLuminance + 0.05) / (foregroundLuminance + 0.05);
if (contrastRatio < 3) {
document.documentElement.setAttribute("darkwindowframe", "true");
}
}
@ -1882,55 +1880,47 @@ function loadURI(uri, referrer, postData, allowThirdPartyFixup) {
} catch (e) {}
}
function getShortcutOrURIAndPostData(aURL) {
return Task.spawn(function() {
let mayInheritPrincipal = false;
let postData = null;
let shortcutURL = null;
let keyword = aURL;
let param = "";
function getShortcutOrURIAndPostData(aURL, aCallback) {
let mayInheritPrincipal = false;
let postData = null;
let shortcutURL = null;
let keyword = aURL;
let param = "";
let offset = aURL.indexOf(" ");
if (offset > 0) {
keyword = aURL.substr(0, offset);
param = aURL.substr(offset + 1);
}
let offset = aURL.indexOf(" ");
if (offset > 0) {
keyword = aURL.substr(0, offset);
param = aURL.substr(offset + 1);
}
let engine = Services.search.getEngineByAlias(keyword);
if (engine) {
let submission = engine.getSubmission(param);
postData = submission.postData;
throw new Task.Result({ postData: submission.postData,
url: submission.uri.spec,
mayInheritPrincipal: mayInheritPrincipal });
}
let engine = Services.search.getEngineByAlias(keyword);
if (engine) {
let submission = engine.getSubmission(param);
postData = submission.postData;
aCallback({ postData: submission.postData, url: submission.uri.spec,
mayInheritPrincipal: mayInheritPrincipal });
return;
}
[shortcutURL, postData] =
PlacesUtils.getURLAndPostDataForKeyword(keyword);
[shortcutURL, postData] =
PlacesUtils.getURLAndPostDataForKeyword(keyword);
if (!shortcutURL)
throw new Task.Result({ postData: postData, url: aURL,
mayInheritPrincipal: mayInheritPrincipal });
if (!shortcutURL) {
aCallback({ postData: postData, url: aURL,
mayInheritPrincipal: mayInheritPrincipal });
return;
}
let escapedPostData = "";
if (postData)
escapedPostData = unescape(postData);
let escapedPostData = "";
if (postData)
escapedPostData = unescape(postData);
if (/%s/i.test(shortcutURL) || /%s/i.test(escapedPostData)) {
let charset = "";
const re = /^(.*)\&mozcharset=([a-zA-Z][_\-a-zA-Z0-9]+)\s*$/;
let matches = shortcutURL.match(re);
if (matches)
[, shortcutURL, charset] = matches;
else {
// Try to get the saved character-set.
try {
// makeURI throws if URI is invalid.
// Will return an empty string if character-set is not found.
charset = yield PlacesUtils.getCharsetForURI(makeURI(shortcutURL));
} catch (e) {}
}
if (/%s/i.test(shortcutURL) || /%s/i.test(escapedPostData)) {
let charset = "";
const re = /^(.*)\&mozcharset=([a-zA-Z][_\-a-zA-Z0-9]+)\s*$/;
let matches = shortcutURL.match(re);
let continueOperation = function () {
// encodeURIComponent produces UTF-8, and cannot be used for other charsets.
// escape() works in those cases, but it doesn't uri-encode +, @, and /.
// Therefore we need to manually replace these ASCII characters by their
@ -1948,23 +1938,45 @@ function getShortcutOrURIAndPostData(aURL) {
if (/%s/i.test(escapedPostData)) // POST keyword
postData = getPostDataStream(escapedPostData, param, encodedParam,
"application/x-www-form-urlencoded");
}
else if (param) {
// This keyword doesn't take a parameter, but one was provided. Just return
// the original URL.
postData = null;
throw new Task.Result({ postData: postData, url: aURL,
mayInheritPrincipal: mayInheritPrincipal });
// This URL came from a bookmark, so it's safe to let it inherit the current
// document's principal.
mayInheritPrincipal = true;
aCallback({ postData: postData, url: shortcutURL,
mayInheritPrincipal: mayInheritPrincipal });
}
if (matches) {
[, shortcutURL, charset] = matches;
continueOperation();
} else {
// Try to get the saved character-set.
// makeURI throws if URI is invalid.
// Will return an empty string if character-set is not found.
try {
PlacesUtils.getCharsetForURI(makeURI(shortcutURL))
.then(c => { charset = c; continueOperation(); });
} catch (ex) {
continueOperation();
}
}
}
else if (param) {
// This keyword doesn't take a parameter, but one was provided. Just return
// the original URL.
postData = null;
aCallback({ postData: postData, url: aURL,
mayInheritPrincipal: mayInheritPrincipal });
} else {
// This URL came from a bookmark, so it's safe to let it inherit the current
// document's principal.
mayInheritPrincipal = true;
throw new Task.Result({ postData: postData, url: shortcutURL,
mayInheritPrincipal: mayInheritPrincipal });
});
aCallback({ postData: postData, url: shortcutURL,
mayInheritPrincipal: mayInheritPrincipal });
}
}
function getPostDataStream(aStringData, aKeyword, aEncKeyword, aType) {
@ -2765,8 +2777,7 @@ var newTabButtonObserver = {
onDrop: function (aEvent)
{
let url = browserDragAndDrop.drop(aEvent, { });
Task.spawn(function() {
let data = yield getShortcutOrURIAndPostData(url);
getShortcutOrURIAndPostData(url, data => {
if (data.url) {
// allow third-party services to fixup this URL
openNewTabWith(data.url, null, data.postData, aEvent, true);
@ -2786,8 +2797,7 @@ var newWindowButtonObserver = {
onDrop: function (aEvent)
{
let url = browserDragAndDrop.drop(aEvent, { });
Task.spawn(function() {
let data = yield getShortcutOrURIAndPostData(url);
getShortcutOrURIAndPostData(url, data => {
if (data.url) {
// allow third-party services to fixup this URL
openNewWindowWith(data.url, null, data.postData, true);
@ -5129,8 +5139,7 @@ function middleMousePaste(event) {
lastLocationChange = gBrowser.selectedBrowser.lastLocationChange;
}
Task.spawn(function() {
let data = yield getShortcutOrURIAndPostData(clipboard);
getShortcutOrURIAndPostData(clipboard, data => {
try {
makeURI(data.url);
} catch (ex) {
@ -5161,8 +5170,7 @@ function handleDroppedLink(event, url, name)
{
let lastLocationChange = gBrowser.selectedBrowser.lastLocationChange;
Task.spawn(function() {
let data = yield getShortcutOrURIAndPostData(url);
getShortcutOrURIAndPostData(url, data => {
if (data.url &&
lastLocationChange == gBrowser.selectedBrowser.lastLocationChange)
loadURI(data.url, null, data.postData, false);

View File

@ -162,7 +162,9 @@ let gGrid = {
'<input type="button" title="' + newTabString("pin") + '"' +
' class="newtab-control newtab-control-pin"/>' +
'<input type="button" title="' + newTabString("block") + '"' +
' class="newtab-control newtab-control-block"/>';
' class="newtab-control newtab-control-block"/>' +
'<input type="button" title="' + newTabString("sponsored") + '"' +
' class="newtab-control newtab-control-sponsored"/>';
this._siteFragment = document.createDocumentFragment();
this._siteFragment.appendChild(site);

View File

@ -129,16 +129,16 @@ input[type=button] {
.newtab-thumbnail[dragged],
.newtab-link:-moz-focusring > .newtab-thumbnail,
.newtab-site:hover > .newtab-link > .newtab-thumbnail {
.newtab-cell:not([ignorehover]) > .newtab-site:hover > .newtab-link > .newtab-thumbnail {
opacity: 1;
}
/* TITLES */
.newtab-title {
bottom: -20px;
bottom: -21px;
position: absolute;
left: 0;
line-height: 20px;
line-height: 21px;
right: 0;
text-align: start;
white-space: nowrap;
@ -155,7 +155,7 @@ input[type=button] {
}
.newtab-control:-moz-focusring,
.newtab-site:hover > .newtab-control {
.newtab-cell:not([ignorehover]) > .newtab-site:hover > .newtab-control {
opacity: 1;
}
@ -169,16 +169,31 @@ input[type=button] {
}
}
.newtab-control-sponsored:-moz-locale-dir(rtl),
.newtab-control-pin:-moz-locale-dir(ltr),
.newtab-control-block:-moz-locale-dir(rtl) {
left: 4px;
}
.newtab-control-sponsored:-moz-locale-dir(ltr),
.newtab-control-block:-moz-locale-dir(ltr),
.newtab-control-pin:-moz-locale-dir(rtl) {
right: 4px;
}
.newtab-control.newtab-control-sponsored {
bottom: -20px;
height: 14px;
-moz-margin-end: -5px;
opacity: 1;
top: auto;
width: 14px;
}
.newtab-site:not([type=sponsored]) .newtab-control-sponsored {
display: none;
}
/* DRAG & DROP */
/*

View File

@ -11,6 +11,7 @@ Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/PageThumbs.jsm");
Cu.import("resource://gre/modules/BackgroundPageThumbs.jsm");
Cu.import("resource://gre/modules/DirectoryLinksProvider.jsm");
Cu.import("resource://gre/modules/NewTabUtils.jsm");
Cu.import("resource://gre/modules/commonjs/sdk/core/promise.js");

View File

@ -36,7 +36,11 @@ let gPage = {
* thumbnail service.
*/
get allowBackgroundCaptures() {
return document.documentElement.getAttribute("allow-background-captures") ==
// The preloader is bypassed altogether for private browsing windows, and
// therefore allow-background-captures will not be set. In that case, the
// page is not preloaded and so it's visible, so allow background captures.
return inPrivateBrowsingMode() ||
document.documentElement.getAttribute("allow-background-captures") ==
"true";
},
@ -65,10 +69,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();
}
},
@ -87,11 +94,29 @@ let gPage = {
if (this.allowBackgroundCaptures) {
Services.telemetry.getHistogramById("NEWTAB_PAGE_SHOWN").add(true);
// Initialize type counting with the types we want to count
let directoryCount = {};
for (let type of DirectoryLinksProvider.linkTypes) {
directoryCount[type] = 0;
}
for (let site of gGrid.sites) {
if (site) {
site.captureIfMissing();
let {type} = site.link;
if (type in directoryCount) {
directoryCount[type]++;
}
}
}
// Record how many directory sites were shown, but place counts over the
// default 9 in the same bucket
for (let [type, count] of Iterator(directoryCount)) {
let shownId = "NEWTAB_PAGE_DIRECTORY_" + type.toUpperCase() + "_SHOWN";
let shownCount = Math.min(10, count);
Services.telemetry.getHistogramById(shownId).add(shownCount);
}
}
});
this._mutationObserver.observe(document.documentElement, {

View File

@ -128,6 +128,7 @@ Site.prototype = {
link.setAttribute("title", tooltip);
link.setAttribute("href", url);
this._querySelector(".newtab-title").textContent = title;
this.node.setAttribute("type", this.link.type);
if (this.isPinned())
this._updateAttributes(true);
@ -143,17 +144,19 @@ Site.prototype = {
* existing thumbnail and the page allows background captures.
*/
captureIfMissing: function Site_captureIfMissing() {
if (gPage.allowBackgroundCaptures)
if (gPage.allowBackgroundCaptures && !this.link.imageURISpec) {
BackgroundPageThumbs.captureIfMissing(this.url);
}
},
/**
* Refreshes the thumbnail for the site.
*/
refreshThumbnail: function Site_refreshThumbnail() {
let thumbnailURL = PageThumbs.getThumbnailURL(this.url);
let thumbnail = this._querySelector(".newtab-thumbnail");
thumbnail.style.backgroundImage = "url(" + thumbnailURL + ")";
thumbnail.style.backgroundColor = this.link.bgColor;
let uri = this.link.imageURISpec || PageThumbs.getThumbnailURL(this.url);
thumbnail.style.backgroundImage = "url(" + uri + ")";
},
/**
@ -165,6 +168,15 @@ Site.prototype = {
this._node.addEventListener("dragend", this, false);
this._node.addEventListener("mouseover", this, false);
this._node.addEventListener("click", this, false);
// Specially treat the sponsored icon to prevent regular hover effects
let sponsored = this._querySelector(".newtab-control-sponsored");
sponsored.addEventListener("mouseover", () => {
this.cell.node.setAttribute("ignorehover", "true");
});
sponsored.addEventListener("mouseout", () => {
this.cell.node.removeAttribute("ignorehover");
});
},
/**
@ -189,6 +201,13 @@ Site.prototype = {
}
Services.telemetry.getHistogramById("NEWTAB_PAGE_SITE_CLICKED")
.add(aIndex);
// Specially count clicks on directory tiles
let typeIndex = DirectoryLinksProvider.linkTypes.indexOf(this.link.type);
if (typeIndex != -1) {
Services.telemetry.getHistogramById("NEWTAB_PAGE_DIRECTORY_TYPE_CLICKED")
.add(typeIndex);
}
},
/**

View File

@ -103,7 +103,8 @@ function test() {
let query = data.keyword;
if (data.searchWord)
query += " " + data.searchWord;
let returnedData = yield getShortcutOrURIAndPostData(query);
let returnedData = yield new Promise(
resolve => getShortcutOrURIAndPostData(query, resolve));
// null result.url means we should expect the same query we sent in
let expected = result.url || query;
is(returnedData.url, expected, "got correct URL for " + data.keyword);

View File

@ -56,6 +56,7 @@ const EXPECTED_REFLOWS = [
];
const PREF_PRELOAD = "browser.newtab.preload";
const PREF_NEWTAB_DIRECTORYSOURCE = "browser.newtabpage.directorySource";
/*
* This test ensures that there are no unexpected
@ -65,7 +66,11 @@ function test() {
waitForExplicitFinish();
Services.prefs.setBoolPref(PREF_PRELOAD, false);
registerCleanupFunction(() => Services.prefs.clearUserPref(PREF_PRELOAD));
Services.prefs.setCharPref(PREF_NEWTAB_DIRECTORYSOURCE, "data:application/json,{}");
registerCleanupFunction(() => {
Services.prefs.clearUserPref(PREF_PRELOAD);
Services.prefs.clearUserPref(PREF_NEWTAB_DIRECTORYSOURCE);
});
// Add a reflow observer and open a new tab.
docShell.addWeakReflowObserver(observer);

View File

@ -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]

View File

@ -0,0 +1,53 @@
/* 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,,,,,,,");
// Wait for fillHistory to add all links before waiting for an update
yield fillHistory([link(2), link(3), link(4)], TestRunner.next);
yield whenPagesUpdated(null, true);
yield addNewTabPageTab();
checkGrid("2,1,3,4,,,,,");
}
function link(id) {
return { url: "http://example.com/#" + id, title: "site#" + id };
}

View File

@ -2,8 +2,11 @@
http://creativecommons.org/publicdomain/zero/1.0/ */
const PREF_NEWTAB_ENABLED = "browser.newtabpage.enabled";
const PREF_NEWTAB_DIRECTORYSOURCE = "browser.newtabpage.directorySource";
Services.prefs.setBoolPref(PREF_NEWTAB_ENABLED, true);
// start with no directory links by default
Services.prefs.setCharPref(PREF_NEWTAB_DIRECTORYSOURCE, "data:application/json,{}");
let tmp = {};
Cu.import("resource://gre/modules/Promise.jsm", tmp);
@ -26,6 +29,7 @@ registerCleanupFunction(function () {
gWindow.gBrowser.removeTab(gWindow.gBrowser.tabs[1]);
Services.prefs.clearUserPref(PREF_NEWTAB_ENABLED);
Services.prefs.clearUserPref(PREF_NEWTAB_DIRECTORYSOURCE);
});
/**
@ -159,20 +163,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 +521,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);
}
}
};

View File

@ -260,29 +260,35 @@
var action = this._parseActionUrl(url);
let lastLocationChange = gBrowser.selectedBrowser.lastLocationChange;
Task.spawn(function() {
let matchLastLocationChange = true;
if (action) {
url = action.param;
if (this.hasAttribute("actiontype")) {
if (action.type == "switchtab") {
this.handleRevert();
let prevTab = gBrowser.selectedTab;
if (switchToTabHavingURI(url) &&
isTabEmpty(prevTab))
gBrowser.removeTab(prevTab);
}
return;
}
}
else {
[url, postData, mayInheritPrincipal] = yield this._canonizeURL(aTriggeringEvent);
matchLastLocationChange = (lastLocationChange ==
gBrowser.selectedBrowser.lastLocationChange);
if (!url)
return;
}
let matchLastLocationChange = true;
if (action) {
url = action.param;
if (this.hasAttribute("actiontype")) {
if (action.type == "switchtab") {
this.handleRevert();
let prevTab = gBrowser.selectedTab;
if (switchToTabHavingURI(url) &&
isTabEmpty(prevTab))
gBrowser.removeTab(prevTab);
}
return;
}
continueOperation.call(this);
}
else {
this._canonizeURL(aTriggeringEvent, response => {
[url, postData, mayInheritPrincipal] = response;
if (url) {
matchLastLocationChange = (lastLocationChange ==
gBrowser.selectedBrowser.lastLocationChange);
continueOperation.call(this);
}
});
}
function continueOperation()
{
this.value = url;
gBrowser.userTypedValue = url;
try {
@ -347,73 +353,74 @@
loadCurrent();
}
}
}.bind(this));
}
]]></body>
</method>
<method name="_canonizeURL">
<parameter name="aTriggeringEvent"/>
<parameter name="aCallback"/>
<body><![CDATA[
return Task.spawn(function() {
var url = this.value;
if (!url)
throw new Task.Result(["", null, false]);
var url = this.value;
if (!url) {
aCallback(["", null, false]);
return;
}
// Only add the suffix when the URL bar value isn't already "URL-like",
// and only if we get a keyboard event, to match user expectations.
if (/^\s*[^.:\/\s]+(?:\/.*|\s*)$/i.test(url) &&
(aTriggeringEvent instanceof KeyEvent)) {
// Only add the suffix when the URL bar value isn't already "URL-like",
// and only if we get a keyboard event, to match user expectations.
if (/^\s*[^.:\/\s]+(?:\/.*|\s*)$/i.test(url) &&
(aTriggeringEvent instanceof KeyEvent)) {
#ifdef XP_MACOSX
let accel = aTriggeringEvent.metaKey;
let accel = aTriggeringEvent.metaKey;
#else
let accel = aTriggeringEvent.ctrlKey;
let accel = aTriggeringEvent.ctrlKey;
#endif
let shift = aTriggeringEvent.shiftKey;
let shift = aTriggeringEvent.shiftKey;
let suffix = "";
let suffix = "";
switch (true) {
case (accel && shift):
suffix = ".org/";
break;
case (shift):
suffix = ".net/";
break;
case (accel):
try {
suffix = gPrefService.getCharPref("browser.fixup.alternate.suffix");
if (suffix.charAt(suffix.length - 1) != "/")
suffix += "/";
} catch(e) {
suffix = ".com/";
}
break;
}
if (suffix) {
// trim leading/trailing spaces (bug 233205)
url = url.trim();
// Tack www. and suffix on. If user has appended directories, insert
// suffix before them (bug 279035). Be careful not to get two slashes.
let firstSlash = url.indexOf("/");
if (firstSlash >= 0) {
url = url.substring(0, firstSlash) + suffix +
url.substring(firstSlash + 1);
} else {
url = url + suffix;
switch (true) {
case (accel && shift):
suffix = ".org/";
break;
case (shift):
suffix = ".net/";
break;
case (accel):
try {
suffix = gPrefService.getCharPref("browser.fixup.alternate.suffix");
if (suffix.charAt(suffix.length - 1) != "/")
suffix += "/";
} catch(e) {
suffix = ".com/";
}
url = "http://www." + url;
}
break;
}
let data = yield getShortcutOrURIAndPostData(url);
if (suffix) {
// trim leading/trailing spaces (bug 233205)
url = url.trim();
throw new Task.Result([data.url, data.postData, data.mayInheritPrincipal]);
}.bind(this));
// Tack www. and suffix on. If user has appended directories, insert
// suffix before them (bug 279035). Be careful not to get two slashes.
let firstSlash = url.indexOf("/");
if (firstSlash >= 0) {
url = url.substring(0, firstSlash) + suffix +
url.substring(firstSlash + 1);
} else {
url = url + suffix;
}
url = "http://www." + url;
}
}
getShortcutOrURIAndPostData(url, data => {
aCallback([data.url, data.postData, data.mayInheritPrincipal]);
});
]]></body>
</method>

View File

@ -45,6 +45,11 @@
Cu.import("resource:///modules/CustomizableUI.jsm", scope);
let CustomizableUI = scope.CustomizableUI;
// Bug 989289: Forcibly set the now unsupported "mode" attribute, just
// in case it gets accidentally restored from persistence from a user
// that's been upgrading and downgrading.
this.setAttribute("mode", "icons");
// Searching for the toolbox palette in the toolbar binding because
// toolbars are constructed first.
let toolbox = this.toolbox;

View File

@ -94,4 +94,5 @@ skip-if = os == "linux"
[browser_987177_destroyWidget_xul.js]
[browser_987177_xul_wrapper_updating.js]
[browser_987492_window_api.js]
[browser_989289_force_icons_mode_attribute.js]
[browser_panel_toggle.js]

View File

@ -0,0 +1,31 @@
/* 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";
const kToolbarID = "test-toolbar";
/**
* Tests that customizable toolbars are forced to have their mode
* attribute set to "icons".
*/
add_task(function* testAddingToolbar() {
let toolbar = document.createElement("toolbar");
toolbar.setAttribute("mode", "full");
toolbar.setAttribute("customizable", "true");
toolbar.setAttribute("id", kToolbarID);
CustomizableUI.registerArea(kToolbarID, {
type: CustomizableUI.TYPE_TOOLBAR,
legacy: false,
})
gNavToolbox.appendChild(toolbar);
is(toolbar.getAttribute("mode"), "icons",
"Toolbar should have its mode attribute set to icons.")
toolbar.remove();
CustomizableUI.unregisterArea(kToolbarID);
});

View File

@ -22,6 +22,9 @@ XPCOMUtils.defineLazyModuleGetter(this, "AddonManager",
XPCOMUtils.defineLazyModuleGetter(this, "ContentClick",
"resource:///modules/ContentClick.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "DirectoryLinksProvider",
"resource://gre/modules/DirectoryLinksProvider.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "NetUtil",
"resource://gre/modules/NetUtil.jsm");
@ -475,6 +478,8 @@ BrowserGlue.prototype = {
WebappManager.init();
PageThumbs.init();
NewTabUtils.init();
DirectoryLinksProvider.init();
NewTabUtils.links.addProvider(DirectoryLinksProvider);
BrowserNewTabPreloader.init();
#ifdef NIGHTLY_BUILD
if (Services.prefs.getBoolPref("dom.identity.enabled")) {

View File

@ -3,10 +3,26 @@
* You can obtain one at http://mozilla.org/MPL/2.0/. */
Components.utils.import("resource://services-sync/main.js");
Components.utils.import("resource://gre/modules/Services.jsm");
XPCOMUtils.defineLazyGetter(this, "FxAccountsCommon", function () {
return Components.utils.import("resource://gre/modules/FxAccountsCommon.js", {});
});
const PAGE_NO_ACCOUNT = 0;
const PAGE_HAS_ACCOUNT = 1;
const PAGE_NEEDS_UPDATE = 2;
const PAGE_PLEASE_WAIT = 3;
const FXA_PAGE_LOGGED_OUT = 4;
const FXA_PAGE_LOGGED_IN = 5;
// Indexes into the "login status" deck.
// We are in a successful verified state - everything should work!
const FXA_LOGIN_VERIFIED = 0;
// We have logged in to an unverified account.
const FXA_LOGIN_UNVERIFIED = 1;
// We are logged in locally, but the server rejected our credentials.
const FXA_LOGIN_FAILED = 2;
let gSyncPane = {
_stringBundle: null,
@ -43,6 +59,10 @@ let gSyncPane = {
return;
}
// it may take some time before we can determine what provider to use
// and the state of that provider, so show the "please wait" page.
this.page = PAGE_PLEASE_WAIT;
let onUnload = function () {
window.removeEventListener("unload", onUnload, false);
try {
@ -56,7 +76,6 @@ let gSyncPane = {
this._init();
}.bind(this);
Services.obs.addObserver(onReady, "weave:service:ready", false);
window.addEventListener("unload", onUnload, false);
@ -66,9 +85,10 @@ let gSyncPane = {
_init: function () {
let topics = ["weave:service:login:error",
"weave:service:login:finish",
"weave:service:start-over",
"weave:service:start-over:finish",
"weave:service:setup-complete",
"weave:service:logout:finish"];
"weave:service:logout:finish",
FxAccountsCommon.ONVERIFIED_NOTIFICATION];
// Add the observers now and remove them on unload
//XXXzpao This should use Services.obs.* but Weave's Obs does nice handling
@ -88,16 +108,73 @@ let gSyncPane = {
},
updateWeavePrefs: function () {
if (Weave.Status.service == Weave.CLIENT_NOT_CONFIGURED ||
Weave.Svc.Prefs.get("firstSync", "") == "notReady") {
let service = Components.classes["@mozilla.org/weave/service;1"]
.getService(Components.interfaces.nsISupports)
.wrappedJSObject;
// service.fxAccountsEnabled is false iff sync is already configured for
// the legacy provider.
if (service.fxAccountsEnabled) {
// determine the fxa status...
this.page = PAGE_PLEASE_WAIT;
Components.utils.import("resource://gre/modules/FxAccounts.jsm");
fxAccounts.getSignedInUser().then(data => {
if (!data) {
this.page = FXA_PAGE_LOGGED_OUT;
return;
}
this.page = FXA_PAGE_LOGGED_IN;
// We are logged in locally, but maybe we are in a state where the
// server rejected our credentials (eg, password changed on the server)
let fxaLoginStatus = document.getElementById("fxaLoginStatus");
let enginesListDisabled;
// Not Verfied implies login error state, so check that first.
if (!data.verified) {
fxaLoginStatus.selectedIndex = FXA_LOGIN_UNVERIFIED;
enginesListDisabled = true;
// So we think we are logged in, so login problems are next.
// (Although if the Sync identity manager is still initializing, we
// ignore login errors and assume all will eventually be good.)
// LOGIN_FAILED_LOGIN_REJECTED explicitly means "you must log back in".
// All other login failures are assumed to be transient and should go
// away by themselves, so aren't reflected here.
} else if (Weave.Status.login == Weave.LOGIN_FAILED_LOGIN_REJECTED) {
fxaLoginStatus.selectedIndex = FXA_LOGIN_FAILED;
enginesListDisabled = true;
// Else we must be golden (or in an error state we expect to magically
// resolve itself)
} else {
fxaLoginStatus.selectedIndex = FXA_LOGIN_VERIFIED;
enginesListDisabled = false;
}
document.getElementById("fxaEmailAddress1").textContent = data.email;
document.getElementById("fxaEmailAddress2").textContent = data.email;
document.getElementById("fxaEmailAddress3").textContent = data.email;
document.getElementById("fxaSyncComputerName").value = Weave.Service.clientsEngine.localName;
let engines = document.getElementById("fxaSyncEngines")
for (let checkbox of engines.querySelectorAll("checkbox")) {
checkbox.disabled = enginesListDisabled;
}
let checkbox = document.getElementById("fxa-pweng-chk");
let help = document.getElementById("fxa-pweng-help");
let allowPasswordsEngine = service.allowPasswordsEngine;
if (!allowPasswordsEngine) {
checkbox.checked = false;
}
checkbox.disabled = !allowPasswordsEngine;
help.hidden = allowPasswordsEngine;
});
// If fxAccountEnabled is false and we are in a "not configured" state,
// then fxAccounts is probably fully disabled rather than just unconfigured,
// so handle this case. This block can be removed once we remove support
// for fxAccounts being disabled.
} else if (Weave.Status.service == Weave.CLIENT_NOT_CONFIGURED ||
Weave.Svc.Prefs.get("firstSync", "") == "notReady") {
this.page = PAGE_NO_ACCOUNT;
let service = Components.classes["@mozilla.org/weave/service;1"]
.getService(Components.interfaces.nsISupports)
.wrappedJSObject;
// no concept of "pair" in an fxAccounts world.
if (service.fxAccountsEnabled) {
document.getElementById("pairDevice").hidden = true;
}
// else: sync was previously configured for the legacy provider, so we
// make the "old" panels available.
} else if (Weave.Status.login == Weave.LOGIN_FAILED_INVALID_PASSPHRASE ||
Weave.Status.login == Weave.LOGIN_FAILED_LOGIN_REJECTED) {
this.needsUpdate();
@ -147,7 +224,7 @@ let gSyncPane = {
/**
* Invoke the Sync setup wizard.
*
*
* @param wizardType
* Indicates type of wizard to launch:
* null -- regular set up wizard
@ -160,8 +237,7 @@ let gSyncPane = {
.wrappedJSObject;
if (service.fxAccountsEnabled) {
let win = Services.wm.getMostRecentWindow("navigator:browser");
win.switchToTabHavingURI("about:accounts", true);
this.openContentInBrowser("about:accounts");
} else {
let win = Services.wm.getMostRecentWindow("Weave:AccountSetup");
if (win)
@ -174,6 +250,85 @@ let gSyncPane = {
}
},
openContentInBrowser: function(url) {
let win = Services.wm.getMostRecentWindow("navigator:browser");
if (!win) {
// no window to use, so use _openLink to create a new one. We don't
// always use that as it prefers to open a new window rather than use
// an existing one.
gSyncUtils._openLink(url);
return;
}
win.switchToTabHavingURI(url, true);
// seeing as we are doing this in a tab we close the prefs dialog.
window.close();
},
signUp: function() {
this.openContentInBrowser("about:accounts?action=signup");
},
signIn: function() {
this.openContentInBrowser("about:accounts?action=signin");
},
reSignIn: function() {
this.openContentInBrowser("about:accounts?action=reauth");
},
manageFirefoxAccount: function() {
let url = Services.prefs.getCharPref("identity.fxaccounts.settings.uri");
this.openContentInBrowser(url);
},
verifyFirefoxAccount: function() {
Components.utils.import("resource://gre/modules/FxAccounts.jsm");
fxAccounts.resendVerificationEmail().then(() => {
fxAccounts.getSignedInUser().then(data => {
let sb = this._stringBundle;
let title = sb.GetStringFromName("firefoxAccountsVerificationSentTitle");
let heading = sb.formatStringFromName("firefoxAccountsVerificationSentHeading",
[data.email], 1);
let description = sb.GetStringFromName("firefoxAccountVerificationSentDescription");
Services.prompt.alert(window, title, heading + "\n\n" + description);
});
});
},
openOldSyncSupportPage: function() {
let url = Services.urlFormatter.formatURLPref('app.support.baseURL') + "old-sync"
this.openContentInBrowser(url);
},
unlinkFirefoxAccount: function(confirm) {
if (confirm) {
// We use a string bundle shared with aboutAccounts.
let sb = Services.strings.createBundle("chrome://browser/locale/syncSetup.properties");
let continueLabel = sb.GetStringFromName("continue.label");
let title = sb.GetStringFromName("disconnect.verify.title");
let brandBundle = Services.strings.createBundle("chrome://branding/locale/brand.properties");
let brandShortName = brandBundle.GetStringFromName("brandShortName");
let body = sb.GetStringFromName("disconnect.verify.heading") +
"\n\n" +
sb.formatStringFromName("disconnect.verify.description",
[brandShortName], 1);
let ps = Services.prompt;
let buttonFlags = (ps.BUTTON_POS_0 * ps.BUTTON_TITLE_IS_STRING) +
(ps.BUTTON_POS_1 * ps.BUTTON_TITLE_CANCEL) +
ps.BUTTON_POS_1_DEFAULT;
let pressed = Services.prompt.confirmEx(window, title, body, buttonFlags,
continueLabel, null, null, null, {});
if (pressed != 0) { // 0 is the "continue" button
return;
}
}
Components.utils.import('resource://gre/modules/FxAccounts.jsm');
fxAccounts.signOut().then(() => {
this.updateWeavePrefs();
});
},
openQuotaDialog: function () {
let win = Services.wm.getMostRecentWindow("Sync:ViewQuota");
if (win)

View File

@ -38,6 +38,7 @@
</hbox>
<deck id="weavePrefsDeck" data-category="paneSync" hidden="true">
<!-- These panels are for the "legacy" sync provider -->
<vbox id="noAccount" align="center">
<spacer flex="1"/>
<description id="syncDesc">
@ -176,4 +177,141 @@
value="&unlinkDevice.label;"/>
</vbox>
<!-- These panels are for the Firefox Accounts identity provider -->
<vbox id="fxaDeterminingStatus" align="center">
<spacer flex="1"/>
<label value="&determiningAcctStatus.label;"/>
<spacer flex="1"/>
</vbox>
<vbox id="noFxaAccount">
<label>&welcome.description;</label>
<label class="text-link"
onclick="gSyncPane.signUp(); return false;"
value="&welcome.createAccount.label;"/>
<label class="text-link"
onclick="gSyncPane.signIn(); return false;"
value="&welcome.signIn.label;"/>
<separator/>
<label class="text-link"
onclick="gSyncPane.openOldSyncSupportPage(); return false;"
value="&welcome.useOldSync.label;"/>
</vbox>
<vbox id="hasFxaAccount">
<groupbox id="fxaGroup">
<caption label="&syncBrand.fxAccount.label;"/>
<deck id="fxaLoginStatus">
<!-- logged in and verified and all is good -->
<hbox>
<label id="fxaEmailAddress1"/>
<vbox>
<label class="text-link"
onclick="gSyncPane.manageFirefoxAccount();"
value="&manage.label;"/>
</vbox>
<spacer flex="1"/>
<vbox>
<button id="fxaUnlinkButton"
oncommand="gSyncPane.unlinkFirefoxAccount(true);"
label="&disconnect.label;"/>
</vbox>
</hbox>
<!-- logged in to an unverified account -->
<hbox flex="1">
<description>
&signedInUnverified.beforename.label;
<label id="fxaEmailAddress2"/>
&signedInUnverified.aftername.label;
</description>
<spacer flex="1"/>
<vbox align="end">
<button oncommand="gSyncPane.verifyFirefoxAccount();"
label="&verify.label;"/>
<label class="text-link"
onclick="/* no warning as account can't have previously synced */ gSyncPane.unlinkFirefoxAccount(false);"
value="&forget.label;"/>
</vbox>
</hbox>
<!-- logged in locally but server rejected credentials -->
<hbox flex="1">
<description>
&signedInLoginFailure.beforename.label;
<label id="fxaEmailAddress3"/>
&signedInLoginFailure.aftername.label;
</description>
<spacer flex="1"/>
<vbox align="end">
<button oncommand="gSyncPane.reSignIn();"
label="&signIn.label;"/>
<label class="text-link"
onclick="gSyncPane.unlinkFirefoxAccount(true);"
value="&forget.label;"/>
</vbox>
</hbox>
</deck>
</groupbox>
<groupbox id="syncOptions">
<caption label="&syncBrand.shortName.label;"/>
<hbox id="fxaSyncEngines">
<vbox>
<checkbox label="&engine.tabs.label;"
accesskey="&engine.tabs.accesskey;"
preference="engine.tabs"/>
<checkbox label="&engine.bookmarks.label;"
accesskey="&engine.bookmarks.accesskey;"
preference="engine.bookmarks"/>
<hbox>
<checkbox id="fxa-pweng-chk"
label="&engine.passwords.label;"
accesskey="&engine.passwords.accesskey;"
preference="engine.passwords"/>
<vbox id="fxa-pweng-help">
<spacer flex="1"/>
<hbox id="fxa-pweng-help-link">
<label value=" ["/>
<label class="text-link" value="?"
onclick="gSyncUtils.openMPInfoPage(event);"/>
<label value="]"/>
</hbox>
<spacer flex="1"/>
</vbox>
</hbox>
<checkbox label="&engine.history.label;"
accesskey="&engine.history.accesskey;"
preference="engine.history"/>
<checkbox label="&engine.addons.label;"
accesskey="&engine.addons.accesskey;"
preference="engine.addons"/>
<checkbox label="&engine.prefs.label;"
accesskey="&engine.prefs.accesskey;"
preference="engine.prefs"/>
</vbox>
<spacer/>
</hbox>
</groupbox>
<hbox align="center">
<label value="&syncDeviceName.label;"
accesskey="&syncDeviceName.accesskey;"
control="syncComputerName"/>
<textbox id="fxaSyncComputerName"
flex="1"
onchange="gSyncUtils.changeName(this)"/>
</hbox>
<spacer flex="1"/>
<hbox id="tosPP" pack="center">
<label class="text-link small"
onclick="event.stopPropagation();gSyncUtils.openToS();"
value="&prefs.tosLink.label;"/>
<label class="text-link small"
onclick="event.stopPropagation();gSyncUtils.openPrivacyPolicy();"
value="&fxaPrivacyNotice.link.label;"/>
</hbox>
</vbox>
</deck>

View File

@ -54,6 +54,7 @@
oncommand="document.getBindingParent(this).translate()">
<xul:menupopup/>
</xul:menulist>
<xul:label value="&translation.translatedToSuffix.label;"/>
<xul:button anonid="showOriginal"
label="&translation.showOriginal.button;"
oncommand="document.getBindingParent(this).showOriginal();"/>

View File

@ -142,8 +142,8 @@ SourcesView.prototype = Heritage.extend(WidgetMethods, {
addSource: function(aSource, aOptions = {}) {
let fullUrl = aSource.url;
let url = fullUrl.split(" -> ").pop();
let label = SourceUtils.getSourceLabel(url);
let group = SourceUtils.getSourceGroup(url);
let label = aSource.addonPath ? aSource.addonPath : SourceUtils.getSourceLabel(url);
let group = aSource.addonID ? aSource.addonID : SourceUtils.getSourceGroup(url);
let unicodeUrl = NetworkHelper.convertToUnicode(unescape(fullUrl));
let contents = document.createElement("label");

View File

@ -0,0 +1,23 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
const { interfaces: Ci, classes: Cc } = Components;
function startup(aParams, aReason) {
Components.utils.import("resource://gre/modules/Services.jsm");
let res = Services.io.getProtocolHandler("resource")
.QueryInterface(Ci.nsIResProtocolHandler);
res.setSubstitution("browser_dbg_addon5", aParams.resourceURI);
// Load a JS module
Components.utils.import("resource://browser_dbg_addon5/test.jsm");
}
function shutdown(aParams, aReason) {
// Unload the JS module
Components.utils.unload("resource://browser_dbg_addon5/test.jsm");
let res = Services.io.getProtocolHandler("resource")
.QueryInterface(Ci.nsIResProtocolHandler);
res.setSubstitution("browser_dbg_addon5", null);
}

View File

@ -0,0 +1,20 @@
<?xml version="1.0"?>
<RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:em="http://www.mozilla.org/2004/em-rdf#">
<Description about="urn:mozilla:install-manifest">
<em:id>browser_dbg_addon5@tests.mozilla.org</em:id>
<em:version>1.0</em:version>
<em:name>Test unpacked add-on with JS Modules</em:name>
<em:bootstrap>true</em:bootstrap>
<em:unpack>true</em:unpack>
<em:targetApplication>
<Description>
<em:id>toolkit@mozilla.org</em:id>
<em:minVersion>0</em:minVersion>
<em:maxVersion>*</em:maxVersion>
</Description>
</em:targetApplication>
</Description>
</RDF>

View File

@ -0,0 +1,6 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
const EXPORTED_SYMBOLS = ["Foo"];
const Foo = {};

View File

@ -0,0 +1,6 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
const EXPORTED_SYMBOLS = ["Bar"];
const Bar = {};

Binary file not shown.

View File

@ -4,6 +4,7 @@ support-files =
addon2.xpi
addon3.xpi
addon4.xpi
addon5.xpi
code_binary_search.coffee
code_binary_search.js
code_binary_search.map
@ -85,6 +86,7 @@ support-files =
[browser_dbg_addonactor.js]
[browser_dbg_addon-sources.js]
[browser_dbg_addon-modules.js]
[browser_dbg_addon-modules-unpacked.js]
[browser_dbg_addon-panels.js]
[browser_dbg_auto-pretty-print-01.js]
[browser_dbg_auto-pretty-print-02.js]

View File

@ -0,0 +1,137 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
// Make sure the add-on actor can see loaded JS Modules from an add-on
const ADDON5_URL = EXAMPLE_URL + "addon5.xpi";
let gAddon, gClient, gThreadClient, gDebugger, gSources, gTitle;
function onMessage(event) {
try {
let json = JSON.parse(event.data);
switch (json.name) {
case "toolbox-title":
gTitle = json.data.value;
break;
}
} catch(e) {
DevToolsUtils.reportException("onMessage", e);
}
}
function test() {
Task.spawn(function () {
if (!DebuggerServer.initialized) {
DebuggerServer.init(() => true);
DebuggerServer.addBrowserActors();
}
gBrowser.selectedTab = gBrowser.addTab();
let iframe = document.createElement("iframe");
document.documentElement.appendChild(iframe);
window.addEventListener("message", onMessage);
let transport = DebuggerServer.connectPipe();
gClient = new DebuggerClient(transport);
let connected = promise.defer();
gClient.connect(connected.resolve);
yield connected.promise;
yield installAddon();
let debuggerPanel = yield initAddonDebugger(gClient, ADDON5_URL, iframe);
gDebugger = debuggerPanel.panelWin;
gThreadClient = gDebugger.gThreadClient;
gSources = gDebugger.DebuggerView.Sources;
yield testSources(false);
Cu.import("resource://browser_dbg_addon5/test2.jsm", {});
yield testSources(true);
Cu.unload("resource://browser_dbg_addon5/test2.jsm");
yield uninstallAddon();
yield closeConnection();
yield debuggerPanel._toolbox.destroy();
iframe.remove();
window.removeEventListener("message", onMessage);
finish();
});
}
function installAddon () {
return addAddon(ADDON5_URL).then(aAddon => {
gAddon = aAddon;
});
}
function testSources(expectSecondModule) {
let deferred = promise.defer();
let foundAddonModule = false;
let foundAddonModule2 = false;
let foundAddonBootstrap = false;
gThreadClient.getSources(({sources}) => {
ok(sources.length, "retrieved sources");
for (let source of sources) {
let url = source.url.split(" -> ").pop();
let { label, group } = gSources.getItemByValue(source.url).attachment;
if (url.indexOf("resource://browser_dbg_addon5/test.jsm") === 0) {
is(label, "test.jsm", "correct label for addon code");
is(group, "browser_dbg_addon5@tests.mozilla.org", "addon module is in the add-on's group");
foundAddonModule = true;
} else if (url.indexOf("resource://browser_dbg_addon5/test2.jsm") === 0) {
is(label, "test2.jsm", "correct label for addon code");
is(group, "browser_dbg_addon5@tests.mozilla.org", "addon module is in the add-on's group");
foundAddonModule2 = true;
} else if (url.endsWith("/browser_dbg_addon5@tests.mozilla.org/bootstrap.js")) {
is(label, "bootstrap.js", "correct label for bootstrap code");
is(group, "browser_dbg_addon5@tests.mozilla.org", "addon bootstrap script is in the add-on's group");
foundAddonBootstrap = true;
} else {
ok(false, "Saw an unexpected source: " + url);
}
}
ok(foundAddonModule, "found JS module for the addon in the list");
is(foundAddonModule2, expectSecondModule, "saw the second addon module");
ok(foundAddonBootstrap, "found bootstrap script for the addon in the list");
is(gTitle, "Debugger - Test unpacked add-on with JS Modules", "Saw the right toolbox title.");
let groups = gDebugger.document.querySelectorAll(".side-menu-widget-group-title .name");
is(groups[0].value, "browser_dbg_addon5@tests.mozilla.org", "Add-on code should be the first group");
is(groups.length, 1, "Should be only one group.");
deferred.resolve();
});
return deferred.promise;
}
function uninstallAddon() {
return removeAddon(gAddon);
}
function closeConnection () {
let deferred = promise.defer();
gClient.close(deferred.resolve);
return deferred.promise;
}
registerCleanupFunction(function() {
gClient = null;
gAddon = null;
gThreadClient = null;
gDebugger = null;
gSources = null;
while (gBrowser.tabs.length > 1) {
gBrowser.removeCurrentTab();
}
});

View File

@ -15,7 +15,9 @@ function onMessage(event) {
gTitle = json.data.value;
break;
}
} catch(e) { Cu.reportError(e); }
} catch(e) {
DevToolsUtils.reportException("onMessage", e);
}
}
function test() {
@ -82,15 +84,15 @@ function testSources(expectSecondModule) {
if (url.indexOf("resource://browser_dbg_addon4/test.jsm") === 0) {
is(label, "test.jsm", "correct label for addon code");
is(group, "resource://browser_dbg_addon4", "addon module is in its own group");
is(group, "browser_dbg_addon4@tests.mozilla.org", "addon module is in the add-on's group");
foundAddonModule = true;
} else if (url.indexOf("resource://browser_dbg_addon4/test2.jsm") === 0) {
is(label, "test2.jsm", "correct label for addon code");
is(group, "resource://browser_dbg_addon4", "addon module is in its own group");
is(group, "browser_dbg_addon4@tests.mozilla.org", "addon module is in the add-on's group");
foundAddonModule2 = true;
} else if (url.endsWith("/browser_dbg_addon4@tests.mozilla.org.xpi!/bootstrap.js")) {
is(label, "bootstrap.js", "correct label for bootstrap code");
is(group, "jar:", "addon bootstrap script is in its own group");
is(group, "browser_dbg_addon4@tests.mozilla.org", "addon bootstrap script is in the add-on's group");
foundAddonBootstrap = true;
} else {
ok(false, "Saw an unexpected source: " + url);
@ -104,9 +106,8 @@ function testSources(expectSecondModule) {
is(gTitle, "Debugger - Test add-on with JS Modules", "Saw the right toolbox title.");
let groups = gDebugger.document.querySelectorAll(".side-menu-widget-group-title .name");
is(groups[0].value, "jar:", "Add-on bootstrap should be the first group");
is(groups[1].value, "resource://browser_dbg_addon4", "Add-on code should be the second group");
is(groups.length, 2, "Should be only two groups.");
is(groups[0].value, "browser_dbg_addon4@tests.mozilla.org", "Add-on code should be the first group");
is(groups.length, 1, "Should be only one group.");
deferred.resolve();
});
@ -130,6 +131,7 @@ registerCleanupFunction(function() {
gThreadClient = null;
gDebugger = null;
gSources = null;
while (gBrowser.tabs.length > 1)
while (gBrowser.tabs.length > 1) {
gBrowser.removeCurrentTab();
}
});

View File

@ -16,7 +16,9 @@ function onMessage(event) {
gTitle = json.data.value;
break;
}
} catch(e) { Cu.reportError(e); }
} catch(e) {
DevToolsUtils.reportException("onMessage", e);
}
}
function test() {
@ -84,12 +86,12 @@ function testSources() {
is(group, "Add-on SDK", "correct SDK group");
foundSDKModule++;
} else if (url.indexOf("resource://jid1-ami3akps3baaeg-at-jetpack") === 0) {
is(label, "main.js", "correct label for addon code");
is(group, "resource://jid1-ami3akps3baaeg-at-jetpack", "addon code is in its own group");
is(label, "resources/browser_dbg_addon3/lib/main.js", "correct label for addon code");
is(group, "jid1-ami3akps3baaeg@jetpack", "addon code is in the add-on's group");
foundAddonModule = true;
} else if (url.endsWith("/jid1-ami3akps3baaeg@jetpack.xpi!/bootstrap.js")) {
is(label, "bootstrap.js", "correct label for bootstrap script");
is(group, "jar:", "bootstrap script is in its own group");
is(group, "jid1-ami3akps3baaeg@jetpack", "addon code is in the add-on's group");
foundAddonBootstrap = true;
} else {
ok(false, "Saw an unexpected source: " + url);
@ -105,10 +107,9 @@ function testSources() {
is(gTitle, "Debugger - browser_dbg_addon3", "Saw the right toolbox title.");
let groups = gDebugger.document.querySelectorAll(".side-menu-widget-group-title .name");
is(groups[0].value, "jar:", "Add-on bootstrap should be the first group");
is(groups[1].value, "resource://jid1-ami3akps3baaeg-at-jetpack", "Add-on code should be the second group");
is(groups[2].value, "Add-on SDK", "Add-on SDK should be the third group");
is(groups.length, 3, "Should be only three groups.");
is(groups[0].value, "jid1-ami3akps3baaeg@jetpack", "Add-on code should be the first group");
is(groups[1].value, "Add-on SDK", "Add-on SDK should be the second group");
is(groups.length, 2, "Should be only two groups.");
deferred.resolve();
});
@ -132,6 +133,7 @@ registerCleanupFunction(function() {
gThreadClient = null;
gDebugger = null;
gSources = null;
while (gBrowser.tabs.length > 1)
while (gBrowser.tabs.length > 1) {
gBrowser.removeCurrentTab();
}
});

View File

@ -31,11 +31,17 @@
<label value="&options.selectDevToolsTheme.label;"/>
<radiogroup id="devtools-theme-box"
class="options-groupbox"
data-pref="devtools.theme"
orient="horizontal">
<radio value="light" label="&options.lightTheme.label;"/>
<radio value="dark" label="&options.darkTheme.label;"/>
<radio checked="true" value="light" label="Metal"/>
<radio checked="false" disabled="true" value="light" label="&options.lightTheme.label;"/>
<radio checked="false" disabled="true" value="dark" label="&options.darkTheme.label;"/>
</radiogroup>
<label value="&options.commonPrefs.label;"/>
<vbox id="commonprefs-options" class="options-groupbox">
<checkbox label="&options.enablePersistentLogging.label;"
tooltiptext="&options.enablePersistentLogging.tooltip;"
data-pref="devtools.webconsole.persistlog"/>
</vbox>
<label value="&options.context.inspector;"/>
<vbox id="inspector-options" class="options-groupbox">
<hbox align="center">
@ -56,9 +62,6 @@
</vbox>
<label value="&options.webconsole.label;"/>
<vbox id="webconsole-options" class="options-groupbox">
<checkbox label="&options.enablePersistentLogging.label;"
tooltiptext="&options.enablePersistentLogging.tooltip;"
data-pref="devtools.webconsole.persistlog"/>
<checkbox id="webconsole-timestamp-messages"
label="&options.timestampMessages.label;"
tooltiptext="&options.timestampMessages.tooltip;"

View File

@ -120,6 +120,9 @@ XPCOMUtils.defineLazyModuleGetter(this, "Chart",
XPCOMUtils.defineLazyModuleGetter(this, "Curl",
"resource:///modules/devtools/Curl.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "CurlUtils",
"resource:///modules/devtools/Curl.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Task",
"resource://gre/modules/Task.jsm");
@ -188,7 +191,7 @@ let NetMonitorController = {
* @return object
* A promise that is resolved when the monitor finishes connecting.
*/
connect: function() {
connect: Task.async(function*() {
if (this._connection) {
return this._connection;
}
@ -204,11 +207,9 @@ let NetMonitorController = {
this._startMonitoringTab(client, form, deferred.resolve);
}
return deferred.promise.then((result) => {
window.emit(EVENTS.CONNECTED);
return result;
});
},
yield deferred.promise;
window.emit(EVENTS.CONNECTED);
}),
/**
* Disconnects the debugger client and removes event handlers as necessary.
@ -444,9 +445,10 @@ TargetEventsHandler.prototype = {
switch (aType) {
case "will-navigate": {
// Reset UI.
NetMonitorView.RequestsMenu.reset();
NetMonitorView.Sidebar.toggle(false);
if (!Services.prefs.getBoolPref("devtools.webconsole.persistlog")) {
NetMonitorView.RequestsMenu.reset();
NetMonitorView.Sidebar.toggle(false);
}
// Switch to the default network traffic inspector view.
if (NetMonitorController.getCurrentActivity() == ACTIVITY_TYPE.NONE) {
NetMonitorView.showNetworkInspectorView();

View File

@ -528,6 +528,7 @@ RequestsMenuView.prototype = Heritage.extend(WidgetMethods, {
*/
copyAsCurl: function() {
let selected = this.selectedItem.attachment;
Task.spawn(function*() {
// Create a sanitized object for the Curl command generator.
let data = {
@ -595,8 +596,8 @@ RequestsMenuView.prototype = Heritage.extend(WidgetMethods, {
let selected = this.selectedItem.attachment;
let data = {
method: selected.method,
url: selected.url,
method: selected.method,
httpVersion: selected.httpVersion,
};
if (selected.requestHeaders) {
@ -1074,20 +1075,20 @@ RequestsMenuView.prototype = Heritage.extend(WidgetMethods, {
// a loop, so remember the actual request item we want to modify.
let currentItem = requestItem;
let currentStore = { headers: [], headersSize: 0 };
gNetwork.getString(value.postData.text).then(aPostData => {
for (let section of aPostData.split(/\r\n|\r|\n/)) {
// Try to retrieve header tuples from this section of the
// POST data. The `parseHeadersText` function will return an
// empty array if no headers are found. We're using Array.p.push
// to avoid creating a new array when concatenating.
let headerTuples = parseHeadersText(section);
currentStore.headersSize += headerTuples.length ? section.length : 0;
Array.prototype.push.apply(currentStore.headers, headerTuples);
}
Task.spawn(function*() {
let postData = yield gNetwork.getString(value.postData.text);
let payloadHeaders = CurlUtils.getHeadersFromMultipartText(postData);
currentStore.headers = payloadHeaders;
currentStore.headersSize = payloadHeaders.reduce(
(acc, { name, value }) => acc + name.length + value.length + 2, 0);
// The `getString` promise is async, so we need to refresh the
// information displayed in the network details pane again here.
refreshNetworkDetailsPaneIfNecessary(currentItem);
});
requestItem.attachment.requestPostData = value;
requestItem.attachment.requestHeadersFromUploadStream = currentStore;
break;
@ -1216,8 +1217,10 @@ RequestsMenuView.prototype = Heritage.extend(WidgetMethods, {
* The type of information that is to be updated.
* @param any aValue
* The new value to be shown.
* @return object
* A promise that is resolved once the information is displayed.
*/
updateMenuView: function(aItem, aKey, aValue) {
updateMenuView: Task.async(function*(aItem, aKey, aValue) {
let target = aItem.target || aItem;
switch (aKey) {
@ -1279,13 +1282,13 @@ RequestsMenuView.prototype = Heritage.extend(WidgetMethods, {
let { text, encoding } = aValue.content;
if (mimeType.contains("image/")) {
gNetwork.getString(text).then(aString => {
let node = $(".requests-menu-icon", aItem.target);
node.src = "data:" + mimeType + ";" + encoding + "," + aString;
node.setAttribute("type", "thumbnail");
node.removeAttribute("hidden");
window.emit(EVENTS.RESPONSE_IMAGE_THUMBNAIL_DISPLAYED);
});
let responseBody = yield gNetwork.getString(text);
let node = $(".requests-menu-icon", aItem.target);
node.src = "data:" + mimeType + ";" + encoding + "," + responseBody;
node.setAttribute("type", "thumbnail");
node.removeAttribute("hidden");
window.emit(EVENTS.RESPONSE_IMAGE_THUMBNAIL_DISPLAYED);
}
break;
}
@ -1297,7 +1300,7 @@ RequestsMenuView.prototype = Heritage.extend(WidgetMethods, {
break;
}
}
},
}),
/**
* Creates a waterfall representing timing information in a network request item view.
@ -1596,7 +1599,7 @@ RequestsMenuView.prototype = Heritage.extend(WidgetMethods, {
let resendElement = $("#request-menu-context-resend");
resendElement.hidden = !NetMonitorController.supportsCustomRequest ||
!selectedItem || selectedItem.attachment.isCustom;
!selectedItem || selectedItem.attachment.isCustom;
let copyUrlElement = $("#request-menu-context-copy-url");
copyUrlElement.hidden = !selectedItem;
@ -1777,17 +1780,17 @@ SidebarView.prototype = {
* @return object
* Returns a promise that resolves upon population of the subview.
*/
populate: function(aData) {
populate: Task.async(function*(aData) {
let isCustom = aData.isCustom;
let view = isCustom ?
NetMonitorView.CustomRequest :
NetMonitorView.NetworkDetails;
return view.populate(aData).then(() => {
$("#details-pane").selectedIndex = isCustom ? 0 : 1;
window.emit(EVENTS.SIDEBAR_POPULATED);
});
}
yield view.populate(aData);
$("#details-pane").selectedIndex = isCustom ? 0 : 1;
window.emit(EVENTS.SIDEBAR_POPULATED);
})
}
/**
@ -1825,29 +1828,22 @@ CustomRequestView.prototype = {
* @return object
* Returns a promise that resolves upon population the view.
*/
populate: function(aData) {
populate: Task.async(function*(aData) {
$("#custom-url-value").value = aData.url;
$("#custom-method-value").value = aData.method;
$("#custom-headers-value").value =
writeHeaderText(aData.requestHeaders.headers);
let view = this;
let postDataPromise = null;
this.updateCustomQuery(aData.url);
if (aData.requestHeaders) {
let headers = aData.requestHeaders.headers;
$("#custom-headers-value").value = writeHeaderText(headers);
}
if (aData.requestPostData) {
let body = aData.requestPostData.postData.text;
postDataPromise = gNetwork.getString(body).then(aString => {
$("#custom-postdata-value").value = aString;
});
} else {
postDataPromise = promise.resolve();
let postData = aData.requestPostData.postData.text;
$("#custom-postdata-value").value = yield gNetwork.getString(postData);
}
return postDataPromise
.then(() => view.updateCustomQuery(aData.url))
.then(() => window.emit(EVENTS.CUSTOMREQUESTVIEW_POPULATED));
},
window.emit(EVENTS.CUSTOMREQUESTVIEW_POPULATED);
}),
/**
* Handle user input in the custom request form.
@ -1895,7 +1891,7 @@ CustomRequestView.prototype = {
* Update the query string field based on the url.
*
* @param object aUrl
* url to extract query string from.
* The URL to extract query string from.
*/
updateCustomQuery: function(aUrl) {
let paramsArray = parseQueryString(nsIURL(aUrl).query);
@ -1911,7 +1907,7 @@ CustomRequestView.prototype = {
* Update the url based on the query string field.
*
* @param object aQueryText
* contents of the query string field.
* The contents of the query string field.
*/
updateCustomUrl: function(aQueryText) {
let params = parseQueryText(aQueryText);
@ -2123,20 +2119,14 @@ NetworkDetailsView.prototype = {
* @return object
* A promise that resolves when request headers are set.
*/
_setRequestHeaders: function(aHeadersResponse, aHeadersFromUploadStream) {
let outstanding = [];
_setRequestHeaders: Task.async(function*(aHeadersResponse, aHeadersFromUploadStream) {
if (aHeadersResponse && aHeadersResponse.headers.length) {
outstanding.push(
this._addHeaders(this._requestHeaders, aHeadersResponse));
yield this._addHeaders(this._requestHeaders, aHeadersResponse);
}
if (aHeadersFromUploadStream && aHeadersFromUploadStream.headers.length) {
outstanding.push(
this._addHeaders(this._requestHeadersFromUpload, aHeadersFromUploadStream));
yield this._addHeaders(this._requestHeadersFromUpload, aHeadersFromUploadStream);
}
return promise.all(outstanding);
},
}),
/**
* Sets the network response headers shown in this view.
@ -2146,13 +2136,12 @@ NetworkDetailsView.prototype = {
* @return object
* A promise that resolves when response headers are set.
*/
_setResponseHeaders: function(aResponse) {
_setResponseHeaders: Task.async(function*(aResponse) {
if (aResponse && aResponse.headers.length) {
aResponse.headers.sort((a, b) => a.name > b.name);
return this._addHeaders(this._responseHeaders, aResponse);
yield this._addHeaders(this._responseHeaders, aResponse);
}
return promise.resolve();
},
}),
/**
* Populates the headers container in this view with the specified data.
@ -2164,18 +2153,20 @@ NetworkDetailsView.prototype = {
* @return object
* A promise that resolves when headers are added.
*/
_addHeaders: function(aName, aResponse) {
_addHeaders: Task.async(function*(aName, aResponse) {
let kb = aResponse.headersSize / 1024;
let size = L10N.numberWithDecimals(kb, HEADERS_SIZE_DECIMALS);
let text = L10N.getFormatStr("networkMenu.sizeKB", size);
let headersScope = this._headers.addScope(aName + " (" + text + ")");
headersScope.expanded = true;
return promise.all(aResponse.headers.map(header => {
for (let header of aResponse.headers) {
let headerVar = headersScope.addItem(header.name, {}, true);
return gNetwork.getString(header.value).then(aString => headerVar.setGrip(aString));
}));
},
let headerValue = yield gNetwork.getString(header.value);
headerVar.setGrip(headerValue);
}
}),
/**
* Sets the network request cookies shown in this view.
@ -2185,13 +2176,12 @@ NetworkDetailsView.prototype = {
* @return object
* A promise that is resolved when the request cookies are set.
*/
_setRequestCookies: function(aResponse) {
_setRequestCookies: Task.async(function*(aResponse) {
if (aResponse && aResponse.cookies.length) {
aResponse.cookies.sort((a, b) => a.name > b.name);
return this._addCookies(this._requestCookies, aResponse);
yield this._addCookies(this._requestCookies, aResponse);
}
return promise.resolve();
},
}),
/**
* Sets the network response cookies shown in this view.
@ -2201,12 +2191,11 @@ NetworkDetailsView.prototype = {
* @return object
* A promise that is resolved when the response cookies are set.
*/
_setResponseCookies: function(aResponse) {
_setResponseCookies: Task.async(function*(aResponse) {
if (aResponse && aResponse.cookies.length) {
return this._addCookies(this._responseCookies, aResponse);
yield this._addCookies(this._responseCookies, aResponse);
}
return promise.resolve();
},
}),
/**
* Populates the cookies container in this view with the specified data.
@ -2218,35 +2207,34 @@ NetworkDetailsView.prototype = {
* @return object
* Returns a promise that resolves upon the adding of cookies.
*/
_addCookies: function(aName, aResponse) {
_addCookies: Task.async(function*(aName, aResponse) {
let cookiesScope = this._cookies.addScope(aName);
cookiesScope.expanded = true;
return promise.all(aResponse.cookies.map(cookie => {
for (let cookie of aResponse.cookies) {
let cookieVar = cookiesScope.addItem(cookie.name, {}, true);
return gNetwork.getString(cookie.value).then(aString => {
cookieVar.setGrip(aString);
let cookieValue = yield gNetwork.getString(cookie.value);
cookieVar.setGrip(cookieValue);
// By default the cookie name and value are shown. If this is the only
// information available, then nothing else is to be displayed.
let cookieProps = Object.keys(cookie);
if (cookieProps.length == 2) {
return;
}
// By default the cookie name and value are shown. If this is the only
// information available, then nothing else is to be displayed.
let cookieProps = Object.keys(cookie);
if (cookieProps.length == 2) {
return;
}
// Display any other information other than the cookie name and value
// which may be available.
let rawObject = Object.create(null);
let otherProps = cookieProps.filter(e => e != "name" && e != "value");
for (let prop of otherProps) {
rawObject[prop] = cookie[prop];
}
cookieVar.populate(rawObject);
cookieVar.twisty = true;
cookieVar.expanded = true;
});
}));
},
// Display any other information other than the cookie name and value
// which may be available.
let rawObject = Object.create(null);
let otherProps = cookieProps.filter(e => e != "name" && e != "value");
for (let prop of otherProps) {
rawObject[prop] = cookie[prop];
}
cookieVar.populate(rawObject);
cookieVar.twisty = true;
cookieVar.expanded = true;
}
}),
/**
* Sets the network request get params shown in this view.
@ -2273,59 +2261,58 @@ NetworkDetailsView.prototype = {
* @return object
* A promise that is resolved when the request post params are set.
*/
_setRequestPostParams: function(aHeadersResponse, aHeadersFromUploadStream, aPostDataResponse) {
_setRequestPostParams: Task.async(function*(aHeadersResponse, aHeadersFromUploadStream, aPostDataResponse) {
if (!aHeadersResponse || !aHeadersFromUploadStream || !aPostDataResponse) {
return promise.resolve();
return;
}
let { headers: requestHeaders } = aHeadersResponse;
let { headers: payloadHeaders } = aHeadersFromUploadStream;
let allHeaders = [...payloadHeaders, ...requestHeaders];
let contentTypeHeader = allHeaders.filter(e => e.name.toLowerCase() == "content-type")[0];
let contentTypeHeader = allHeaders.find(e => e.name.toLowerCase() == "content-type");
let contentTypeLongString = contentTypeHeader ? contentTypeHeader.value : "";
let postDataLongString = aPostDataResponse.postData.text;
return promise.all([
gNetwork.getString(postDataLongString),
gNetwork.getString(contentTypeLongString)
])
.then(([aPostData, aContentType]) => {
// Handle query strings (e.g. "?foo=bar&baz=42").
if (aContentType.contains("x-www-form-urlencoded")) {
for (let section of aPostData.split(/\r\n|\r|\n/)) {
// Before displaying it, make sure this section of the POST data
// isn't a line containing upload stream headers.
if (payloadHeaders.every(header => !section.startsWith(header.name))) {
this._addParams(this._paramsFormData, section);
}
let postData = yield gNetwork.getString(postDataLongString);
let contentType = yield gNetwork.getString(contentTypeLongString);
// Handle query strings (e.g. "?foo=bar&baz=42").
if (contentType.contains("x-www-form-urlencoded")) {
for (let section of postData.split(/\r\n|\r|\n/)) {
// Before displaying it, make sure this section of the POST data
// isn't a line containing upload stream headers.
if (payloadHeaders.every(header => !section.startsWith(header.name))) {
this._addParams(this._paramsFormData, section);
}
}
// Handle actual forms ("multipart/form-data" content type).
else {
// This is really awkward, but hey, it works. Let's show an empty
// scope in the params view and place the source editor containing
// the raw post data directly underneath.
$("#request-params-box").removeAttribute("flex");
let paramsScope = this._params.addScope(this._paramsPostPayload);
paramsScope.expanded = true;
paramsScope.locked = true;
}
// Handle actual forms ("multipart/form-data" content type).
else {
// This is really awkward, but hey, it works. Let's show an empty
// scope in the params view and place the source editor containing
// the raw post data directly underneath.
$("#request-params-box").removeAttribute("flex");
let paramsScope = this._params.addScope(this._paramsPostPayload);
paramsScope.expanded = true;
paramsScope.locked = true;
$("#request-post-data-textarea-box").hidden = false;
return NetMonitorView.editor("#request-post-data-textarea").then(aEditor => {
// Most POST bodies are usually JSON, so they can be neatly
// syntax highlighted as JS. Otheriwse, fall back to plain text.
try {
JSON.parse(aPostData);
aEditor.setMode(Editor.modes.js);
} catch (e) {
aEditor.setMode(Editor.modes.text);
} finally {
aEditor.setText(aPostData);
}
});
$("#request-post-data-textarea-box").hidden = false;
let editor = yield NetMonitorView.editor("#request-post-data-textarea");
// Most POST bodies are usually JSON, so they can be neatly
// syntax highlighted as JS. Otheriwse, fall back to plain text.
try {
JSON.parse(postData);
editor.setMode(Editor.modes.js);
} catch (e) {
editor.setMode(Editor.modes.text);
} finally {
editor.setText(postData);
}
}).then(() => window.emit(EVENTS.REQUEST_POST_PARAMS_DISPLAYED));
},
}
window.emit(EVENTS.REQUEST_POST_PARAMS_DISPLAYED);
}),
/**
* Populates the params container in this view with the specified data.
@ -2357,118 +2344,112 @@ NetworkDetailsView.prototype = {
* @param object aResponse
* The message received from the server.
* @return object
* A promise that is resolved when the response body is set
* A promise that is resolved when the response body is set.
*/
_setResponseBody: function(aUrl, aResponse) {
_setResponseBody: Task.async(function*(aUrl, aResponse) {
if (!aResponse) {
return promise.resolve();
return;
}
let { mimeType, text, encoding } = aResponse.content;
let responseBody = yield gNetwork.getString(text);
return gNetwork.getString(text).then(aString => {
// Handle json, which we tentatively identify by checking the MIME type
// for "json" after any word boundary. This works for the standard
// "application/json", and also for custom types like "x-bigcorp-json".
// Additionally, we also directly parse the response text content to
// verify whether it's json or not, to handle responses incorrectly
// labeled as text/plain instead.
let jsonMimeType, jsonObject, jsonObjectParseError;
try {
// Test the mime type *and* parse the string, because "JSONP" responses
// (json with callback) aren't actually valid json.
jsonMimeType = /\bjson/.test(mimeType);
jsonObject = JSON.parse(aString);
} catch (e) {
jsonObjectParseError = e;
}
if (jsonMimeType || jsonObject) {
// Extract the actual json substring in case this might be a "JSONP".
// This regex basically parses a function call and captures the
// function name and arguments in two separate groups.
let jsonpRegex = /^\s*([\w$]+)\s*\(\s*([^]*)\s*\)\s*;?\s*$/;
let [_, callbackPadding, jsonpString] = aString.match(jsonpRegex) || [];
// Handle json, which we tentatively identify by checking the MIME type
// for "json" after any word boundary. This works for the standard
// "application/json", and also for custom types like "x-bigcorp-json".
// Additionally, we also directly parse the response text content to
// verify whether it's json or not, to handle responses incorrectly
// labeled as text/plain instead.
let jsonMimeType, jsonObject, jsonObjectParseError;
try {
jsonMimeType = /\bjson/.test(mimeType);
jsonObject = JSON.parse(responseBody);
} catch (e) {
jsonObjectParseError = e;
}
if (jsonMimeType || jsonObject) {
// Extract the actual json substring in case this might be a "JSONP".
// This regex basically parses a function call and captures the
// function name and arguments in two separate groups.
let jsonpRegex = /^\s*([\w$]+)\s*\(\s*([^]*)\s*\)\s*;?\s*$/;
let [_, callbackPadding, jsonpString] = responseBody.match(jsonpRegex) || [];
// Make sure this is a valid JSON object first. If so, nicely display
// the parsing results in a variables view. Otherwise, simply show
// the contents as plain text.
if (callbackPadding && jsonpString) {
try {
jsonObject = JSON.parse(jsonpString);
} catch (e) {
jsonObjectParseError = e;
}
}
// Valid JSON or JSONP.
if (jsonObject) {
$("#response-content-json-box").hidden = false;
let jsonScopeName = callbackPadding
? L10N.getFormatStr("jsonpScopeName", callbackPadding)
: L10N.getStr("jsonScopeName");
return this._json.controller.setSingleVariable({
label: jsonScopeName,
rawObject: jsonObject,
}).expanded;
}
// Malformed JSON.
else {
$("#response-content-textarea-box").hidden = false;
let infoHeader = $("#response-content-info-header");
infoHeader.setAttribute("value", jsonObjectParseError);
infoHeader.setAttribute("tooltiptext", jsonObjectParseError);
infoHeader.hidden = false;
return NetMonitorView.editor("#response-content-textarea").then(aEditor => {
aEditor.setMode(Editor.modes.js);
aEditor.setText(aString);
});
// Make sure this is a valid JSON object first. If so, nicely display
// the parsing results in a variables view. Otherwise, simply show
// the contents as plain text.
if (callbackPadding && jsonpString) {
try {
jsonObject = JSON.parse(jsonpString);
} catch (e) {
jsonObjectParseError = e;
}
}
// Handle images.
else if (mimeType.contains("image/")) {
$("#response-content-image-box").setAttribute("align", "center");
$("#response-content-image-box").setAttribute("pack", "center");
$("#response-content-image-box").hidden = false;
$("#response-content-image").src =
"data:" + mimeType + ";" + encoding + "," + aString;
// Immediately display additional information about the image:
// file name, mime type and encoding.
$("#response-content-image-name-value").setAttribute("value", nsIURL(aUrl).fileName);
$("#response-content-image-mime-value").setAttribute("value", mimeType);
$("#response-content-image-encoding-value").setAttribute("value", encoding);
// Valid JSON or JSONP.
if (jsonObject) {
$("#response-content-json-box").hidden = false;
let jsonScopeName = callbackPadding
? L10N.getFormatStr("jsonpScopeName", callbackPadding)
: L10N.getStr("jsonScopeName");
// Wait for the image to load in order to display the width and height.
$("#response-content-image").onload = e => {
// XUL images are majestic so they don't bother storing their dimensions
// in width and height attributes like the rest of the folk. Hack around
// this by getting the bounding client rect and subtracting the margins.
let { width, height } = e.target.getBoundingClientRect();
let dimensions = (width - 2) + " x " + (height - 2);
$("#response-content-image-dimensions-value").setAttribute("value", dimensions);
};
let jsonVar = { label: jsonScopeName, rawObject: jsonObject };
yield this._json.controller.setSingleVariable(jsonVar).expanded;
}
// Handle anything else.
// Malformed JSON.
else {
$("#response-content-textarea-box").hidden = false;
return NetMonitorView.editor("#response-content-textarea").then(aEditor => {
aEditor.setMode(Editor.modes.text);
aEditor.setText(aString);
let infoHeader = $("#response-content-info-header");
infoHeader.setAttribute("value", jsonObjectParseError);
infoHeader.setAttribute("tooltiptext", jsonObjectParseError);
infoHeader.hidden = false;
// Maybe set a more appropriate mode in the Source Editor if possible,
// but avoid doing this for very large files.
if (aString.length < SOURCE_SYNTAX_HIGHLIGHT_MAX_FILE_SIZE) {
for (let key in CONTENT_MIME_TYPE_MAPPINGS) {
if (mimeType.contains(key)) {
aEditor.setMode(CONTENT_MIME_TYPE_MAPPINGS[key]);
break;
}
}
}
});
let editor = yield NetMonitorView.editor("#response-content-textarea");
editor.setMode(Editor.modes.js);
editor.setText(responseBody);
}
}).then(() => window.emit(EVENTS.RESPONSE_BODY_DISPLAYED));
},
}
// Handle images.
else if (mimeType.contains("image/")) {
$("#response-content-image-box").setAttribute("align", "center");
$("#response-content-image-box").setAttribute("pack", "center");
$("#response-content-image-box").hidden = false;
$("#response-content-image").src =
"data:" + mimeType + ";" + encoding + "," + responseBody;
// Immediately display additional information about the image:
// file name, mime type and encoding.
$("#response-content-image-name-value").setAttribute("value", nsIURL(aUrl).fileName);
$("#response-content-image-mime-value").setAttribute("value", mimeType);
$("#response-content-image-encoding-value").setAttribute("value", encoding);
// Wait for the image to load in order to display the width and height.
$("#response-content-image").onload = e => {
// XUL images are majestic so they don't bother storing their dimensions
// in width and height attributes like the rest of the folk. Hack around
// this by getting the bounding client rect and subtracting the margins.
let { width, height } = e.target.getBoundingClientRect();
let dimensions = (width - 2) + " x " + (height - 2);
$("#response-content-image-dimensions-value").setAttribute("value", dimensions);
};
}
// Handle anything else.
else {
$("#response-content-textarea-box").hidden = false;
let editor = yield NetMonitorView.editor("#response-content-textarea");
editor.setMode(Editor.modes.text);
editor.setText(responseBody);
// Maybe set a more appropriate mode in the Source Editor if possible,
// but avoid doing this for very large files.
if (responseBody.length < SOURCE_SYNTAX_HIGHLIGHT_MAX_FILE_SIZE) {
let mapping = Object.keys(CONTENT_MIME_TYPE_MAPPINGS).find(key => mimeType.contains(key));
if (mapping) {
editor.setMode(CONTENT_MIME_TYPE_MAPPINGS[mapping]);
}
}
}
window.emit(EVENTS.RESPONSE_BODY_DISPLAYED);
}),
/**
* Sets the timings information shown in this view.
@ -2545,23 +2526,22 @@ NetworkDetailsView.prototype = {
* @param object aResponse
* The message received from the server.
* @return object
* A promise that is resolved when the response body is set
* A promise that is resolved when the html preview is rendered.
*/
_setHtmlPreview: function(aResponse) {
_setHtmlPreview: Task.async(function*(aResponse) {
if (!aResponse) {
return promise.resolve();
}
let { text } = aResponse.content;
let responseBody = yield gNetwork.getString(text);
// Always disable JS when previewing HTML responses.
let iframe = $("#response-preview");
iframe.contentDocument.docShell.allowJavascript = false;
iframe.contentDocument.documentElement.innerHTML = responseBody;
return gNetwork.getString(text).then(aString => {
// Always disable JS when previewing HTML responses.
iframe.contentDocument.docShell.allowJavascript = false;
iframe.contentDocument.documentElement.innerHTML = aString;
window.emit(EVENTS.RESPONSE_HTML_PREVIEW_DISPLAYED);
});
},
window.emit(EVENTS.RESPONSE_HTML_PREVIEW_DISPLAYED);
}),
_dataSrc: null,
_headers: null,

View File

@ -34,7 +34,8 @@
#details-pane-toggle,
#details-pane[pane-collapsed],
.requests-menu-waterfall,
.requests-menu-footer-label {
.requests-menu-footer-label,
.requests-menu-status-code {
display: none;
}
}

View File

@ -4,6 +4,7 @@ support-files =
html_content-type-test-page.html
html_content-type-without-cache-test-page.html
html_custom-get-page.html
html_single-get-page.html
html_cyrillic-test-page.html
html_filter-test-page.html
html_infinite-get-page.html
@ -87,3 +88,4 @@ support-files =
[browser_net_status-codes.js]
[browser_net_timeline_ticks.js]
[browser_net_timing-division.js]
[browser_net_persistent_logs.js]

View File

@ -0,0 +1,53 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* Tests if the network monitor leaks on initialization and sudden destruction.
* You can also use this initialization format as a template for other tests.
*/
function test() {
let monitor, reqMenu;
initNetMonitor(SINGLE_GET_URL).then(([aTab, aDebuggee, aMonitor]) => {
info("Starting test... ");
monitor = aMonitor;
let { document, NetMonitorView, NetMonitorController } = aMonitor.panelWin;
let { RequestsMenu, NetworkDetails } = NetMonitorView;
reqMenu = RequestsMenu;
Services.prefs.setBoolPref("devtools.webconsole.persistlog", false);
content.location.reload(true);
})
.then(() => {
return waitForNetworkEvents(monitor, 2);
})
.then(() => {
is(reqMenu.itemCount, 2,
"The request menu should have two items at this point.");
})
.then(() => {
content.location.reload(true);
return waitForNetworkEvents(monitor, 2);
})
.then(() => {
// Since the reload clears the log, we still expect two requests in the log
is(reqMenu.itemCount, 2,
"The request menu should still have two items at this point.");
})
.then(() => {
// Now we toggle the persistence logs on
Services.prefs.setBoolPref("devtools.webconsole.persistlog", true);
content.location.reload(true);
return waitForNetworkEvents(monitor, 2);
})
.then(() => {
// Since we togged the persistence logs, we expect four items after the reload
is(reqMenu.itemCount, 4,
"The request menu should now have four items at this point.");
})
.then(() => {
Services.prefs.setBoolPref("devtools.webconsole.persistlog", false);
return teardown(monitor).then(finish);
});
}

View File

@ -34,6 +34,7 @@ const SORTING_URL = EXAMPLE_URL + "html_sorting-test-page.html";
const FILTERING_URL = EXAMPLE_URL + "html_filter-test-page.html";
const INFINITE_GET_URL = EXAMPLE_URL + "html_infinite-get-page.html";
const CUSTOM_GET_URL = EXAMPLE_URL + "html_custom-get-page.html";
const SINGLE_GET_URL = EXAMPLE_URL + "html_single-get-page.html";
const STATISTICS_URL = EXAMPLE_URL + "html_statistics-test-page.html";
const CURL_URL = EXAMPLE_URL + "html_copy-as-curl.html";
const CURL_UTILS_URL = EXAMPLE_URL + "html_curl-utils.html";

View File

@ -26,9 +26,10 @@
function performRequests() {
var rawData = [
"content-type: application/x-www-form-urlencoded",
"custom-header: hello world!",
"",
"content-type: application/x-www-form-urlencoded\r",
"custom-header: hello world!\r",
"\r",
"\r",
"foo=bar&baz=123"
];
post("sjs_simple-test-server.sjs", rawData.join("\n"), function() {

View File

@ -0,0 +1,34 @@
<!-- Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ -->
<!doctype html>
<html>
<head>
<meta charset="utf-8"/>
<title>Network Monitor test page</title>
</head>
<body>
<p>Performing a custom number of GETs</p>
<script type="text/javascript">
function get(aAddress, aCallback) {
var xhr = new XMLHttpRequest();
xhr.open("GET", aAddress, true);
xhr.onreadystatechange = function() {
if (this.readyState == this.DONE) {
aCallback();
}
};
xhr.send(null);
}
(function performRequests() {
get("request_0", function() {
});
})();
</script>
</body>
</html>

View File

@ -184,7 +184,7 @@ this.CurlUtils = {
let contentType = this.findHeader(aData.headers, "content-type");
return (contentType &&
return (contentType &&
contentType.toLowerCase().contains("multipart/form-data;"));
},
@ -228,7 +228,7 @@ this.CurlUtils = {
/**
* Returns the boundary string for a multipart request.
*
*
* @param string aData
* The data source. See the description in the Curl object.
* @return string
@ -243,7 +243,7 @@ this.CurlUtils = {
return contentType.match(boundaryRe)[1];
}
// Temporary workaround. As of 2014-03-11 the requestHeaders array does not
// always contain the Content-Type header for mulitpart requests. See bug 978144.
// always contain the Content-Type header for mulitpart requests. See bug 978144.
// Find the header from the request payload.
let boundaryString = aData.postDataText.match(boundaryRe)[1];
if (boundaryString) {
@ -393,4 +393,4 @@ this.CurlUtils = {
.replace(/\\/g, "\\\\")
.replace(/[\r\n]+/g, "\"^$&\"") + "\"";
}
};
};

View File

@ -79,7 +79,7 @@
const StylesheetUtils = devtools.require("sdk/stylesheet/utils");
let theme = Services.prefs.getCharPref("devtools.theme");
switchTheme(theme);
switchTheme("light");
gDevTools.on("pref-changed", handlePrefChange);
window.addEventListener("unload", function() {

View File

@ -1927,12 +1927,6 @@ function TextPropertyEditor(aRuleEditor, aProperty) {
this.browserWindow = this.doc.defaultView.top;
this.removeOnRevert = this.prop.value === "";
let domRule = this.prop.rule.domRule;
let href = domRule ? domRule.href : null;
if (href) {
this.sheetURI = IOService.newURI(href, null, null);
}
this._onEnableClicked = this._onEnableClicked.bind(this);
this._onExpandClicked = this._onExpandClicked.bind(this);
this._onStartEditing = this._onStartEditing.bind(this);
@ -2079,6 +2073,35 @@ TextPropertyEditor.prototype = {
});
},
/**
* Get the path from which to resolve requests for this
* rule's stylesheet.
* @return {string} the stylesheet's href.
*/
get sheetHref() {
let domRule = this.prop.rule.domRule;
if (domRule) {
return domRule.href || domRule.nodeHref;
}
},
/**
* Get the URI from which to resolve relative requests for
* this rule's stylesheet.
* @return {nsIURI} A URI based on the the stylesheet's href.
*/
get sheetURI() {
if (this._sheetURI === undefined) {
if (this.sheetHref) {
this._sheetURI = IOService.newURI(this.sheetHref, null, null);
} else {
this._sheetURI = null;
}
}
return this._sheetURI;
},
/**
* Resolve a URI based on the rule stylesheet
* @param {string} relativePath the path to resolve

View File

@ -5,10 +5,17 @@
<link href="./browser_styleinspector_bug_677930_urls_clickable/browser_styleinspector_bug_677930_urls_clickable.css" rel="stylesheet" type="text/css">
<style>
.relative2 {
background-image: url(test-image.png);
}
</style>
</head>
<body>
<div class="relative">Background image with relative path (loaded from external css)</div>
<div class="relative1">Background image #1 with relative path (loaded from external css)</div>
<div class="relative2">Background image #2 with relative path (loaded from style tag)</div>
<div class="absolute">Background image with absolute path (loaded from external css)</div>

View File

@ -15,72 +15,80 @@ const TEST_URI = BASE_URL +
const TEST_IMAGE = BASE_URL + "test-image.png";
const BASE_64_URL = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg==";
function selectNode(aInspector, aRuleView)
function setNode(node, inspector)
{
let updated = inspector.once("inspector-updated");
inspector.selection.setNode(node);
return updated;
}
function selectNodes(aInspector, aRuleView)
{
inspector = aInspector;
let sidebar = inspector.sidebar;
let contentDoc = aRuleView.doc;
let relative = doc.querySelector(".relative");
let relative1 = doc.querySelector(".relative1");
let relative2 = doc.querySelector(".relative2");
let absolute = doc.querySelector(".absolute");
let inline = doc.querySelector(".inline");
let base64 = doc.querySelector(".base64");
let noimage = doc.querySelector(".noimage");
let inlineresolved = doc.querySelector(".inline-resolved");
ok(relative, "captain, we have the relative div");
ok(relative1, "captain, we have the relative1 div");
ok(relative2, "captain, we have the relative2 div");
ok(absolute, "captain, we have the absolute div");
ok(inline, "captain, we have the inline div");
ok(base64, "captain, we have the base64 div");
ok(noimage, "captain, we have the noimage div");
ok(inlineresolved, "captain, we have the inlineresolved div");
inspector.selection.setNode(relative);
inspector.once("inspector-updated", () => {
is(inspector.selection.node, relative, "selection matches the relative element");
Task.spawn(function*() {
yield setNode(relative1, inspector);
is(inspector.selection.node, relative1, "selection matches the relative1 element");
let relativeLink = contentDoc.querySelector(".ruleview-propertycontainer a");
ok (relativeLink, "Link exists for relative node");
ok (relativeLink.getAttribute("href"), TEST_IMAGE);
ok (relativeLink, "Link exists for relative1 node");
is (relativeLink.getAttribute("href"), TEST_IMAGE, "href matches");
inspector.selection.setNode(absolute);
inspector.once("inspector-updated", () => {
is(inspector.selection.node, absolute, "selection matches the absolute element");
let absoluteLink = contentDoc.querySelector(".ruleview-propertycontainer a");
ok (absoluteLink, "Link exists for absolute node");
ok (absoluteLink.getAttribute("href"), TEST_IMAGE);
yield setNode(relative2, inspector);
is(inspector.selection.node, relative2, "selection matches the relative2 element");
let relativeLink = contentDoc.querySelector(".ruleview-propertycontainer a");
ok (relativeLink, "Link exists for relative2 node");
is (relativeLink.getAttribute("href"), TEST_IMAGE, "href matches");
inspector.selection.setNode(inline);
inspector.once("inspector-updated", () => {
is(inspector.selection.node, inline, "selection matches the inline element");
let inlineLink = contentDoc.querySelector(".ruleview-propertycontainer a");
ok (inlineLink, "Link exists for inline node");
ok (inlineLink.getAttribute("href"), TEST_IMAGE);
yield setNode(absolute, inspector);
is(inspector.selection.node, absolute, "selection matches the absolute element");
let absoluteLink = contentDoc.querySelector(".ruleview-propertycontainer a");
ok (absoluteLink, "Link exists for absolute node");
is (absoluteLink.getAttribute("href"), TEST_IMAGE, "href matches");
inspector.selection.setNode(base64);
inspector.once("inspector-updated", () => {
is(inspector.selection.node, base64, "selection matches the base64 element");
let base64Link = contentDoc.querySelector(".ruleview-propertycontainer a");
ok (base64Link, "Link exists for base64 node");
ok (base64Link.getAttribute("href"), BASE_64_URL);
yield setNode(inline, inspector);
is(inspector.selection.node, inline, "selection matches the inline element");
let inlineLink = contentDoc.querySelector(".ruleview-propertycontainer a");
ok (inlineLink, "Link exists for inline node");
is (inlineLink.getAttribute("href"), TEST_IMAGE, "href matches");
inspector.selection.setNode(inlineresolved);
inspector.once("inspector-updated", () => {
is(inspector.selection.node, inlineresolved, "selection matches the style tag element");
let inlineResolvedLink = contentDoc.querySelector(".ruleview-propertycontainer a");
ok (inlineResolvedLink, "Link exists for style tag node");
ok (inlineResolvedLink.getAttribute("href"), TEST_IMAGE);
yield setNode(base64, inspector);
is(inspector.selection.node, base64, "selection matches the base64 element");
let base64Link = contentDoc.querySelector(".ruleview-propertycontainer a");
ok (base64Link, "Link exists for base64 node");
is (base64Link.getAttribute("href"), BASE_64_URL, "href matches");
inspector.selection.setNode(noimage);
inspector.once("inspector-updated", () => {
is(inspector.selection.node, noimage, "selection matches the inline element");
let noimageLink = contentDoc.querySelector(".ruleview-propertycontainer a");
ok (!noimageLink, "There is no link for the node with no background image");
finishUp();
});
});
});
});
});
yield setNode(inlineresolved, inspector);
is(inspector.selection.node, inlineresolved, "selection matches the style tag element");
let inlineResolvedLink = contentDoc.querySelector(".ruleview-propertycontainer a");
ok (inlineResolvedLink, "Link exists for style tag node");
is (inlineResolvedLink.getAttribute("href"), TEST_IMAGE, "href matches");
yield setNode(noimage, inspector);
is(inspector.selection.node, noimage, "selection matches the inline element");
let noimageLink = contentDoc.querySelector(".ruleview-propertycontainer a");
ok (!noimageLink, "There is no link for the node with no background image");
finishUp();
});
}
@ -98,7 +106,7 @@ function test()
gBrowser.selectedBrowser.addEventListener("load", function onLoad(evt) {
gBrowser.selectedBrowser.removeEventListener(evt.type, onLoad, true);
doc = content.document;
waitForFocus(() => openRuleView(selectNode), content);
waitForFocus(() => openRuleView(selectNodes), content);
}, true);
content.location = TEST_URI;

View File

@ -1,4 +1,4 @@
.relative {
.relative1 {
background-image: url(../test-image.png);
}
.absolute {

View File

@ -6,8 +6,8 @@
const TEST_BASE_HTTP = "http://example.com/browser/browser/devtools/styleinspector/test/";
const TEST_BASE_HTTPS = "https://example.com/browser/browser/devtools/styleinspector/test/";
//Services.prefs.setBoolPref("devtools.dump.emit", true);
Services.prefs.setBoolPref("devtools.debugger.log", true);
// Services.prefs.setBoolPref("devtools.dump.emit", true);
// Services.prefs.setBoolPref("devtools.debugger.log", true);
let tempScope = {};
@ -31,7 +31,7 @@ SimpleTest.registerCleanupFunction(() => {
});
SimpleTest.registerCleanupFunction(() => {
Services.prefs.clearUserPref("devtools.debugger.log");
// Services.prefs.clearUserPref("devtools.debugger.log");
Services.prefs.clearUserPref("devtools.dump.emit");
});
@ -74,7 +74,7 @@ function openView(name, callback)
callback(inspector, view);
}
if (inspector.sidebar.getTab(name)) {
if (inspector.sidebar.getWindowForTab(name)) {
onReady();
} else {
inspector.sidebar.once(name + "-ready", onReady);

View File

@ -0,0 +1,17 @@
# 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/.
include $(topsrcdir)/config/rules.mk
# This is so hacky. Waiting on bug 988938.
addondir = $(srcdir)/test/addons
testdir = $(abspath $(DEPTH)/_tests/xpcshell/browser/experiments/test/xpcshell)
libs::
$(EXIT_ON_ERROR) \
$(NSINSTALL) -D $(testdir); \
for dir in $(addondir)/*; do \
base=`basename $$dir`; \
(cd $$dir && zip -qr $(testdir)/$$base.xpi *); \
done

View File

@ -0,0 +1,16 @@
<?xml version="1.0"?>
<RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:em="http://www.mozilla.org/2004/em-rdf#">
<Description about="urn:mozilla:install-manifest">
<em:id>test-experiment-1@tests.mozilla.org</em:id>
<em:version>1</em:version>
<em:type>128</em:type>
<!-- Front End MetaData -->
<em:name>Test experiment 1</em:name>
<em:description>Yet another experiment that experiments experimentally.</em:description>
</Description>
</RDF>

View File

@ -0,0 +1,16 @@
<?xml version="1.0"?>
<RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:em="http://www.mozilla.org/2004/em-rdf#">
<Description about="urn:mozilla:install-manifest">
<em:id>test-experiment-1@tests.mozilla.org</em:id>
<em:version>1.1</em:version>
<em:type>128</em:type>
<!-- Front End MetaData -->
<em:name>Test experiment 1.1</em:name>
<em:description>And yet another experiment that experiments experimentally.</em:description>
</Description>
</RDF>

View File

@ -0,0 +1,16 @@
<?xml version="1.0"?>
<RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:em="http://www.mozilla.org/2004/em-rdf#">
<Description about="urn:mozilla:install-manifest">
<em:id>test-experiment-2@tests.mozilla.org</em:id>
<em:version>1</em:version>
<em:type>128</em:type>
<!-- Front End MetaData -->
<em:name>Test experiment 2</em:name>
<em:description>And yet another experiment that experiments experimentally.</em:description>
</Description>
</RDF>

View File

@ -12,18 +12,48 @@ Cu.import("resource://services-sync/healthreport.jsm", this);
Cu.import("resource://testing-common/services/healthreport/utils.jsm", this);
Cu.import("resource://gre/modules/services/healthreport/providers.jsm");
function getExperimentPath(base) {
let p = do_get_cwd();
p.append(base);
return p.path;
}
function sha1File(path) {
let f = Cc["@mozilla.org/file/local;1"]
.createInstance(Ci.nsILocalFile);
f.initWithPath(path);
let hasher = Cc["@mozilla.org/security/hash;1"]
.createInstance(Ci.nsICryptoHash);
hasher.init(hasher.SHA1);
let is = Cc["@mozilla.org/network/file-input-stream;1"]
.createInstance(Ci.nsIFileInputStream);
is.init(f, -1, 0, 0);
hasher.updateFromStream(is, Math.pow(2, 32) - 1);
is.close();
let bytes = hasher.finish(false);
return [("0" + bytes.charCodeAt(byte).toString(16)).slice(-2)
for (byte in bytes)]
.join("");
}
const EXPERIMENT1_ID = "test-experiment-1@tests.mozilla.org";
const EXPERIMENT1_XPI_SHA1 = "sha1:0f15ee3677ffbf1e82367069fe4e8fe8e2ad838f";
const EXPERIMENT1_XPI_NAME = "experiment-1.xpi";
const EXPERIMENT1_NAME = "Test experiment 1";
const EXPERIMENT1_PATH = getExperimentPath(EXPERIMENT1_XPI_NAME);
const EXPERIMENT1_XPI_SHA1 = "sha1:" + sha1File(EXPERIMENT1_PATH);
const EXPERIMENT1A_XPI_SHA1 = "sha1:b938f1b4f0bf466a67257aff26d4305ac24231eb";
const EXPERIMENT1A_XPI_NAME = "experiment-1a.xpi";
const EXPERIMENT1A_NAME = "Test experiment 1.1";
const EXPERIMENT1A_PATH = getExperimentPath(EXPERIMENT1A_XPI_NAME);
const EXPERIMENT1A_XPI_SHA1 = "sha1:" + sha1File(EXPERIMENT1A_PATH);
const EXPERIMENT2_ID = "test-experiment-2@tests.mozilla.org"
const EXPERIMENT2_XPI_SHA1 = "sha1:9d23425421941e1d1e2037232cf5aeae82dbd4e4";
const EXPERIMENT2_XPI_NAME = "experiment-2.xpi";
const EXPERIMENT2_PATH = getExperimentPath(EXPERIMENT2_XPI_NAME);
const EXPERIMENT2_XPI_SHA1 = "sha1:" + sha1File(EXPERIMENT2_PATH);
const EXPERIMENT3_ID = "test-experiment-3@tests.mozilla.org";
const EXPERIMENT4_ID = "test-experiment-4@tests.mozilla.org";

View File

@ -4,9 +4,13 @@ tail =
firefox-appdir = browser
support-files =
experiments_1.manifest
../experiment-1.xpi
../experiment-1a.xpi
../experiment-2.xpi
experiment-1.xpi
experiment-1a.xpi
experiment-2.xpi
generated-files =
experiment-1.xpi
experiment-1a.xpi
experiment-2.xpi
[test_activate.js]
[test_api.js]

View File

@ -560,15 +560,15 @@ FunctionEnd
${EndIf}
${GetLongPath} "$INSTDIR" $8
StrCpy $0 "Software\Mozilla\${BrandFullNameInternal}\${AppVersion}$3 (${AB_CD})\Main"
StrCpy $0 "Software\Mozilla\${BrandFullNameInternal}\${AppVersion}$3 (${ARCH} ${AB_CD})\Main"
${WriteRegStr2} $TmpVal "$0" "Install Directory" "$8" 0
${WriteRegStr2} $TmpVal "$0" "PathToExe" "$8\${FileMainEXE}" 0
StrCpy $0 "Software\Mozilla\${BrandFullNameInternal}\${AppVersion}$3 (${AB_CD})\Uninstall"
StrCpy $0 "Software\Mozilla\${BrandFullNameInternal}\${AppVersion}$3 (${ARCH} ${AB_CD})\Uninstall"
${WriteRegStr2} $TmpVal "$0" "Description" "${BrandFullNameInternal} ${AppVersion}$3 (${ARCH} ${AB_CD})" 0
StrCpy $0 "Software\Mozilla\${BrandFullNameInternal}\${AppVersion}$3 (${AB_CD})"
${WriteRegStr2} $TmpVal "$0" "" "${AppVersion}$3 (${AB_CD})" 0
StrCpy $0 "Software\Mozilla\${BrandFullNameInternal}\${AppVersion}$3 (${ARCH} ${AB_CD})"
${WriteRegStr2} $TmpVal "$0" "" "${AppVersion}$3 (${ARCH} ${AB_CD})" 0
${If} "$3" == ""
DeleteRegValue SHCTX "$0" "ESR"
${Else}
@ -592,7 +592,7 @@ FunctionEnd
StrCpy $0 "Software\Mozilla\${BrandFullNameInternal}$3"
${WriteRegStr2} $TmpVal "$0" "" "${GREVersion}" 0
${WriteRegStr2} $TmpVal "$0" "CurrentVersion" "${AppVersion}$3 (${AB_CD})" 0
${WriteRegStr2} $TmpVal "$0" "CurrentVersion" "${AppVersion}$3 (${ARCH} ${AB_CD})" 0
!macroend
!define SetAppKeys "!insertmacro SetAppKeys"

View File

@ -1770,6 +1770,15 @@ FunctionEnd
Function OnChange_DirRequest
Pop $0
System::Call 'user32::GetWindowTextW(i $DirRequest, w .r0, i ${NSIS_MAX_STRLEN})'
StrCpy $1 "$0" 1 ; the first character
${If} "$1" == "$\""
StrCpy $1 "$0" "" -1 ; the last character
${If} "$1" == "$\""
StrCpy $0 "$0" "" 1 ; all but the first character
StrCpy $0 "$0" -1 ; all but the last character
${EndIf}
${EndIf}
StrCpy $INSTDIR "$0"
Call UpdateFreeSpaceLabel
@ -1782,17 +1791,7 @@ Function OnChange_DirRequest
FunctionEnd
Function OnClick_ButtonBrowse
; The call to GetLongPath returns a long path without a trailing
; back-slash. Append a \ to the path to prevent the directory
; name from being appended when using the NSIS create new folder.
; http://www.nullsoft.com/free/nsis/makensis.htm#InstallDir
StrCpy $0 "$INSTDIR" "" -1 ; the last character
${If} "$0" == "\"
StrCpy $0 "$INSTDIR"
${Else}
StrCpy $0 "$INSTDIR"
${EndIf}
StrCpy $0 "$INSTDIR"
nsDialogs::SelectFolderDialog /NOUNLOAD "$(SELECT_FOLDER_TEXT)" $0
Pop $0
${If} $0 == "error" ; returns 'error' if 'cancel' was pressed?
@ -1801,7 +1800,7 @@ Function OnClick_ButtonBrowse
${If} $0 != ""
StrCpy $INSTDIR "$0"
system::Call 'user32::SetWindowTextW(i $DirRequest, w "$INSTDIR")'
System::Call 'user32::SetWindowTextW(i $DirRequest, w "$INSTDIR")'
${EndIf}
FunctionEnd

View File

@ -113,13 +113,6 @@
- heading of the group of Web Console preferences in the options panel. -->
<!ENTITY options.webconsole.label "Web Console">
<!-- LOCALIZATION NOTE (options.enablePersistentLogging.label): This is the
- label for the checkbox that toggles persistent logs in the Web Console,
- i.e. devtools.webconsole.persistlog a boolean preference in about:config,
- in the options panel. -->
<!ENTITY options.enablePersistentLogging.label "Enable persistent logs">
<!ENTITY options.enablePersistentLogging.tooltip "If you enable this option the Web Console will not clear the output each time you navigate to a new page">
<!-- LOCALIZATION NOTE (options.timestampMessages.label): This is the
- label for the checkbox that toggles timestamps in the Web Console -->
<!ENTITY options.timestampMessages.label "Enable timestamps">
@ -145,6 +138,18 @@
- panel. -->
<!ENTITY options.profiler.label "JavaScript Profiler">
<!-- LOCALICATION NOTE (options.commonprefs): This is the label for the heading
of all preferences that affect both the Web Console and the Network
Monitor -->
<!ENTITY options.commonPrefs.label "Common Preferences">
<!-- LOCALIZATION NOTE (options.enablePersistentLogging.label): This is the
- label for the checkbox that toggles persistent logs in the Web Console and
- network monitor, i.e. devtools.webconsole.persistlog a boolean preference in
- about:config, in the options panel. -->
<!ENTITY options.enablePersistentLogging.label "Enable persistent logs">
<!ENTITY options.enablePersistentLogging.tooltip "If you enable this option the Web Console and Network Monitor will not clear the output each time you navigate to a new page">
<!-- LOCALIZATION NOTE (options.showPlatformData.label): This is the
- label for the checkbox that toggles the display of the platform data in the,
- Profiler i.e. devtools.profiler.ui.show-platform-data a boolean preference

View File

@ -7,3 +7,4 @@ newtab.unpin=Unpin this site
newtab.block=Remove this site
newtab.show=Show the new tab page
newtab.hide=Hide the new tab page
newtab.sponsored=Show information on sponsored tiles

View File

@ -2,6 +2,16 @@
- 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/. -->
<!-- LOCALIZATION NOTE (translation.thisPageIsIn.label,
- translation.translateThisPage.label):
- These 2 strings are used to construct a sentence that contains a dropdown
- showing the detected language of the current web page.
- In en-US it looks like this:
- This page is in [detected language] Translate this page?
- "detected language" here is a language name coming from the
- global/languageNames.properties file; for some locales it may not be in
- the correct grammar case to keep the same structure of the original
- sentence. -->
<!ENTITY translation.thisPageIsIn.label "This page is in">
<!ENTITY translation.translateThisPage.label "Translate this page?">
<!ENTITY translation.translate.button "Translate">
@ -9,8 +19,25 @@
<!ENTITY translation.translatingContent.label "Translating page content…">
<!-- LOCALIZATION NOTE (translation.translatedFrom.label,
- translation.translatedTo.label,
- translation.translatedToSuffix.label):
- These 3 strings are used to construct a sentence that contains 2 dropdowns
- showing the source and target language of a translated web page.
- In en-US it looks like this:
- This page has been translated from [from language] to [to language]
- "from language" and "to language" here are language names coming from the
- global/languageNames.properties file; for some locales they may not be in
- the correct grammar case to keep the same structure of the original
- sentence.
-
- translation.translatedToSuffix.label (empty in en-US) is for locales that
- need to display some text after the second drop down for the sentence to
- be grammatically correct. -->
<!ENTITY translation.translatedFrom.label "This page has been translated from">
<!ENTITY translation.translatedTo.label "to">
<!ENTITY translation.translatedToSuffix.label "">
<!ENTITY translation.showOriginal.button "Show Original">
<!ENTITY translation.showTranslation.button "Show Translation">

View File

@ -869,6 +869,7 @@ toolbarbutton[sdk-button="true"][cui-areatype="toolbar"] > .toolbarbutton-icon {
padding: 1px;
border: 1px solid ThreeDShadow;
border-radius: 2px;
margin: 1px 3px;
}
#urlbar[focused],
@ -894,8 +895,7 @@ toolbarbutton[sdk-button="true"][cui-areatype="toolbar"] > .toolbarbutton-icon {
}
#urlbar-container {
-moz-box-orient: horizontal;
-moz-box-align: stretch;
-moz-box-align: center;
}
@conditionalForwardWithUrlbar@ > #urlbar-wrapper {
@ -926,13 +926,10 @@ toolbarbutton[sdk-button="true"][cui-areatype="toolbar"] > .toolbarbutton-icon {
}
@conditionalForwardWithUrlbar@[forwarddisabled] > #urlbar-wrapper {
/* Work with margin-top to align the clip-path correctly. */
margin-top: 5px;
clip-path: url("chrome://browser/content/browser.xul#urlbar-back-button-clip-path");
}
@conditionalForwardWithUrlbar@[forwarddisabled] > #urlbar-wrapper > #urlbar {
margin-top: -4px;
margin-left: -@conditionalForwardWithUrlbarWidth@px;
}

View File

@ -107,7 +107,7 @@ browser.jar:
skin/classic/browser/feeds/subscribe-ui.css (feeds/subscribe-ui.css)
skin/classic/browser/fonts/ClearSans-Regular.ttf (../shared/ClearSans-Regular.ttf)
skin/classic/browser/newtab/newTab.css (newtab/newTab.css)
skin/classic/browser/newtab/controls.png (newtab/controls.png)
skin/classic/browser/newtab/controls.png (../shared/newtab/controls.png)
skin/classic/browser/places/bookmarksMenu.png (places/bookmarksMenu.png)
skin/classic/browser/places/bookmarksToolbar.png (places/bookmarksToolbar.png)
skin/classic/browser/places/bookmarksToolbar-menuPanel.png (places/bookmarksToolbar-menuPanel.png)
@ -176,6 +176,7 @@ browser.jar:
* skin/classic/browser/devtools/common.css (../shared/devtools/common.css)
* skin/classic/browser/devtools/dark-theme.css (../shared/devtools/dark-theme.css)
* skin/classic/browser/devtools/light-theme.css (../shared/devtools/light-theme.css)
skin/classic/browser/devtools/metal.jpg (../shared/devtools/images/metal.jpg)
skin/classic/browser/devtools/filters.svg (../shared/devtools/filters.svg)
skin/classic/browser/devtools/controls.png (../shared/devtools/images/controls.png)
skin/classic/browser/devtools/controls@2x.png (../shared/devtools/images/controls@2x.png)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.1 KiB

View File

@ -89,7 +89,7 @@
border-color: rgba(8,22,37,.15) rgba(8,22,37,.17) rgba(8,22,37,.19);
}
.newtab-cell:hover:not(:empty):not([dragged]) {
.newtab-cell:hover:not(:empty):not([dragged]):not([ignorehover]) {
border-color: rgba(8,22,37,.25) rgba(8,22,37,.27) rgba(8,22,37,.3);
}
@ -99,7 +99,7 @@
transition-property: top, left, opacity, box-shadow, background-color;
}
.newtab-site:hover,
.newtab-cell:not([ignorehover]) > .newtab-site:hover,
.newtab-site[dragged] {
box-shadow: 0 0 10px rgba(8,22,37,.3);
}
@ -117,6 +117,13 @@
background-size: cover;
}
.newtab-site[type=affiliate] .newtab-thumbnail,
.newtab-site[type=organic] .newtab-thumbnail,
.newtab-site[type=sponsored] .newtab-thumbnail {
background-position: top center;
background-size: auto;
}
/* TITLES */
.newtab-title {
color: #525c66;
@ -124,6 +131,10 @@
font-size: 13px;
}
.newtab-site[type=sponsored] .newtab-title {
-moz-padding-end: 24px;
}
/* CONTROLS */
.newtab-control {
width: 24px;
@ -164,3 +175,11 @@
.newtab-control-block:active {
background-position: -192px 0;
}
.newtab-control-sponsored {
background-position: -249px -1px;
}
.newtab-control-sponsored:hover {
background-position: -265px -1px;
}

View File

@ -170,7 +170,7 @@ browser.jar:
skin/classic/browser/feeds/audioFeedIcon16.png (feeds/feedIcon16.png)
skin/classic/browser/fonts/ClearSans-Regular.ttf (../shared/ClearSans-Regular.ttf)
skin/classic/browser/newtab/newTab.css (newtab/newTab.css)
skin/classic/browser/newtab/controls.png (newtab/controls.png)
skin/classic/browser/newtab/controls.png (../shared/newtab/controls.png)
skin/classic/browser/newtab/controls@2x.png (newtab/controls@2x.png)
skin/classic/browser/setDesktopBackground.css
skin/classic/browser/monitor.png
@ -297,6 +297,7 @@ browser.jar:
* skin/classic/browser/devtools/common.css (../shared/devtools/common.css)
* skin/classic/browser/devtools/dark-theme.css (../shared/devtools/dark-theme.css)
* skin/classic/browser/devtools/light-theme.css (../shared/devtools/light-theme.css)
skin/classic/browser/devtools/metal.jpg (../shared/devtools/images/metal.jpg)
skin/classic/browser/devtools/filters.svg (../shared/devtools/filters.svg)
skin/classic/browser/devtools/controls.png (../shared/devtools/images/controls.png)
skin/classic/browser/devtools/controls@2x.png (../shared/devtools/images/controls@2x.png)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 22 KiB

View File

@ -77,7 +77,7 @@
@media (min-resolution: 2dppx) {
#newtab-toggle {
background-image: url(chrome://browser/skin/newtab/controls@2x.png);
background-size: 248px;
background-size: 296px;
}
}
@ -93,7 +93,7 @@
border-color: rgba(8,22,37,.15) rgba(8,22,37,.17) rgba(8,22,37,.19);
}
.newtab-cell:hover:not(:empty):not([dragged]) {
.newtab-cell:hover:not(:empty):not([dragged]):not([ignorehover]) {
border-color: rgba(8,22,37,.25) rgba(8,22,37,.27) rgba(8,22,37,.3);
}
@ -103,7 +103,7 @@
transition-property: top, left, opacity, box-shadow, background-color;
}
.newtab-site:hover,
.newtab-cell:not([ignorehover]) > .newtab-site:hover,
.newtab-site[dragged] {
box-shadow: 0 0 10px rgba(8,22,37,.3);
}
@ -121,6 +121,13 @@
background-size: cover;
}
.newtab-site[type=affiliate] .newtab-thumbnail,
.newtab-site[type=organic] .newtab-thumbnail,
.newtab-site[type=sponsored] .newtab-thumbnail {
background-position: top center;
background-size: auto;
}
/* TITLES */
.newtab-title {
color: #525c66;
@ -128,6 +135,10 @@
font-size: 13px;
}
.newtab-site[type=sponsored] .newtab-title {
-moz-padding-end: 24px;
}
/* CONTROLS */
.newtab-control {
width: 24px;
@ -140,7 +151,7 @@
@media (min-resolution: 2dppx) {
.newtab-control {
background-image: url(chrome://browser/skin/newtab/controls@2x.png);
background-size: 248px;
background-size: 296px;
}
}
@ -175,3 +186,11 @@
.newtab-control-block:active {
background-position: -192px 0;
}
.newtab-control-sponsored {
background-position: -249px -1px;
}
.newtab-control-sponsored:hover {
background-position: -265px -1px;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 111 KiB

View File

@ -135,6 +135,7 @@
color: #585959;
background-color: #f0f1f2;
border-color: #aaa;
background-image: url("chrome://browser/skin/devtools/metal.jpg");
}
.ruleview-colorswatch,

View File

@ -15,6 +15,7 @@
padding: 4px 3px;
border-bottom-width: 1px;
border-bottom-style: solid;
font: 3mm "comic sans ms", Purisa, message-box !important;
}
.devtools-menulist,
@ -615,11 +616,13 @@
border: 0px solid;
border-bottom-width: 1px;
padding: 0;
font: 3mm "comic sans ms", Purisa, message-box !important;
}
.theme-light .devtools-tabbar {
box-shadow: 0 -2px 0 rgba(170,170,170,.1) inset;
background: #ebeced;
background-image: url("chrome://browser/skin/devtools/metal.jpg");
border-bottom-color: #aaa;
}

View File

@ -161,7 +161,7 @@
.theme-light #breadcrumb-separator-after,
.theme-light #breadcrumb-separator-before:after {
background: #f0f1f2; /* Toolbars */
background: url("chrome://browser/skin/devtools/metal.jpg"); /* Toolbars */
}
/* This chevron arrow cannot be replicated easily in CSS, so we are using

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.2 KiB

View File

@ -634,7 +634,8 @@ toolbar .toolbarbutton-1 > .toolbarbutton-menubutton-dropmarker:-moz-lwtheme-bri
#nav-bar .toolbarbutton-1 > .toolbarbutton-icon,
#nav-bar .toolbarbutton-1 > .toolbarbutton-text,
#nav-bar .toolbarbutton-1 > .toolbarbutton-badge-container,
#nav-bar .toolbarbutton-1 > .toolbarbutton-menubutton-button > .toolbarbutton-icon {
#nav-bar .toolbarbutton-1 > .toolbarbutton-menubutton-button > .toolbarbutton-icon,
#nav-bar .toolbarbutton-1 > .toolbarbutton-menubutton-dropmarker > .dropmarker-icon {
background-color: hsla(210,32%,93%,0);
background-origin: padding-box;
border-radius: 2px;

View File

@ -128,7 +128,7 @@ browser.jar:
skin/classic/browser/feeds/subscribe-ui.css (feeds/subscribe-ui.css)
skin/classic/browser/fonts/ClearSans-Regular.ttf (../shared/ClearSans-Regular.ttf)
skin/classic/browser/newtab/newTab.css (newtab/newTab.css)
skin/classic/browser/newtab/controls.png (newtab/controls.png)
skin/classic/browser/newtab/controls.png (../shared/newtab/controls.png)
skin/classic/browser/places/places.css (places/places.css)
* skin/classic/browser/places/organizer.css (places/organizer.css)
skin/classic/browser/places/bookmark.png (places/bookmark.png)
@ -209,6 +209,7 @@ browser.jar:
* skin/classic/browser/devtools/common.css (../shared/devtools/common.css)
* skin/classic/browser/devtools/dark-theme.css (../shared/devtools/dark-theme.css)
* skin/classic/browser/devtools/light-theme.css (../shared/devtools/light-theme.css)
skin/classic/browser/devtools/metal.jpg (../shared/devtools/images/metal.jpg)
skin/classic/browser/devtools/filters.svg (../shared/devtools/filters.svg)
skin/classic/browser/devtools/controls.png (../shared/devtools/images/controls.png)
skin/classic/browser/devtools/controls@2x.png (../shared/devtools/images/controls@2x.png)
@ -477,7 +478,7 @@ browser.jar:
skin/classic/aero/browser/feeds/subscribe-ui.css (feeds/subscribe-ui.css)
skin/classic/aero/browser/fonts/ClearSans-Regular.ttf (../shared/ClearSans-Regular.ttf)
skin/classic/aero/browser/newtab/newTab.css (newtab/newTab.css)
skin/classic/aero/browser/newtab/controls.png (newtab/controls.png)
skin/classic/aero/browser/newtab/controls.png (../shared/newtab/controls.png)
* skin/classic/aero/browser/places/places.css (places/places-aero.css)
* skin/classic/aero/browser/places/organizer.css (places/organizer-aero.css)
skin/classic/aero/browser/places/bookmark.png (places/bookmark-aero.png)
@ -557,6 +558,7 @@ browser.jar:
* skin/classic/aero/browser/devtools/common.css (../shared/devtools/common.css)
* skin/classic/aero/browser/devtools/dark-theme.css (../shared/devtools/dark-theme.css)
* skin/classic/aero/browser/devtools/light-theme.css (../shared/devtools/light-theme.css)
skin/classic/aero/browser/devtools/metal.jpg (../shared/devtools/images/metal.jpg)
skin/classic/aero/browser/devtools/filters.svg (../shared/devtools/filters.svg)
skin/classic/aero/browser/devtools/controls.png (../shared/devtools/images/controls.png)
skin/classic/aero/browser/devtools/controls@2x.png (../shared/devtools/images/controls@2x.png)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.1 KiB

View File

@ -92,7 +92,7 @@
border-color: rgba(8,22,37,.15) rgba(8,22,37,.17) rgba(8,22,37,.19);
}
.newtab-cell:hover:not(:empty):not([dragged]) {
.newtab-cell:hover:not(:empty):not([dragged]):not([ignorehover]) {
border-color: rgba(8,22,37,.25) rgba(8,22,37,.27) rgba(8,22,37,.3);
}
@ -102,7 +102,7 @@
transition-property: top, left, opacity, box-shadow, background-color;
}
.newtab-site:hover,
.newtab-cell:not([ignorehover]) > .newtab-site:hover,
.newtab-site[dragged] {
box-shadow: 0 0 10px rgba(8,22,37,.3);
}
@ -120,6 +120,13 @@
background-size: cover;
}
.newtab-site[type=affiliate] .newtab-thumbnail,
.newtab-site[type=organic] .newtab-thumbnail,
.newtab-site[type=sponsored] .newtab-thumbnail {
background-position: top center;
background-size: auto;
}
/* TITLES */
.newtab-title {
color: #525c66;
@ -127,6 +134,10 @@
font-size: 13px;
}
.newtab-site[type=sponsored] .newtab-title {
-moz-padding-end: 24px;
}
/* CONTROLS */
.newtab-control {
width: 24px;
@ -167,3 +178,11 @@
.newtab-control-block:active {
background-position: -192px 0;
}
.newtab-control-sponsored {
background-position: -249px -1px;
}
.newtab-control-sponsored:hover {
background-position: -265px -1px;
}

View File

@ -4,18 +4,18 @@
"use strict";
let Cu = Components.utils;
let Ci = Components.interfaces;
let Cc = Components.classes;
let Cr = Components.results;
const {utils: Cu, interfaces: Ci} = Components;
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/BrowserElementParent.jsm");
const NS_PREFBRANCH_PREFCHANGE_TOPIC_ID = "nsPref:changed";
const BROWSER_FRAMES_ENABLED_PREF = "dom.mozBrowserFramesEnabled";
XPCOMUtils.defineLazyModuleGetter(this, "BrowserElementParentBuilder",
"resource://gre/modules/BrowserElementParent.jsm",
"BrowserElementParentBuilder");
function debug(msg) {
//dump("BrowserElementParent.js - " + msg + "\n");
}

View File

@ -11,6 +11,7 @@ GARBAGE = \
ant.properties \
build.xml \
local.properties \
geckoview_example.apk \
$(NULL)
GARBAGE_DIRS = \
@ -55,5 +56,10 @@ build.xml: $(CURDIR)/AndroidManifest.xml
mv AndroidManifest.xml.save AndroidManifest.xml
echo jar.libs.dir=libs >> project.properties
package: $(PACKAGE_DEPS) FORCE
bin/GeckoViewExample-debug.apk: $(PACKAGE_DEPS)
ant debug
geckoview_example.apk: bin/GeckoViewExample-debug.apk
cp $< $@
package: geckoview_example.apk FORCE

View File

@ -85,6 +85,9 @@ public class DynamicPanel extends HomeFragment
// On URL open listener
private OnUrlOpenListener mUrlOpenListener;
// The current UI mode in the fragment
private UIMode mUIMode;
/*
* Different UI modes to display depending on the authentication state.
*
@ -141,6 +144,12 @@ public class DynamicPanel extends HomeFragment
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
// Restore whatever the UI mode the fragment had before
// a device rotation.
if (mUIMode != null) {
setUIMode(mUIMode);
}
mPanelAuthCache.setOnChangeListener(new PanelAuthChangeListener());
GeckoAppShell.registerEventListener("HomePanels:RefreshDataset", this);
}
@ -250,7 +259,12 @@ public class DynamicPanel extends HomeFragment
}
mPanelLayout.setVisibility(View.VISIBLE);
if (canLoad()) {
// Only trigger a reload if the UI mode has changed
// (e.g. auth cache changes) and the fragment is allowed
// to load its contents. Any loaders associated with the
// panel layout will be automatically re-bound after a
// device rotation, no need to explicitly load it again.
if (mUIMode != mode && canLoad()) {
mPanelLayout.load();
}
break;
@ -268,6 +282,8 @@ public class DynamicPanel extends HomeFragment
default:
throw new IllegalStateException("Unrecognized UIMode in DynamicPanel");
}
mUIMode = mode;
}
@Override

View File

@ -862,12 +862,26 @@ public final class HomeConfig {
* method.
*/
public static class State implements Iterable<PanelConfig> {
private final HomeConfig mHomeConfig;
private HomeConfig mHomeConfig;
private final List<PanelConfig> mPanelConfigs;
private final boolean mIsDefault;
private State(HomeConfig homeConfig, List<PanelConfig> panelConfigs) {
State(List<PanelConfig> panelConfigs, boolean isDefault) {
this(null, panelConfigs, isDefault);
}
private State(HomeConfig homeConfig, List<PanelConfig> panelConfigs, boolean isDefault) {
mHomeConfig = homeConfig;
mPanelConfigs = Collections.unmodifiableList(panelConfigs);
mIsDefault = isDefault;
}
private void setHomeConfig(HomeConfig homeConfig) {
if (mHomeConfig != null) {
throw new IllegalStateException("Can't set HomeConfig more than once");
}
mHomeConfig = homeConfig;
}
@Override
@ -875,6 +889,14 @@ public final class HomeConfig {
return mPanelConfigs.iterator();
}
/**
* Returns whether this {@code State} instance represents the default
* {@code HomeConfig} configuration or not.
*/
public boolean isDefault() {
return mIsDefault;
}
/**
* Creates an {@code Editor} for this state.
*/
@ -909,6 +931,9 @@ public final class HomeConfig {
private PanelConfig mDefaultPanel;
private int mEnabledCount;
private boolean mHasChanged;
private final boolean mIsFromDefault;
private Editor(HomeConfig homeConfig, State configState) {
mHomeConfig = homeConfig;
mOriginalThread = Thread.currentThread();
@ -916,6 +941,9 @@ public final class HomeConfig {
mConfigOrder = new LinkedList<String>();
mEnabledCount = 0;
mHasChanged = false;
mIsFromDefault = configState.isDefault();
initFromState(configState);
}
@ -1050,6 +1078,7 @@ public final class HomeConfig {
setPanelIsDisabled(panelConfig, false);
mDefaultPanel = panelConfig;
mHasChanged = true;
}
/**
@ -1076,6 +1105,8 @@ public final class HomeConfig {
} else if (mEnabledCount == 1) {
setDefault(panelId);
}
mHasChanged = true;
}
/**
@ -1115,6 +1146,7 @@ public final class HomeConfig {
installed = true;
}
mHasChanged = true;
return installed;
}
@ -1146,6 +1178,7 @@ public final class HomeConfig {
findNewDefault();
}
mHasChanged = true;
return true;
}
@ -1194,6 +1227,7 @@ public final class HomeConfig {
updated = true;
}
mHasChanged = true;
return updated;
}
@ -1209,7 +1243,8 @@ public final class HomeConfig {
// We're about to save the current state in the background thread
// so we should use a deep copy of the PanelConfig instances to
// avoid saving corrupted state.
final State newConfigState = new State(mHomeConfig, makeOrderedCopy(true));
final State newConfigState =
new State(mHomeConfig, makeOrderedCopy(true), isDefault());
ThreadUtils.getBackgroundHandler().post(new Runnable() {
@Override
@ -1230,7 +1265,8 @@ public final class HomeConfig {
public State commit() {
ThreadUtils.assertOnThread(mOriginalThread);
final State newConfigState = new State(mHomeConfig, makeOrderedCopy(false));
final State newConfigState =
new State(mHomeConfig, makeOrderedCopy(false), isDefault());
// This is a synchronous blocking operation, hence no
// need to deep copy the current PanelConfig instances.
@ -1239,6 +1275,16 @@ public final class HomeConfig {
return newConfigState;
}
/**
* Returns whether the {@code Editor} represents the default
* {@code HomeConfig} configuration without any unsaved changes.
*/
public boolean isDefault() {
ThreadUtils.assertOnThread(mOriginalThread);
return (!mHasChanged && mIsFromDefault);
}
public boolean isEmpty() {
return mConfigMap.isEmpty();
}
@ -1275,15 +1321,15 @@ public final class HomeConfig {
}
}
public interface OnChangeListener {
public void onChange();
public interface OnReloadListener {
public void onReload();
}
public interface HomeConfigBackend {
public List<PanelConfig> load();
public void save(List<PanelConfig> entries);
public State load();
public void save(State configState);
public String getLocale();
public void setOnChangeListener(OnChangeListener listener);
public void setOnReloadListener(OnReloadListener listener);
}
// UUIDs used to create PanelConfigs for default built-in panels
@ -1299,8 +1345,10 @@ public final class HomeConfig {
}
public State load() {
final List<PanelConfig> panelConfigs = mBackend.load();
return new State(this, panelConfigs);
final State configState = mBackend.load();
configState.setHomeConfig(this);
return configState;
}
public String getLocale() {
@ -1308,11 +1356,11 @@ public final class HomeConfig {
}
public void save(State configState) {
mBackend.save(configState.mPanelConfigs);
mBackend.save(configState);
}
public void setOnChangeListener(OnChangeListener listener) {
mBackend.setOnChangeListener(listener);
public void setOnReloadListener(OnReloadListener listener) {
mBackend.setOnReloadListener(listener);
}
public static PanelConfig createBuiltinPanelConfig(Context context, PanelType panelType) {

View File

@ -232,7 +232,12 @@ public class HomeConfigInvalidator implements GeckoEventListener {
}
}
if (shouldRefresh) {
// The editor still represents the default HomeConfig
// configuration and hasn't been changed by any operation
// above. No need to refresh as the HomeConfig backend will
// take of forcing all existing HomeConfigLoader instances to
// refresh their contents.
if (shouldRefresh && !editor.isDefault()) {
executeRefresh(editor);
}
}

View File

@ -6,7 +6,7 @@
package org.mozilla.gecko.home;
import org.mozilla.gecko.home.HomeConfig.PanelConfig;
import org.mozilla.gecko.home.HomeConfig.OnChangeListener;
import org.mozilla.gecko.home.HomeConfig.OnReloadListener;
import android.content.Context;
import android.support.v4.content.AsyncTaskLoader;
@ -17,8 +17,11 @@ public class HomeConfigLoader extends AsyncTaskLoader<HomeConfig.State> {
private final HomeConfig mConfig;
private HomeConfig.State mConfigState;
private final Context mContext;
public HomeConfigLoader(Context context, HomeConfig homeConfig) {
super(context);
mContext = context;
mConfig = homeConfig;
}
@ -35,7 +38,7 @@ public class HomeConfigLoader extends AsyncTaskLoader<HomeConfig.State> {
}
mConfigState = configState;
mConfig.setOnChangeListener(new ForceLoadChangeListener());
mConfig.setOnReloadListener(new ForceReloadListener());
if (isStarted()) {
super.deliverResult(configState);
@ -71,12 +74,12 @@ public class HomeConfigLoader extends AsyncTaskLoader<HomeConfig.State> {
onStopLoading();
mConfigState = null;
mConfig.setOnChangeListener(null);
mConfig.setOnReloadListener(null);
}
private class ForceLoadChangeListener implements OnChangeListener {
private class ForceReloadListener implements OnReloadListener {
@Override
public void onChange() {
public void onReload() {
onContentChanged();
}
}

View File

@ -16,15 +16,20 @@ import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.mozilla.gecko.home.HomeConfig.HomeConfigBackend;
import org.mozilla.gecko.home.HomeConfig.OnChangeListener;
import org.mozilla.gecko.home.HomeConfig.OnReloadListener;
import org.mozilla.gecko.home.HomeConfig.PanelConfig;
import org.mozilla.gecko.home.HomeConfig.PanelType;
import org.mozilla.gecko.home.HomeConfig.State;
import org.mozilla.gecko.util.HardwareUtils;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
import android.preference.PreferenceManager;
import android.support.v4.content.LocalBroadcastManager;
import android.text.TextUtils;
import android.util.Log;
@ -34,9 +39,11 @@ class HomeConfigPrefsBackend implements HomeConfigBackend {
private static final String PREFS_CONFIG_KEY = "home_panels";
private static final String PREFS_LOCALE_KEY = "home_locale";
private static final String RELOAD_BROADCAST = "HomeConfigPrefsBackend:Reload";
private final Context mContext;
private PrefsListener mPrefsListener;
private OnChangeListener mChangeListener;
private ReloadBroadcastReceiver mReloadBroadcastReceiver;
private OnReloadListener mReloadListener;
public HomeConfigPrefsBackend(Context context) {
mContext = context;
@ -46,7 +53,7 @@ class HomeConfigPrefsBackend implements HomeConfigBackend {
return PreferenceManager.getDefaultSharedPreferences(mContext);
}
private List<PanelConfig> loadDefaultConfig() {
private State loadDefaultConfig() {
final ArrayList<PanelConfig> panelConfigs = new ArrayList<PanelConfig>();
panelConfigs.add(createBuiltinPanelConfig(mContext, PanelType.TOP_SITES,
@ -70,10 +77,10 @@ class HomeConfigPrefsBackend implements HomeConfigBackend {
panelConfigs.add(0, historyEntry);
}
return panelConfigs;
return new State(panelConfigs, true);
}
private List<PanelConfig> loadConfigFromString(String jsonString) {
private State loadConfigFromString(String jsonString) {
final JSONArray jsonPanelConfigs;
try {
jsonPanelConfigs = new JSONArray(jsonString);
@ -97,46 +104,52 @@ class HomeConfigPrefsBackend implements HomeConfigBackend {
}
}
return panelConfigs;
return new State(panelConfigs, false);
}
@Override
public List<PanelConfig> load() {
public State load() {
final SharedPreferences prefs = getSharedPreferences();
final String jsonString = prefs.getString(PREFS_CONFIG_KEY, null);
final List<PanelConfig> panelConfigs;
final State configState;
if (TextUtils.isEmpty(jsonString)) {
panelConfigs = loadDefaultConfig();
configState = loadDefaultConfig();
} else {
panelConfigs = loadConfigFromString(jsonString);
configState = loadConfigFromString(jsonString);
}
return panelConfigs;
return configState;
}
@Override
public void save(List<PanelConfig> panelConfigs) {
final JSONArray jsonPanelConfigs = new JSONArray();
final int count = panelConfigs.size();
for (int i = 0; i < count; i++) {
try {
final PanelConfig panelConfig = panelConfigs.get(i);
final JSONObject jsonPanelConfig = panelConfig.toJSON();
jsonPanelConfigs.put(jsonPanelConfig);
} catch (Exception e) {
Log.e(LOGTAG, "Exception converting PanelConfig to JSON", e);
}
}
public void save(State configState) {
final SharedPreferences prefs = getSharedPreferences();
final SharedPreferences.Editor editor = prefs.edit();
final String jsonString = jsonPanelConfigs.toString();
editor.putString(PREFS_CONFIG_KEY, jsonString);
// No need to save the state to disk if it represents the default
// HomeConfig configuration. Simply force all existing HomeConfigLoader
// instances to refresh their contents.
if (!configState.isDefault()) {
final JSONArray jsonPanelConfigs = new JSONArray();
for (PanelConfig panelConfig : configState) {
try {
final JSONObject jsonPanelConfig = panelConfig.toJSON();
jsonPanelConfigs.put(jsonPanelConfig);
} catch (Exception e) {
Log.e(LOGTAG, "Exception converting PanelConfig to JSON", e);
}
}
editor.putString(PREFS_CONFIG_KEY, jsonPanelConfigs.toString());
}
editor.putString(PREFS_LOCALE_KEY, Locale.getDefault().toString());
editor.commit();
// Trigger reload listeners on all live backend instances
sendReloadBroadcast();
}
@Override
@ -165,28 +178,40 @@ class HomeConfigPrefsBackend implements HomeConfigBackend {
}
@Override
public void setOnChangeListener(OnChangeListener listener) {
final SharedPreferences prefs = getSharedPreferences();
if (mChangeListener != null) {
prefs.unregisterOnSharedPreferenceChangeListener(mPrefsListener);
mPrefsListener = null;
public void setOnReloadListener(OnReloadListener listener) {
if (mReloadListener != null) {
unregisterReloadReceiver();
mReloadBroadcastReceiver = null;
}
mChangeListener = listener;
mReloadListener = listener;
if (mChangeListener != null) {
mPrefsListener = new PrefsListener();
prefs.registerOnSharedPreferenceChangeListener(mPrefsListener);
if (mReloadListener != null) {
mReloadBroadcastReceiver = new ReloadBroadcastReceiver();
registerReloadReceiver();
}
}
private class PrefsListener implements OnSharedPreferenceChangeListener {
private void sendReloadBroadcast() {
final LocalBroadcastManager lbm = LocalBroadcastManager.getInstance(mContext);
final Intent reloadIntent = new Intent(RELOAD_BROADCAST);
lbm.sendBroadcast(reloadIntent);
}
private void registerReloadReceiver() {
final LocalBroadcastManager lbm = LocalBroadcastManager.getInstance(mContext);
lbm.registerReceiver(mReloadBroadcastReceiver, new IntentFilter(RELOAD_BROADCAST));
}
private void unregisterReloadReceiver() {
final LocalBroadcastManager lbm = LocalBroadcastManager.getInstance(mContext);
lbm.unregisterReceiver(mReloadBroadcastReceiver);
}
private class ReloadBroadcastReceiver extends BroadcastReceiver {
@Override
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
if (TextUtils.equals(key, PREFS_CONFIG_KEY)) {
mChangeListener.onChange();
}
public void onReceive(Context context, Intent intent) {
mReloadListener.onReload();
}
}
}

View File

@ -203,7 +203,6 @@ abstract class HomeFragment extends Fragment {
@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
mIsLoaded = false;
}
void setCanLoadHint(boolean canLoadHint) {

View File

@ -1,23 +1,18 @@
package org.mozilla.gecko.tests;
import org.mozilla.gecko.*;
import org.mozilla.gecko.tests.helpers.JavascriptMessageParser;
import android.util.Log;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import junit.framework.AssertionFailedError;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
public class JavascriptTest extends BaseTest {
public static final String LOGTAG = "JavascriptTest";
private static final String LOGTAG = "JavascriptTest";
private static final String EVENT_TYPE = "Robocop:JS";
public final String javascriptUrl;
private final String javascriptUrl;
public JavascriptTest(String javascriptUrl) {
super();
@ -29,119 +24,51 @@ public class JavascriptTest extends BaseTest {
return TEST_MOCHITEST;
}
/**
* Route messages from Javascript's head.js test framework into Java's
* Mochitest framework.
*/
protected static class JavascriptTestMessageParser {
// Messages matching this pattern are handled specially. Messages not
// matching this pattern are still printed.
private static final Pattern testMessagePattern =
Pattern.compile("\n+TEST-(.*) \\| (.*) \\| (.*)\n*");
private final Assert mAsserter;
// Used to help print stack traces neatly.
private String lastTestName = "";
// Have we seen a message saying the test is finished?
private boolean testFinishedMessageSeen = false;
public JavascriptTestMessageParser(final Assert asserter) {
this.mAsserter = asserter;
}
private boolean testIsFinished() {
return testFinishedMessageSeen;
}
private void logMessage(String str) {
Matcher m = testMessagePattern.matcher(str);
if (m.matches()) {
String type = m.group(1);
String name = m.group(2);
String message = m.group(3);
if ("INFO".equals(type)) {
mAsserter.info(name, message);
testFinishedMessageSeen = testFinishedMessageSeen ||
"exiting test".equals(message);
} else if ("PASS".equals(type)) {
mAsserter.ok(true, name, message);
} else if ("UNEXPECTED-FAIL".equals(type)) {
try {
mAsserter.ok(false, name, message);
} catch (junit.framework.AssertionFailedError e) {
// Swallow this exception. We want to see all the
// Javascript failures, not die on the very first one!
}
} else if ("KNOWN-FAIL".equals(type)) {
mAsserter.todo(false, name, message);
} else if ("UNEXPECTED-PASS".equals(type)) {
mAsserter.todo(true, name, message);
}
lastTestName = name;
} else {
// Generally, these extra lines are stack traces from failures,
// so we print them with the name of the last test seen.
mAsserter.info(lastTestName, str.trim());
}
}
}
public void testJavascript() throws Exception {
blockForGeckoReady();
// We want to be waiting for Robocop messages before the page is loaded
// because the test harness runs each test in the suite (and possibly
// completes testing) before the page load event is fired.
final Actions.EventExpecter expecter = mActions.expectGeckoEvent("Robocop:Status");
mAsserter.dumpLog("Registered listener for Robocop:Status");
final Actions.EventExpecter expecter =
mActions.expectGeckoEvent(EVENT_TYPE);
mAsserter.dumpLog("Registered listener for " + EVENT_TYPE);
final String url = getAbsoluteUrl("/robocop/robocop_javascript.html?path=" + javascriptUrl);
final String url = getAbsoluteUrl(StringHelper.ROBOCOP_JS_HARNESS_URL +
"?path=" + javascriptUrl);
mAsserter.dumpLog("Loading JavaScript test from " + url);
loadUrl(url);
final JavascriptTestMessageParser testMessageParser =
new JavascriptTestMessageParser(mAsserter);
final JavascriptMessageParser testMessageParser = new JavascriptMessageParser(mAsserter);
try {
while (true) {
while (!testMessageParser.isTestFinished()) {
if (Log.isLoggable(LOGTAG, Log.VERBOSE)) {
Log.v(LOGTAG, "Waiting for Robocop:Status");
Log.v(LOGTAG, "Waiting for " + EVENT_TYPE);
}
String data = expecter.blockForEventData();
if (Log.isLoggable(LOGTAG, Log.VERBOSE)) {
Log.v(LOGTAG, "Got Robocop:Status with data '" + data + "'");
Log.v(LOGTAG, "Got event with data '" + data + "'");
}
JSONObject o = new JSONObject(data);
String innerType = o.getString("innerType");
if (!"progress".equals(innerType)) {
throw new Exception("Unexpected Robocop:Status innerType " + innerType);
throw new Exception("Unexpected event innerType " + innerType);
}
String message = o.getString("message");
if (message == null) {
throw new Exception("Robocop:Status progress message must not be null");
throw new Exception("Progress message must not be null");
}
testMessageParser.logMessage(message);
}
if (testMessageParser.testIsFinished()) {
if (Log.isLoggable(LOGTAG, Log.DEBUG)) {
Log.d(LOGTAG, "Got test finished message");
}
break;
}
if (Log.isLoggable(LOGTAG, Log.DEBUG)) {
Log.d(LOGTAG, "Got test finished message");
}
} finally {
expecter.unregisterListener();
mAsserter.dumpLog("Unregistered listener for Robocop:Status");
mAsserter.dumpLog("Unregistered listener for " + EVENT_TYPE);
}
}
}

View File

@ -77,6 +77,7 @@ public class StringHelper {
public static final String ROBOCOP_TEXT_PAGE_URL = "/robocop/robocop_text_page.html";
public static final String ROBOCOP_ADOBE_FLASH_URL = "/robocop/robocop_adobe_flash.html";
public static final String ROBOCOP_INPUT_URL = "/robocop/robocop_input.html";
public static final String ROBOCOP_JS_HARNESS_URL = "/robocop/robocop_javascript.html";
// Robocop page titles
public static final String ROBOCOP_BIG_LINK_TITLE = "Big Link";

View File

@ -0,0 +1,79 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mozilla.gecko.tests.helpers;
import org.mozilla.gecko.Assert;
import junit.framework.AssertionFailedError;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Route messages from Javascript's head.js test framework into Java's
* Mochitest framework.
*/
public final class JavascriptMessageParser {
/**
* The Javascript test harness sends test events to Java.
* Each such test event is wrapped in a Robocop:JS event.
*/
public static final String EVENT_TYPE = "Robocop:JS";
// Messages matching this pattern are handled specially. Messages not
// matching this pattern are still printed.
private static final Pattern testMessagePattern =
Pattern.compile("\n+TEST-(.*) \\| (.*) \\| (.*)\n*");
private final Assert asserter;
// Used to help print stack traces neatly.
private String lastTestName = "";
// Have we seen a message saying the test is finished?
private boolean testFinishedMessageSeen = false;
public JavascriptMessageParser(final Assert asserter) {
this.asserter = asserter;
}
public boolean isTestFinished() {
return testFinishedMessageSeen;
}
public void logMessage(final String str) {
final Matcher m = testMessagePattern.matcher(str);
if (m.matches()) {
final String type = m.group(1);
final String name = m.group(2);
final String message = m.group(3);
if ("INFO".equals(type)) {
asserter.info(name, message);
testFinishedMessageSeen = testFinishedMessageSeen ||
"exiting test".equals(message);
} else if ("PASS".equals(type)) {
asserter.ok(true, name, message);
} else if ("UNEXPECTED-FAIL".equals(type)) {
try {
asserter.ok(false, name, message);
} catch (AssertionFailedError e) {
// Swallow this exception. We want to see all the
// Javascript failures, not die on the very first one!
}
} else if ("KNOWN-FAIL".equals(type)) {
asserter.todo(false, name, message);
} else if ("UNEXPECTED-PASS".equals(type)) {
asserter.todo(true, name, message);
}
lastTestName = name;
} else {
// Generally, these extra lines are stack traces from failures,
// so we print them with the name of the last test seen.
asserter.info(lastTestName, str.trim());
}
}
}

View File

@ -36,7 +36,7 @@ function _evalURI(uri, sandbox) {
* absolute.
*
* The Javascript test harness sends all output to Java via
* Robocop:Status messages.
* Robocop:JS messages.
*/
function testOneFile(uri) {
let HEAD_JS = "robocop_head.js";
@ -59,7 +59,7 @@ function testOneFile(uri) {
// Output from head.js is fed, line by line, to this function. We
// send any such output back to the Java Robocop harness.
testScope.dump = function (str) {
let message = { type: "Robocop:Status",
let message = { type: "Robocop:JS",
innerType: "progress",
message: str,
};

Some files were not shown because too many files have changed in this diff Show More