Merge m-c to inbound
2
CLOBBER
@ -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
|
||||
|
@ -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"/>
|
||||
|
@ -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"/>
|
||||
|
@ -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"/>
|
||||
|
@ -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"/>
|
||||
|
@ -4,6 +4,6 @@
|
||||
"remote": "",
|
||||
"branch": ""
|
||||
},
|
||||
"revision": "6c593455e3d1292120a6f3d41ec5d06bc91019f1",
|
||||
"revision": "f3575a1613e6c94fbc6b2ae01fd00130ee1b3f8a",
|
||||
"repo_path": "/integration/gaia-central"
|
||||
}
|
||||
|
@ -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"/>
|
||||
|
@ -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"/>
|
||||
|
@ -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"/>
|
||||
|
@ -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"/>
|
||||
|
@ -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"/>
|
||||
|
@ -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"/>
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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 */
|
||||
|
||||
/*
|
||||
|
@ -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");
|
||||
|
||||
|
@ -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, {
|
||||
|
@ -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);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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]
|
||||
|
53
browser/base/content/test/newtab/browser_newtab_update.js
Normal 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 };
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -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>
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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]
|
||||
|
@ -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);
|
||||
});
|
@ -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")) {
|
||||
|
@ -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)
|
||||
|
@ -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>
|
||||
|
@ -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();"/>
|
||||
|
@ -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");
|
||||
|
23
browser/devtools/debugger/test/addon-source/browser_dbg_addon5/bootstrap.js
vendored
Normal 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);
|
||||
}
|
@ -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>
|
@ -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 = {};
|
@ -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 = {};
|
BIN
browser/devtools/debugger/test/addon5.xpi
Normal 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]
|
||||
|
@ -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();
|
||||
}
|
||||
});
|
@ -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();
|
||||
}
|
||||
});
|
||||
|
@ -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();
|
||||
}
|
||||
});
|
||||
|
@ -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;"
|
||||
|
@ -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();
|
||||
|
@ -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,
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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]
|
||||
|
@ -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);
|
||||
});
|
||||
}
|
@ -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";
|
||||
|
@ -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() {
|
||||
|
34
browser/devtools/netmonitor/test/html_single-get-page.html
Normal 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>
|
@ -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, "\"^$&\"") + "\"";
|
||||
}
|
||||
};
|
||||
};
|
||||
|
@ -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() {
|
||||
|
@ -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
|
||||
|
@ -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>
|
||||
|
||||
|
@ -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;
|
||||
|
@ -1,4 +1,4 @@
|
||||
.relative {
|
||||
.relative1 {
|
||||
background-image: url(../test-image.png);
|
||||
}
|
||||
.absolute {
|
||||
|
@ -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);
|
||||
|
17
browser/experiments/Makefile.in
Normal 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
|
16
browser/experiments/test/addons/experiment-1/install.rdf
Normal 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>
|
16
browser/experiments/test/addons/experiment-1a/install.rdf
Normal 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>
|
16
browser/experiments/test/addons/experiment-2/install.rdf
Normal 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>
|
@ -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";
|
||||
|
@ -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]
|
||||
|
@ -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"
|
||||
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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">
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
|
Before Width: | Height: | Size: 4.1 KiB |
@ -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;
|
||||
}
|
||||
|
@ -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)
|
||||
|
Before Width: | Height: | Size: 4.6 KiB |
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 22 KiB |
@ -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;
|
||||
}
|
||||
|
BIN
browser/themes/shared/devtools/images/metal.jpg
Normal file
After Width: | Height: | Size: 111 KiB |
@ -135,6 +135,7 @@
|
||||
color: #585959;
|
||||
background-color: #f0f1f2;
|
||||
border-color: #aaa;
|
||||
background-image: url("chrome://browser/skin/devtools/metal.jpg");
|
||||
}
|
||||
|
||||
.ruleview-colorswatch,
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
BIN
browser/themes/shared/newtab/controls.png
Normal file
After Width: | Height: | Size: 7.2 KiB |
@ -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;
|
||||
|
@ -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)
|
||||
|
Before Width: | Height: | Size: 4.1 KiB |
@ -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;
|
||||
}
|
||||
|
@ -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");
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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) {
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -203,7 +203,6 @@ abstract class HomeFragment extends Fragment {
|
||||
@Override
|
||||
public void onConfigurationChanged(Configuration newConfig) {
|
||||
super.onConfigurationChanged(newConfig);
|
||||
mIsLoaded = false;
|
||||
}
|
||||
|
||||
void setCanLoadHint(boolean canLoadHint) {
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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";
|
||||
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
@ -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,
|
||||
};
|
||||
|