зеркало из https://github.com/mozilla/gecko-dev.git
merge fx-team to mozilla-central a=merge
--HG-- extra : amend_source : ad367ce5c609bdeabd41e252d379aae9bee81e04
This commit is contained in:
Коммит
26fa2ff692
|
@ -696,6 +696,9 @@ html|*#fullscreen-warning[ontop] {
|
|||
/* Use -10px to hide the border and border-radius on the top */
|
||||
transform: translate(-50%, -10px);
|
||||
}
|
||||
#main-window[OSXLionFullscreen] html|*#fullscreen-warning[ontop] {
|
||||
transform: translate(-50%, 80px);
|
||||
}
|
||||
|
||||
html|*#fullscreen-domain-text,
|
||||
html|*#fullscreen-generic-text {
|
||||
|
|
|
@ -2376,25 +2376,36 @@ function URLBarSetURI(aURI) {
|
|||
}
|
||||
|
||||
function losslessDecodeURI(aURI) {
|
||||
if (aURI.schemeIs("moz-action"))
|
||||
let scheme = aURI.scheme;
|
||||
if (scheme == "moz-action")
|
||||
throw new Error("losslessDecodeURI should never get a moz-action URI");
|
||||
|
||||
var value = aURI.spec;
|
||||
|
||||
let decodeASCIIOnly = !["https", "http", "file", "ftp"].includes(scheme);
|
||||
// Try to decode as UTF-8 if there's no encoding sequence that we would break.
|
||||
if (!/%25(?:3B|2F|3F|3A|40|26|3D|2B|24|2C|23)/i.test(value))
|
||||
try {
|
||||
value = decodeURI(value)
|
||||
// 1. decodeURI decodes %25 to %, which creates unintended
|
||||
// encoding sequences. Re-encode it, unless it's part of
|
||||
// a sequence that survived decodeURI, i.e. one for:
|
||||
// ';', '/', '?', ':', '@', '&', '=', '+', '$', ',', '#'
|
||||
// (RFC 3987 section 3.2)
|
||||
// 2. Re-encode whitespace so that it doesn't get eaten away
|
||||
// by the location bar (bug 410726).
|
||||
.replace(/%(?!3B|2F|3F|3A|40|26|3D|2B|24|2C|23)|[\r\n\t]/ig,
|
||||
encodeURIComponent);
|
||||
} catch (e) {}
|
||||
if (!/%25(?:3B|2F|3F|3A|40|26|3D|2B|24|2C|23)/i.test(value)) {
|
||||
if (decodeASCIIOnly) {
|
||||
// This only decodes ascii characters (hex) 20-7e, except 25 (%).
|
||||
// This avoids both cases stipulated below (%-related issues, and \r, \n
|
||||
// and \t, which would be %0d, %0a and %09, respectively) as well as any
|
||||
// non-US-ascii characters.
|
||||
value = value.replace(/%(2[0-4]|2[6-9a-f]|[3-6][0-9a-f]|7[0-9a-e])/g, decodeURI);
|
||||
} else {
|
||||
try {
|
||||
value = decodeURI(value)
|
||||
// 1. decodeURI decodes %25 to %, which creates unintended
|
||||
// encoding sequences. Re-encode it, unless it's part of
|
||||
// a sequence that survived decodeURI, i.e. one for:
|
||||
// ';', '/', '?', ':', '@', '&', '=', '+', '$', ',', '#'
|
||||
// (RFC 3987 section 3.2)
|
||||
// 2. Re-encode whitespace so that it doesn't get eaten away
|
||||
// by the location bar (bug 410726).
|
||||
.replace(/%(?!3B|2F|3F|3A|40|26|3D|2B|24|2C|23)|[\r\n\t]/ig,
|
||||
encodeURIComponent);
|
||||
} catch (e) {}
|
||||
}
|
||||
}
|
||||
|
||||
// Encode invisible characters (C0/C1 control characters, U+007F [DEL],
|
||||
// U+00A0 [no-break space], line and paragraph separator,
|
||||
|
|
|
@ -120,27 +120,27 @@ var tests = [
|
|||
|
||||
// data: and javsacript: URIs shouldn't be encoded
|
||||
{
|
||||
loadURL: "javascript:('%C3%A9')",
|
||||
expectedURL: "javascript:('\xe9')",
|
||||
copyExpected: "javascript:('\xe9')"
|
||||
loadURL: "javascript:('%C3%A9%20%25%50')",
|
||||
expectedURL: "javascript:('%C3%A9 %25P')",
|
||||
copyExpected: "javascript:('%C3%A9 %25P')"
|
||||
},
|
||||
{
|
||||
copyVal: "<javascript:(>'\xe9')",
|
||||
copyVal: "<javascript:(>'%C3%A9 %25P')",
|
||||
copyExpected: "javascript:("
|
||||
},
|
||||
|
||||
{
|
||||
loadURL: "data:text/html,(%C3%A9)",
|
||||
expectedURL: "data:text/html,(\xe9)",
|
||||
copyExpected: "data:text/html,(\xe9)"
|
||||
loadURL: "data:text/html,(%C3%A9%20%25%50)",
|
||||
expectedURL: "data:text/html,(%C3%A9 %25P)",
|
||||
copyExpected: "data:text/html,(%C3%A9 %25P)",
|
||||
},
|
||||
{
|
||||
copyVal: "<data:text/html,(>\xe9)",
|
||||
copyVal: "<data:text/html,(>%C3%A9 %25P)",
|
||||
copyExpected: "data:text/html,("
|
||||
},
|
||||
{
|
||||
copyVal: "data:<text/html,(\xe9>)",
|
||||
copyExpected: "text/html,(\xe9"
|
||||
copyVal: "<data:text/html,(%C3%A9 %25P>)",
|
||||
copyExpected: "data:text/html,(%C3%A9 %25P",
|
||||
}
|
||||
];
|
||||
|
||||
|
|
|
@ -475,11 +475,13 @@ const CustomizableWidgets = [
|
|||
_createTabElement(doc, tabInfo) {
|
||||
let win = doc.defaultView;
|
||||
let item = doc.createElementNS(kNSXUL, "toolbarbutton");
|
||||
let tooltipText = (tabInfo.title ? tabInfo.title + "\n" : "") + tabInfo.url;
|
||||
item.setAttribute("itemtype", "tab");
|
||||
item.setAttribute("class", "subviewbutton");
|
||||
item.setAttribute("targetURI", tabInfo.url);
|
||||
item.setAttribute("label", tabInfo.title != "" ? tabInfo.title : tabInfo.url);
|
||||
item.setAttribute("image", tabInfo.icon);
|
||||
item.setAttribute("tooltiptext", tooltipText);
|
||||
// We need to use "click" instead of "command" here so openUILink
|
||||
// respects different buttons (eg, to open in a new tab).
|
||||
item.addEventListener("click", e => {
|
||||
|
|
|
@ -234,15 +234,16 @@ CustomizeMode.prototype = {
|
|||
Task.spawn(function*() {
|
||||
// We shouldn't start customize mode until after browser-delayed-startup has finished:
|
||||
if (!this.window.gBrowserInit.delayedStartupFinished) {
|
||||
let delayedStartupDeferred = Promise.defer();
|
||||
let delayedStartupObserver = function(aSubject) {
|
||||
if (aSubject == this.window) {
|
||||
Services.obs.removeObserver(delayedStartupObserver, "browser-delayed-startup-finished");
|
||||
delayedStartupDeferred.resolve();
|
||||
}
|
||||
}.bind(this);
|
||||
Services.obs.addObserver(delayedStartupObserver, "browser-delayed-startup-finished", false);
|
||||
yield delayedStartupDeferred.promise;
|
||||
yield new Promise(resolve => {
|
||||
let delayedStartupObserver = aSubject => {
|
||||
if (aSubject == this.window) {
|
||||
Services.obs.removeObserver(delayedStartupObserver, "browser-delayed-startup-finished");
|
||||
resolve();
|
||||
}
|
||||
};
|
||||
|
||||
Services.obs.addObserver(delayedStartupObserver, "browser-delayed-startup-finished", false);
|
||||
});
|
||||
}
|
||||
|
||||
let toolbarVisibilityBtn = document.getElementById(kToolbarVisibilityBtn);
|
||||
|
@ -562,33 +563,35 @@ CustomizeMode.prototype = {
|
|||
* excluding certain styles while in any phase of customize mode.
|
||||
*/
|
||||
_doTransition: function(aEntering) {
|
||||
let deferred = Promise.defer();
|
||||
let deck = this.document.getElementById("content-deck");
|
||||
|
||||
let customizeTransitionEnd = (aEvent) => {
|
||||
if (aEvent != "timedout" &&
|
||||
(aEvent.originalTarget != deck || aEvent.propertyName != "margin-left")) {
|
||||
return;
|
||||
}
|
||||
this.window.clearTimeout(catchAllTimeout);
|
||||
// We request an animation frame to do the final stage of the transition
|
||||
// to improve perceived performance. (bug 962677)
|
||||
this.window.requestAnimationFrame(() => {
|
||||
deck.removeEventListener("transitionend", customizeTransitionEnd);
|
||||
|
||||
if (!aEntering) {
|
||||
this.document.documentElement.removeAttribute("customize-exiting");
|
||||
this.document.documentElement.removeAttribute("customizing");
|
||||
} else {
|
||||
this.document.documentElement.setAttribute("customize-entered", true);
|
||||
this.document.documentElement.removeAttribute("customize-entering");
|
||||
let customizeTransitionEndPromise = new Promise(resolve => {
|
||||
let customizeTransitionEnd = (aEvent) => {
|
||||
if (aEvent != "timedout" &&
|
||||
(aEvent.originalTarget != deck || aEvent.propertyName != "margin-left")) {
|
||||
return;
|
||||
}
|
||||
CustomizableUI.dispatchToolboxEvent("customization-transitionend", aEntering, this.window);
|
||||
this.window.clearTimeout(catchAllTimeout);
|
||||
// We request an animation frame to do the final stage of the transition
|
||||
// to improve perceived performance. (bug 962677)
|
||||
this.window.requestAnimationFrame(() => {
|
||||
deck.removeEventListener("transitionend", customizeTransitionEnd);
|
||||
|
||||
deferred.resolve();
|
||||
});
|
||||
};
|
||||
deck.addEventListener("transitionend", customizeTransitionEnd);
|
||||
if (!aEntering) {
|
||||
this.document.documentElement.removeAttribute("customize-exiting");
|
||||
this.document.documentElement.removeAttribute("customizing");
|
||||
} else {
|
||||
this.document.documentElement.setAttribute("customize-entered", true);
|
||||
this.document.documentElement.removeAttribute("customize-entering");
|
||||
}
|
||||
CustomizableUI.dispatchToolboxEvent("customization-transitionend", aEntering, this.window);
|
||||
|
||||
resolve();
|
||||
});
|
||||
};
|
||||
deck.addEventListener("transitionend", customizeTransitionEnd);
|
||||
let catchAll = () => customizeTransitionEnd("timedout");
|
||||
let catchAllTimeout = this.window.setTimeout(catchAll, kMaxTransitionDurationMs);
|
||||
});
|
||||
|
||||
if (gDisableAnimation) {
|
||||
this.document.getElementById("tab-view-deck").setAttribute("fastcustomizeanimation", true);
|
||||
|
@ -602,9 +605,7 @@ CustomizeMode.prototype = {
|
|||
this.document.documentElement.removeAttribute("customize-entered");
|
||||
}
|
||||
|
||||
let catchAll = () => customizeTransitionEnd("timedout");
|
||||
let catchAllTimeout = this.window.setTimeout(catchAll, kMaxTransitionDurationMs);
|
||||
return deferred.promise;
|
||||
return customizeTransitionEndPromise;
|
||||
},
|
||||
|
||||
updateLWTStyling: function(aData) {
|
||||
|
@ -868,14 +869,12 @@ CustomizeMode.prototype = {
|
|||
},
|
||||
|
||||
deferredWrapToolbarItem: function(aNode, aPlace) {
|
||||
let deferred = Promise.defer();
|
||||
|
||||
dispatchFunction(function() {
|
||||
let wrapper = this.wrapToolbarItem(aNode, aPlace);
|
||||
deferred.resolve(wrapper);
|
||||
}.bind(this));
|
||||
|
||||
return deferred.promise;
|
||||
return new Promise(resolve => {
|
||||
dispatchFunction(() => {
|
||||
let wrapper = this.wrapToolbarItem(aNode, aPlace);
|
||||
resolve(wrapper);
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
wrapToolbarItem: function(aNode, aPlace) {
|
||||
|
@ -985,17 +984,17 @@ CustomizeMode.prototype = {
|
|||
},
|
||||
|
||||
deferredUnwrapToolbarItem: function(aWrapper) {
|
||||
let deferred = Promise.defer();
|
||||
dispatchFunction(function() {
|
||||
let item = null;
|
||||
try {
|
||||
item = this.unwrapToolbarItem(aWrapper);
|
||||
} catch (ex) {
|
||||
Cu.reportError(ex);
|
||||
}
|
||||
deferred.resolve(item);
|
||||
}.bind(this));
|
||||
return deferred.promise;
|
||||
return new Promise(resolve => {
|
||||
dispatchFunction(() => {
|
||||
let item = null;
|
||||
try {
|
||||
item = this.unwrapToolbarItem(aWrapper);
|
||||
} catch (ex) {
|
||||
Cu.reportError(ex);
|
||||
}
|
||||
resolve(item);
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
unwrapToolbarItem: function(aWrapper) {
|
||||
|
|
|
@ -2223,7 +2223,7 @@ BrowserGlue.prototype = {
|
|||
// be set to the version it has been added in. We will compare its value
|
||||
// to users' smartBookmarksVersion and add new smart bookmarks without
|
||||
// recreating old deleted ones.
|
||||
const SMART_BOOKMARKS_VERSION = 7;
|
||||
const SMART_BOOKMARKS_VERSION = 8;
|
||||
const SMART_BOOKMARKS_ANNO = "Places/SmartBookmark";
|
||||
const SMART_BOOKMARKS_PREF = "browser.places.smartBookmarksVersion";
|
||||
|
||||
|
@ -2256,18 +2256,6 @@ BrowserGlue.prototype = {
|
|||
parentGuid: PlacesUtils.bookmarks.toolbarGuid,
|
||||
newInVersion: 1
|
||||
},
|
||||
RecentlyBookmarked: {
|
||||
title: bundle.GetStringFromName("recentlyBookmarkedTitle"),
|
||||
url: "place:folder=BOOKMARKS_MENU" +
|
||||
"&folder=UNFILED_BOOKMARKS" +
|
||||
"&folder=TOOLBAR" +
|
||||
"&queryType=" + queryOptions.QUERY_TYPE_BOOKMARKS +
|
||||
"&sort=" + queryOptions.SORT_BY_DATEADDED_DESCENDING +
|
||||
"&maxResults=" + MAX_RESULTS +
|
||||
"&excludeQueries=1",
|
||||
parentGuid: PlacesUtils.bookmarks.menuGuid,
|
||||
newInVersion: 1
|
||||
},
|
||||
RecentTags: {
|
||||
title: bundle.GetStringFromName("recentTagsTitle"),
|
||||
url: "place:type=" + queryOptions.RESULTS_AS_TAG_QUERY +
|
||||
|
|
|
@ -38,9 +38,9 @@ updateAppInfo({
|
|||
});
|
||||
|
||||
// Smart bookmarks constants.
|
||||
const SMART_BOOKMARKS_VERSION = 7;
|
||||
const SMART_BOOKMARKS_VERSION = 8;
|
||||
const SMART_BOOKMARKS_ON_TOOLBAR = 1;
|
||||
const SMART_BOOKMARKS_ON_MENU = 3; // Takes into account the additional separator.
|
||||
const SMART_BOOKMARKS_ON_MENU = 2; // Takes into account the additional separator.
|
||||
|
||||
// Default bookmarks constants.
|
||||
const DEFAULT_BOOKMARKS_ON_TOOLBAR = 1;
|
||||
|
|
|
@ -142,13 +142,6 @@ add_task(function* test_version_change_pos() {
|
|||
yield checkItemHasAnnotation(bm.guid, SMART_BOOKMARKS_ANNO);
|
||||
let firstItemTitle = bm.title;
|
||||
|
||||
bm = yield PlacesUtils.bookmarks.fetch({
|
||||
parentGuid: PlacesUtils.bookmarks.menuGuid,
|
||||
index: 1
|
||||
});
|
||||
yield checkItemHasAnnotation(bm.guid, SMART_BOOKMARKS_ANNO);
|
||||
let secondItemTitle = bm.title;
|
||||
|
||||
// Set preferences.
|
||||
Services.prefs.setIntPref(PREF_SMART_BOOKMARKS_VERSION, 1);
|
||||
|
||||
|
@ -168,13 +161,6 @@ add_task(function* test_version_change_pos() {
|
|||
yield checkItemHasAnnotation(bm.guid, SMART_BOOKMARKS_ANNO);
|
||||
Assert.equal(bm.title, firstItemTitle);
|
||||
|
||||
bm = yield PlacesUtils.bookmarks.fetch({
|
||||
parentGuid: PlacesUtils.bookmarks.menuGuid,
|
||||
index: 1
|
||||
});
|
||||
yield checkItemHasAnnotation(bm.guid, SMART_BOOKMARKS_ANNO);
|
||||
Assert.equal(bm.title, secondItemTitle);
|
||||
|
||||
// Check version has been updated.
|
||||
Assert.equal(Services.prefs.getIntPref(PREF_SMART_BOOKMARKS_VERSION),
|
||||
SMART_BOOKMARKS_VERSION);
|
||||
|
@ -196,13 +182,6 @@ add_task(function* test_version_change_pos_moved() {
|
|||
yield checkItemHasAnnotation(bm1.guid, SMART_BOOKMARKS_ANNO);
|
||||
let firstItemTitle = bm1.title;
|
||||
|
||||
let bm2 = yield PlacesUtils.bookmarks.fetch({
|
||||
parentGuid: PlacesUtils.bookmarks.menuGuid,
|
||||
index: 1
|
||||
});
|
||||
yield checkItemHasAnnotation(bm2.guid, SMART_BOOKMARKS_ANNO);
|
||||
let secondItemTitle = bm2.title;
|
||||
|
||||
// Move the first smart bookmark to the end of the menu.
|
||||
yield PlacesUtils.bookmarks.update({
|
||||
parentGuid: PlacesUtils.bookmarks.menuGuid,
|
||||
|
@ -227,14 +206,6 @@ add_task(function* test_version_change_pos_moved() {
|
|||
Assert.equal(countFolderChildren(PlacesUtils.bookmarksMenuFolderId),
|
||||
SMART_BOOKMARKS_ON_MENU + DEFAULT_BOOKMARKS_ON_MENU);
|
||||
|
||||
// Check smart bookmarks are still in correct position.
|
||||
bm2 = yield PlacesUtils.bookmarks.fetch({
|
||||
parentGuid: PlacesUtils.bookmarks.menuGuid,
|
||||
index: 0
|
||||
});
|
||||
yield checkItemHasAnnotation(bm2.guid, SMART_BOOKMARKS_ANNO);
|
||||
Assert.equal(bm2.title, secondItemTitle);
|
||||
|
||||
bm1 = yield PlacesUtils.bookmarks.fetch({
|
||||
parentGuid: PlacesUtils.bookmarks.menuGuid,
|
||||
index: PlacesUtils.bookmarks.DEFAULT_INDEX
|
||||
|
|
|
@ -10,6 +10,9 @@ MOZ_AUTOMATION_L10N_CHECK=0
|
|||
# Needed to enable breakpad in application.ini
|
||||
export MOZILLA_OFFICIAL=1
|
||||
|
||||
# Enable Telemetry
|
||||
export MOZ_TELEMETRY_REPORTING=1
|
||||
|
||||
#Use ccache
|
||||
|
||||
# Treat warnings as errors (modulo ALLOW_COMPILER_WARNINGS).
|
||||
|
|
|
@ -13,6 +13,9 @@ ac_add_options --enable-valgrind
|
|||
export PKG_CONFIG_LIBDIR=/usr/lib/pkgconfig:/usr/share/pkgconfig
|
||||
. $topsrcdir/build/unix/mozconfig.gtk
|
||||
|
||||
# Enable Telemetry
|
||||
export MOZ_TELEMETRY_REPORTING=1
|
||||
|
||||
# Package js shell.
|
||||
export MOZ_PACKAGE_JSSHELL=1
|
||||
|
||||
|
|
|
@ -9,6 +9,9 @@ ac_add_options --with-branding=browser/branding/nightly
|
|||
|
||||
export MOZILLA_OFFICIAL=1
|
||||
|
||||
# Enable Telemetry
|
||||
export MOZ_TELEMETRY_REPORTING=1
|
||||
|
||||
ac_add_options --disable-stdcxx-compat
|
||||
|
||||
. "$topsrcdir/build/mozconfig.common.override"
|
||||
|
|
|
@ -15,6 +15,9 @@ export PKG_CONFIG_LIBDIR=/usr/lib/pkgconfig:/usr/share/pkgconfig
|
|||
# Package js shell.
|
||||
export MOZ_PACKAGE_JSSHELL=1
|
||||
|
||||
# Enable Telemetry
|
||||
export MOZ_TELEMETRY_REPORTING=1
|
||||
|
||||
# Need this to prevent name conflicts with the normal nightly build packages
|
||||
export MOZ_PKG_SPECIAL=asan
|
||||
|
||||
|
|
|
@ -10,6 +10,9 @@ MOZ_AUTOMATION_L10N_CHECK=0
|
|||
# Needed to enable breakpad in application.ini
|
||||
export MOZILLA_OFFICIAL=1
|
||||
|
||||
# Enable Telemetry
|
||||
export MOZ_TELEMETRY_REPORTING=1
|
||||
|
||||
# Treat warnings as errors (modulo ALLOW_COMPILER_WARNINGS).
|
||||
ac_add_options --enable-warnings-as-errors
|
||||
|
||||
|
|
|
@ -13,6 +13,9 @@ ac_add_options --enable-valgrind
|
|||
export PKG_CONFIG_LIBDIR=/usr/lib64/pkgconfig:/usr/share/pkgconfig
|
||||
. $topsrcdir/build/unix/mozconfig.gtk
|
||||
|
||||
# Enable Telemetry
|
||||
export MOZ_TELEMETRY_REPORTING=1
|
||||
|
||||
# Package js shell.
|
||||
export MOZ_PACKAGE_JSSHELL=1
|
||||
|
||||
|
|
|
@ -9,6 +9,9 @@ ac_add_options --with-branding=browser/branding/nightly
|
|||
|
||||
export MOZILLA_OFFICIAL=1
|
||||
|
||||
# Enable Telemetry
|
||||
export MOZ_TELEMETRY_REPORTING=1
|
||||
|
||||
ac_add_options --disable-stdcxx-compat
|
||||
|
||||
. "$topsrcdir/build/mozconfig.common.override"
|
||||
|
|
|
@ -12,5 +12,8 @@ fi
|
|||
|
||||
export MOZILLA_OFFICIAL=1
|
||||
|
||||
# Enable Telemetry
|
||||
export MOZ_TELEMETRY_REPORTING=1
|
||||
|
||||
. "$topsrcdir/build/mozconfig.common.override"
|
||||
. "$topsrcdir/build/mozconfig.cache"
|
||||
|
|
|
@ -8,6 +8,9 @@ ac_add_options --with-google-oauth-api-keyfile=/builds/google-oauth-api.key
|
|||
# Needed to enable breakpad in application.ini
|
||||
export MOZILLA_OFFICIAL=1
|
||||
|
||||
# Enable Telemetry
|
||||
export MOZ_TELEMETRY_REPORTING=1
|
||||
|
||||
if test "${MOZ_UPDATE_CHANNEL}" = "nightly"; then
|
||||
ac_add_options --with-macbundlename-prefix=Firefox
|
||||
fi
|
||||
|
|
|
@ -5,6 +5,9 @@ ac_add_options --enable-debug
|
|||
ac_add_options --enable-optimize="-O1"
|
||||
ac_add_options --with-google-oauth-api-keyfile=/builds/google-oauth-api.key
|
||||
|
||||
# Enable Telemetry
|
||||
export MOZ_TELEMETRY_REPORTING=1
|
||||
|
||||
# Package js shell.
|
||||
export MOZ_PACKAGE_JSSHELL=1
|
||||
|
||||
|
|
|
@ -6,6 +6,9 @@ ac_add_options --with-google-oauth-api-keyfile=/builds/google-oauth-api.key
|
|||
# Needed to enable breakpad in application.ini
|
||||
export MOZILLA_OFFICIAL=1
|
||||
|
||||
# Enable Telemetry
|
||||
export MOZ_TELEMETRY_REPORTING=1
|
||||
|
||||
if test "${MOZ_UPDATE_CHANNEL}" = "nightly"; then
|
||||
ac_add_options --with-macbundlename-prefix=Firefox
|
||||
fi
|
||||
|
|
|
@ -18,6 +18,9 @@ ac_add_options --with-google-oauth-api-keyfile=${_google_oauth_api_keyfile}
|
|||
# Needed to enable breakpad in application.ini
|
||||
export MOZILLA_OFFICIAL=1
|
||||
|
||||
# Enable Telemetry
|
||||
export MOZ_TELEMETRY_REPORTING=1
|
||||
|
||||
. $topsrcdir/build/win32/mozconfig.vs2015-win64
|
||||
|
||||
# Treat warnings as errors (modulo ALLOW_COMPILER_WARNINGS).
|
||||
|
|
|
@ -8,6 +8,9 @@ ac_add_options --with-branding=browser/branding/nightly
|
|||
|
||||
export MOZILLA_OFFICIAL=1
|
||||
|
||||
# Enable Telemetry
|
||||
export MOZ_TELEMETRY_REPORTING=1
|
||||
|
||||
if test "$PROCESSOR_ARCHITECTURE" = "AMD64" -o "$PROCESSOR_ARCHITEW6432" = "AMD64"; then
|
||||
. $topsrcdir/build/win32/mozconfig.vs2015-win64
|
||||
else
|
||||
|
|
|
@ -20,6 +20,9 @@ ac_add_options --with-google-oauth-api-keyfile=${_google_oauth_api_keyfile}
|
|||
# Needed to enable breakpad in application.ini
|
||||
export MOZILLA_OFFICIAL=1
|
||||
|
||||
# Enable Telemetry
|
||||
export MOZ_TELEMETRY_REPORTING=1
|
||||
|
||||
# Treat warnings as errors (modulo ALLOW_COMPILER_WARNINGS).
|
||||
ac_add_options --enable-warnings-as-errors
|
||||
|
||||
|
|
|
@ -9,6 +9,9 @@ ac_add_options --with-branding=browser/branding/nightly
|
|||
|
||||
export MOZILLA_OFFICIAL=1
|
||||
|
||||
# Enable Telemetry
|
||||
export MOZ_TELEMETRY_REPORTING=1
|
||||
|
||||
. $topsrcdir/build/win64/mozconfig.vs2015
|
||||
|
||||
. "$topsrcdir/build/mozconfig.common.override"
|
||||
|
|
|
@ -65,7 +65,6 @@ detailsPane.noItems=No items
|
|||
detailsPane.itemsCountLabel=One item;#1 items
|
||||
|
||||
mostVisitedTitle=Most Visited
|
||||
recentlyBookmarkedTitle=Recently Bookmarked
|
||||
recentTagsTitle=Recent Tags
|
||||
|
||||
OrganizerQueryHistory=History
|
||||
|
|
|
@ -107,15 +107,11 @@
|
|||
}
|
||||
|
||||
#nav-bar {
|
||||
box-shadow: 0 1px 0 @toolbarHighlight@ inset;
|
||||
box-shadow: 0 1px 0 @navbarInsetHighlight@ inset;
|
||||
padding-top: 2px;
|
||||
padding-bottom: 2px;
|
||||
}
|
||||
|
||||
#nav-bar:-moz-lwtheme {
|
||||
box-shadow: 0 1px 0 @toolbarHighlightLWT@ inset;
|
||||
}
|
||||
|
||||
#nav-bar-overflow-button {
|
||||
-moz-image-region: rect(-5px, 12px, 11px, -4px);
|
||||
}
|
||||
|
|
|
@ -6,6 +6,8 @@
|
|||
|
||||
%define toolbarHighlight hsla(0,0%,100%,.05)
|
||||
%define toolbarHighlightLWT rgba(255,255,255,.4)
|
||||
/* navbarInsetHighlight is tightly coupled to the toolbarHighlight constant. */
|
||||
%define navbarInsetHighlight hsla(0,0%,100%,.4)
|
||||
%define fgTabTexture linear-gradient(transparent 2px, @toolbarHighlight@ 2px, @toolbarHighlight@)
|
||||
%define fgTabTextureLWT linear-gradient(transparent 2px, @toolbarHighlightLWT@ 2px, @toolbarHighlightLWT@)
|
||||
%define fgTabBackgroundColor -moz-dialog
|
||||
|
|
|
@ -4,10 +4,6 @@
|
|||
|
||||
%include ../../shared/customizableui/panelUIOverlay.inc.css
|
||||
|
||||
:root {
|
||||
--panel-separator-color: hsla(210,4%,10%,.15);
|
||||
}
|
||||
|
||||
.panel-subviews {
|
||||
background-color: hsla(0,0%,100%,.97);
|
||||
}
|
||||
|
@ -77,4 +73,4 @@ menu.subviewbutton > .menu-right > image {
|
|||
|
||||
toolbarpaletteitem[place="palette"] > .toolbarbutton-1 > .toolbarbutton-menubutton-button {
|
||||
padding: 3px 1px;
|
||||
}
|
||||
}
|
|
@ -21,7 +21,6 @@
|
|||
%include ../browser.inc
|
||||
|
||||
:root {
|
||||
--panel-separator-color: ThreeDShadow;
|
||||
--panel-ui-exit-subview-gutter-width: 38px;
|
||||
}
|
||||
|
||||
|
|
|
@ -33,7 +33,7 @@
|
|||
// Rules from the mozilla plugin
|
||||
"mozilla/mark-test-function-used": 1,
|
||||
"mozilla/no-aArgs": 1,
|
||||
"mozilla/no-cpows-in-tests": 1,
|
||||
"mozilla/no-cpows-in-tests": 2,
|
||||
// See bug 1224289.
|
||||
"mozilla/reject-importGlobalProperties": 1,
|
||||
"mozilla/var-only-at-top-level": 1,
|
||||
|
|
|
@ -74,19 +74,54 @@ function reload(event) {
|
|||
// We automatically reload the toolbox if we are on a browser tab
|
||||
// with a toolbox already opened
|
||||
let top = getTopLevelWindow(event.view)
|
||||
let isBrowser = top.location.href.includes("/browser.xul") && top.gDevToolsBrowser;
|
||||
let isBrowser = top.location.href.includes("/browser.xul");
|
||||
let reloadToolbox = false;
|
||||
if (isBrowser && top.gDevToolsBrowser.hasToolboxOpened) {
|
||||
reloadToolbox = top.gDevToolsBrowser.hasToolboxOpened(top);
|
||||
if (isBrowser && top.gBrowser) {
|
||||
// We do not use any devtools code before the call to Loader.jsm reload as
|
||||
// any attempt to use Loader.jsm to load a module will instanciate a new
|
||||
// Loader.
|
||||
let nbox = top.gBrowser.getNotificationBox();
|
||||
reloadToolbox =
|
||||
top.document.getAnonymousElementByAttribute(nbox, "class",
|
||||
"devtools-toolbox-bottom-iframe") ||
|
||||
top.document.getAnonymousElementByAttribute(nbox, "class",
|
||||
"devtools-toolbox-side-iframe") ||
|
||||
Services.wm.getMostRecentWindow("devtools:toolbox");
|
||||
}
|
||||
let browserConsole = Services.wm.getMostRecentWindow("devtools:webconsole");
|
||||
let reopenBrowserConsole = false;
|
||||
if (browserConsole) {
|
||||
browserConsole.close();
|
||||
reopenBrowserConsole = true;
|
||||
}
|
||||
|
||||
dump("Reload DevTools. (reload-toolbox:"+reloadToolbox+")\n");
|
||||
|
||||
// Invalidate xul cache in order to see changes made to chrome:// files
|
||||
Services.obs.notifyObservers(null, "startupcache-invalidate", null);
|
||||
|
||||
// Ask the loader to update itself and reopen the toolbox if needed
|
||||
// This frame script is going to be executed in all processes: parent and childs
|
||||
Services.ppmm.loadProcessScript("data:,new " + function () {
|
||||
/* Flush message manager cached frame scripts as well as chrome locales */
|
||||
let obs = Components.classes["@mozilla.org/observer-service;1"]
|
||||
.getService(Components.interfaces.nsIObserverService);
|
||||
obs.notifyObservers(null, "message-manager-flush-caches", null);
|
||||
|
||||
/* Also purge cached modules in child processes, we do it a few lines after
|
||||
in the parent process */
|
||||
if (Services.appinfo.processType == Services.appinfo.PROCESS_TYPE_CONTENT) {
|
||||
Services.obs.notifyObservers(null, "devtools-unload", "reload");
|
||||
}
|
||||
}, false);
|
||||
|
||||
// As we can't get a reference to existing Loader.jsm instances, we send them
|
||||
// an observer service notification to unload them.
|
||||
Services.obs.notifyObservers(null, "devtools-unload", "reload");
|
||||
|
||||
// Then spawn a brand new Loader.jsm instance and start the main module
|
||||
Cu.unload("resource://devtools/shared/Loader.jsm");
|
||||
const {devtools} = Cu.import("resource://devtools/shared/Loader.jsm", {});
|
||||
devtools.reload();
|
||||
devtools.require("devtools/client/framework/devtools-browser");
|
||||
|
||||
// Go over all top level windows to reload all devtools related things
|
||||
let windowsEnum = Services.wm.getEnumerator(null);
|
||||
|
@ -114,15 +149,6 @@ function reload(event) {
|
|||
}
|
||||
} else if (windowtype === "devtools:webide") {
|
||||
window.location.reload();
|
||||
} else if (windowtype === "devtools:webconsole") {
|
||||
// Browser console document can't just be reloaded.
|
||||
// HUDService is going to close it on unload.
|
||||
// Instead we have to manually toggle it.
|
||||
let HUDService = devtools.require("devtools/client/webconsole/hudservice");
|
||||
HUDService.toggleBrowserConsole()
|
||||
.then(() => {
|
||||
HUDService.toggleBrowserConsole();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -140,6 +166,14 @@ function reload(event) {
|
|||
}, 1000);
|
||||
}
|
||||
|
||||
// Browser console document can't just be reloaded.
|
||||
// HUDService is going to close it on unload.
|
||||
// Instead we have to manually toggle it.
|
||||
if (reopenBrowserConsole) {
|
||||
let HUDService = devtools.require("devtools/client/webconsole/hudservice");
|
||||
HUDService.toggleBrowserConsole();
|
||||
}
|
||||
|
||||
actionOccurred("reloadAddonReload");
|
||||
}
|
||||
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
loader.lazyImporter(this, "AddonManager",
|
||||
"resource://gre/modules/AddonManager.jsm");
|
||||
|
||||
const { Cc, Ci } = require("chrome");
|
||||
const { Cc, Ci, Cu } = require("chrome");
|
||||
const { createFactory, createClass, DOM: dom } =
|
||||
require("devtools/client/shared/vendor/react");
|
||||
const Services = require("Services");
|
||||
|
@ -57,6 +57,7 @@ module.exports = createClass({
|
|||
|
||||
AddonManager.installTemporaryAddon(file)
|
||||
.catch(e => {
|
||||
Cu.reportError(e);
|
||||
this.setState({ installError: e.message });
|
||||
});
|
||||
},
|
||||
|
|
|
@ -17,8 +17,7 @@ var gNewChromeSource = promise.defer()
|
|||
var { DevToolsLoader } = Cu.import("resource://devtools/shared/Loader.jsm", {});
|
||||
var loader = new DevToolsLoader();
|
||||
loader.invisibleToDebugger = true;
|
||||
loader.main("devtools/server/main");
|
||||
var DebuggerServer = loader.DebuggerServer;
|
||||
var { DebuggerServer } = loader.require("devtools/server/main");
|
||||
|
||||
function test() {
|
||||
if (!DebuggerServer.initialized) {
|
||||
|
|
|
@ -70,7 +70,7 @@ DevToolsStartup.prototype = {
|
|||
let { loader } = Cu.import("resource://devtools/shared/Loader.jsm", {});
|
||||
// Ensure loading main devtools module that hooks up into browser UI
|
||||
// and initialize all devtools machinery.
|
||||
loader.main("devtools/client/main");
|
||||
loader.require("devtools/client/framework/devtools-browser");
|
||||
},
|
||||
|
||||
handleConsoleFlag: function(cmdLine) {
|
||||
|
@ -154,8 +154,8 @@ DevToolsStartup.prototype = {
|
|||
// settings).
|
||||
let serverLoader = new DevToolsLoader();
|
||||
serverLoader.invisibleToDebugger = true;
|
||||
serverLoader.main("devtools/server/main");
|
||||
let debuggerServer = serverLoader.DebuggerServer;
|
||||
let { DebuggerServer: debuggerServer } =
|
||||
serverLoader.require("devtools/server/main");
|
||||
debuggerServer.init();
|
||||
debuggerServer.addBrowserActors();
|
||||
debuggerServer.allowChromeProcess = true;
|
||||
|
|
|
@ -127,8 +127,8 @@ BrowserToolboxProcess.prototype = {
|
|||
// invisible to the debugger (unlike the usual loader settings).
|
||||
this.loader = new DevToolsLoader();
|
||||
this.loader.invisibleToDebugger = true;
|
||||
this.loader.main("devtools/server/main");
|
||||
this.debuggerServer = this.loader.DebuggerServer;
|
||||
let { DebuggerServer } = this.loader.require("devtools/server/main");
|
||||
this.debuggerServer = DebuggerServer;
|
||||
dumpn("Created a separate loader instance for the DebuggerServer.");
|
||||
|
||||
// Forward interesting events.
|
||||
|
|
|
@ -10,7 +10,7 @@ var { classes: Cc, interfaces: Ci, utils: Cu } = Components;
|
|||
|
||||
var { loader, require } = Cu.import("resource://devtools/shared/Loader.jsm", {});
|
||||
// Require this module to setup core modules
|
||||
loader.main("devtools/client/main");
|
||||
loader.require("devtools/client/framework/devtools-browser");
|
||||
|
||||
var { gDevTools } = require("devtools/client/framework/devtools");
|
||||
var { TargetFactory } = require("devtools/client/framework/target");
|
||||
|
|
|
@ -3,6 +3,7 @@ tags = devtools
|
|||
subsuite = devtools
|
||||
support-files =
|
||||
doc_author-sheet.html
|
||||
doc_blob_stylesheet.html
|
||||
doc_content_stylesheet.html
|
||||
doc_content_stylesheet_imported.css
|
||||
doc_content_stylesheet_imported2.css
|
||||
|
@ -55,6 +56,7 @@ support-files =
|
|||
[browser_rules_authored.js]
|
||||
[browser_rules_authored_color.js]
|
||||
[browser_rules_authored_override.js]
|
||||
[browser_rules_blob_stylesheet.js]
|
||||
[browser_rules_colorpicker-and-image-tooltip_01.js]
|
||||
[browser_rules_colorpicker-and-image-tooltip_02.js]
|
||||
[browser_rules_colorpicker-appears-on-swatch-click.js]
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
// Test that the rule-view content is correct for stylesheet generated
|
||||
// with createObjectURL(cssBlob)
|
||||
const TEST_URL = URL_ROOT + "doc_blob_stylesheet.html";
|
||||
|
||||
add_task(function* () {
|
||||
yield addTab(TEST_URL);
|
||||
let {inspector, view} = yield openRuleView();
|
||||
|
||||
yield selectNode("h1", inspector);
|
||||
is(view.element.querySelectorAll("#noResults").length, 0,
|
||||
"The no-results element is not displayed");
|
||||
|
||||
is(view.element.querySelectorAll(".ruleview-rule").length, 2,
|
||||
"There are 2 displayed rules");
|
||||
});
|
|
@ -0,0 +1,39 @@
|
|||
<!-- Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ -->
|
||||
<!doctype html>
|
||||
</html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Blob stylesheet sourcemap</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Test</h1>
|
||||
<script>
|
||||
"use strict";
|
||||
|
||||
var cssContent = `body {
|
||||
background-color: black;
|
||||
}
|
||||
body > h1 {
|
||||
color: white;
|
||||
}
|
||||
` +
|
||||
"/*# sourceMappingURL=data:application/json;base64,ewoidmVyc2lvbiI6IDMsCiJtYX" +
|
||||
"BwaW5ncyI6ICJBQUFBLElBQUs7RUFDSCxnQkFBZ0IsRUFBRSxLQUFLOztBQUN2QixTQUFPO0VBQ0" +
|
||||
"wsS0FBSyxFQUFFLEtBQUsiLAoic291cmNlcyI6IFsidGVzdC5zY3NzIl0sCiJzb3VyY2VzQ29udG" +
|
||||
"VudCI6IFsiYm9keSB7XG4gIGJhY2tncm91bmQtY29sb3I6IGJsYWNrO1xuICAmID4gaDEge1xuIC" +
|
||||
"AgIGNvbG9yOiB3aGl0ZTsgIFxuICB9XG59XG4iXSwKIm5hbWVzIjogW10sCiJmaWxlIjogInRlc3" +
|
||||
"QuY3NzIgp9Cg== */";
|
||||
var cssBlob = new Blob([cssContent], {type: "text/css"});
|
||||
var url = URL.createObjectURL(cssBlob);
|
||||
|
||||
var head = document.querySelector("head");
|
||||
var link = document.createElement("link");
|
||||
link.rel = "stylesheet";
|
||||
link.type = "text/css";
|
||||
link.href = url;
|
||||
head.appendChild(link);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
|
@ -65,13 +65,16 @@ toolbar.displayBy=Group by:
|
|||
# describing the select menu options of the display options.
|
||||
toolbar.displayBy.tooltip=Change how objects are grouped
|
||||
|
||||
# TODO FITZGEN
|
||||
# LOCALIZATION NOTE (toolbar.pop-view): The text in the button to go back to the
|
||||
# previous view.
|
||||
toolbar.pop-view=←
|
||||
|
||||
# TODO FITZGEN
|
||||
# LOCALIZATION NOTE (toolbar.pop-view.label): The text for the label for the
|
||||
# button to go back to the previous view.
|
||||
toolbar.pop-view.label=Go back to aggregates
|
||||
|
||||
# TODO FITZGEN
|
||||
# LOCALIZATION NOTE (toolbar.viewing-individuals): The text letting the user
|
||||
# know that they are viewing individual nodes from a census group.
|
||||
toolbar.viewing-individuals=⁂ Viewing individuals in group
|
||||
|
||||
# LOCALIZATION NOTE (censusDisplays.coarseType.tooltip): The tooltip for the
|
||||
|
@ -178,7 +181,8 @@ filter.placeholder=Filter
|
|||
# tool's filter search box.
|
||||
filter.tooltip=Filter the contents of the heap snapshot
|
||||
|
||||
# TODO FITZGEN
|
||||
# LOCALIZATION NOTE (tree-item.view-individuals.tooltip): The tooltip for the
|
||||
# button to view individuals in this group.
|
||||
tree-item.view-individuals.tooltip=View individual nodes in this group and their retaining paths
|
||||
|
||||
# LOCALIZATION NOTE (tree-item.load-more): The label for the links to fetch the
|
||||
|
@ -307,22 +311,28 @@ snapshot.state.saving-tree-map.full=Saving tree map…
|
|||
# snapshot state ERROR, used in the main heap view.
|
||||
snapshot.state.error.full=There was an error processing this snapshot.
|
||||
|
||||
# TODO FITZGEN
|
||||
# LOCALIZATION NOTE (individuals.state.error): The short message displayed when
|
||||
# there is an error fetching individuals from a group.
|
||||
individuals.state.error=Error
|
||||
|
||||
# TODO FITZGEN
|
||||
# LOCALIZATION NOTE (individuals.state.error.full): The longer message displayed
|
||||
# when there is an error fetching individuals from a group.
|
||||
individuals.state.error.full=There was an error while fetching individuals in the group
|
||||
|
||||
# TODO FITZGEN
|
||||
# LOCALIZATION NOTE (individuals.state.fetching): The short message displayed
|
||||
# while fetching individuals.
|
||||
individuals.state.fetching=Fetching…
|
||||
|
||||
# TODO FITZGEN
|
||||
# LOCALIZATION NOTE (individuals.state.fetching.full): The longer message
|
||||
# displayed while fetching individuals.
|
||||
individuals.state.fetching.full=Fetching individuals in group…
|
||||
|
||||
# TODO FITZGEN
|
||||
# LOCALIZATION NOTE (individuals.field.node): The header label for an individual
|
||||
# node.
|
||||
individuals.field.node=Node
|
||||
|
||||
# TODO FITZGEN
|
||||
# LOCALIZATION NOTE (individuals.field.node.tooltip): The tooltip for the header
|
||||
# label for an individual node.
|
||||
individuals.field.node.tooltip=The individual node in the snapshot
|
||||
|
||||
# LOCALIZATION NOTE (snapshot.state.saving): The label describing the snapshot
|
||||
|
|
|
@ -1,22 +0,0 @@
|
|||
/* 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";
|
||||
|
||||
/**
|
||||
* This module could have been devtools-browser.js.
|
||||
* But we need this wrapper in order to define precisely what we are exporting
|
||||
* out of client module loader (Loader.jsm): only Toolbox and TargetFactory.
|
||||
*/
|
||||
|
||||
// For compatiblity reasons, exposes these symbols on "devtools":
|
||||
Object.defineProperty(exports, "Toolbox", {
|
||||
get: () => require("devtools/client/framework/toolbox").Toolbox
|
||||
});
|
||||
Object.defineProperty(exports, "TargetFactory", {
|
||||
get: () => require("devtools/client/framework/target").TargetFactory
|
||||
});
|
||||
|
||||
// Load the main browser module
|
||||
require("devtools/client/framework/devtools-browser");
|
|
@ -9,7 +9,6 @@ const { refresh } = require("./refresh");
|
|||
|
||||
exports.setCensusDisplayAndRefresh = function(heapWorker, display) {
|
||||
return function*(dispatch, getState) {
|
||||
console.log("FITZGEN: setCensusDisplayAndRefresh", display);
|
||||
dispatch(setCensusDisplay(display));
|
||||
yield dispatch(refresh(heapWorker));
|
||||
};
|
||||
|
|
|
@ -14,7 +14,6 @@ const snapshot = require("./snapshot");
|
|||
* @param {HeapAnalysesWorker} heapWorker
|
||||
*/
|
||||
exports.refresh = function (heapWorker) {
|
||||
console.log("FITZGEN: refresh");
|
||||
return function* (dispatch, getState) {
|
||||
switch (getState().view.state) {
|
||||
case viewState.DIFFING:
|
||||
|
|
|
@ -140,8 +140,6 @@ TaskCache.declareCacheableTask({
|
|||
},
|
||||
|
||||
task: function*(heapWorker, id, removeFromCache, dispatch, getState) {
|
||||
console.log("FITZGEN: readSnapshot");
|
||||
|
||||
const snapshot = getSnapshot(getState(), id);
|
||||
assert([states.SAVED, states.IMPORTING].includes(snapshot.state),
|
||||
`Should only read a snapshot once. Found snapshot in state ${snapshot.state}`);
|
||||
|
@ -153,14 +151,12 @@ TaskCache.declareCacheableTask({
|
|||
yield heapWorker.readHeapSnapshot(snapshot.path);
|
||||
creationTime = yield heapWorker.getCreationTime(snapshot.path);
|
||||
} catch (error) {
|
||||
console.log("FITZGEN: readSnapshot: error", error);
|
||||
removeFromCache();
|
||||
reportException("readSnapshot", error);
|
||||
dispatch({ type: actions.SNAPSHOT_ERROR, id, error });
|
||||
return;
|
||||
}
|
||||
|
||||
console.log("FITZGEN: readSnapshot: done reading");
|
||||
removeFromCache();
|
||||
dispatch({ type: actions.READ_SNAPSHOT_END, id, creationTime });
|
||||
}
|
||||
|
@ -196,10 +192,8 @@ function makeTakeCensusTask({ getDisplay, getFilter, getCensus, beginAction,
|
|||
},
|
||||
|
||||
task: function*(heapWorker, id, removeFromCache, dispatch, getState) {
|
||||
console.log("FITZGEN: takeCensus");
|
||||
const snapshot = getSnapshot(getState(), id);
|
||||
if (!snapshot) {
|
||||
console.log("FITZGEN: no snapshot");
|
||||
removeFromCache();
|
||||
return;
|
||||
}
|
||||
|
@ -214,7 +208,6 @@ function makeTakeCensusTask({ getDisplay, getFilter, getCensus, beginAction,
|
|||
|
||||
// If display, filter and inversion haven't changed, don't do anything.
|
||||
if (censusIsUpToDate(filter, display, getCensus(snapshot))) {
|
||||
console.log("FITZGEN: census is up to date");
|
||||
removeFromCache();
|
||||
return;
|
||||
}
|
||||
|
@ -226,7 +219,6 @@ function makeTakeCensusTask({ getDisplay, getFilter, getCensus, beginAction,
|
|||
display = getDisplay(getState());
|
||||
filter = getState().filter;
|
||||
|
||||
console.log("FITZGEN: taking census with display =", display.displayName);
|
||||
dispatch({
|
||||
type: beginAction,
|
||||
id,
|
||||
|
@ -246,7 +238,6 @@ function makeTakeCensusTask({ getDisplay, getFilter, getCensus, beginAction,
|
|||
{ breakdown: display.breakdown },
|
||||
opts));
|
||||
} catch (error) {
|
||||
console.log("FITZGEN: error taking census: " + error + "\n" + error.stack);
|
||||
removeFromCache();
|
||||
reportException("takeCensus", error);
|
||||
dispatch({ type: errorAction, id, error });
|
||||
|
@ -256,8 +247,6 @@ function makeTakeCensusTask({ getDisplay, getFilter, getCensus, beginAction,
|
|||
while (filter !== getState().filter ||
|
||||
display !== getDisplay(getState()));
|
||||
|
||||
console.log("FITZGEN: done taking census");
|
||||
|
||||
removeFromCache();
|
||||
dispatch({
|
||||
type: endAction,
|
||||
|
@ -465,11 +454,9 @@ const refreshIndividuals = exports.refreshIndividuals = function(heapWorker) {
|
|||
* @param {HeapAnalysesClient} heapWorker
|
||||
*/
|
||||
const refreshSelectedCensus = exports.refreshSelectedCensus = function (heapWorker) {
|
||||
console.log("FITZGEN: refreshSelectedCensus");
|
||||
return function*(dispatch, getState) {
|
||||
let snapshot = getState().snapshots.find(s => s.selected);
|
||||
if (!snapshot || snapshot.state !== states.READ) {
|
||||
console.log("FITZGEN: nothing to do");
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -481,7 +468,6 @@ const refreshSelectedCensus = exports.refreshSelectedCensus = function (heapWork
|
|||
// task action will follow through and ensure that a census is taken.
|
||||
if ((snapshot.census && snapshot.census.state === censusState.SAVED) ||
|
||||
!snapshot.census) {
|
||||
console.log("FITZGEN: taking census");
|
||||
yield dispatch(takeCensus(heapWorker, snapshot.id));
|
||||
}
|
||||
};
|
||||
|
|
|
@ -45,7 +45,6 @@ const TaskCache = module.exports = class TaskCache {
|
|||
* @param {Any} key
|
||||
*/
|
||||
remove(key) {
|
||||
console.log("FITZGEN: removing task from cache with keky =", key);
|
||||
assert(this._cache.has(key),
|
||||
`Should have an extant entry for key = ${key}`);
|
||||
|
||||
|
@ -71,12 +70,9 @@ TaskCache.declareCacheableTask = function({ getCacheKey, task }) {
|
|||
|
||||
const extantResult = cache.get(key);
|
||||
if (extantResult) {
|
||||
console.log("FITZGEN: re-using task with cache key =", key);
|
||||
return extantResult;
|
||||
}
|
||||
|
||||
console.log("FITZGEN: creating new task with cache key =", key);
|
||||
|
||||
// Ensure that we have our new entry in the cache *before* dispatching the
|
||||
// task!
|
||||
let resolve;
|
||||
|
|
|
@ -51,6 +51,5 @@ JAR_MANIFESTS += ['jar.mn']
|
|||
|
||||
DevToolsModules(
|
||||
'definitions.js',
|
||||
'main.js',
|
||||
'menus.js',
|
||||
)
|
||||
|
|
|
@ -24,7 +24,7 @@ addRDMTask(TEST_URL, function* ({ ui }) {
|
|||
// Browser's location should match original tab
|
||||
yield waitForFrameLoad(ui, TEST_URL);
|
||||
let location = yield spawnViewportTask(ui, {}, function* () {
|
||||
return content.location.href;
|
||||
return content.location.href; // eslint-disable-line
|
||||
});
|
||||
is(location, TEST_URL, "Viewport location matches");
|
||||
});
|
||||
|
|
|
@ -16,15 +16,18 @@ add_task(function*() {
|
|||
let hud = yield openConsole(null);
|
||||
info("console opened");
|
||||
|
||||
let button = content.document.querySelector("button");
|
||||
ok(button, "we have the button on the page");
|
||||
|
||||
// On e10s, the exception is triggered in child process
|
||||
// and is ignored by test harness
|
||||
if (!Services.appinfo.browserTabsRemoteAutostart) {
|
||||
expectUncaughtException();
|
||||
}
|
||||
EventUtils.sendMouseEvent({ type: "click" }, button, content);
|
||||
|
||||
ContentTask.spawn(gBrowser.selectedBrowser, {}, function*() {
|
||||
let button = content.document.querySelector("button");
|
||||
ok(button, "we have the button on the page");
|
||||
button.click();
|
||||
});
|
||||
|
||||
let [result] = yield waitForMessages({
|
||||
webconsole: hud,
|
||||
|
|
|
@ -1957,49 +1957,66 @@ const ThreadActor = ActorClass({
|
|||
this.scripts.addScripts(this.dbg.findScripts({ source: aSource }));
|
||||
|
||||
let sourceActor = this.sources.createNonSourceMappedActor(aSource);
|
||||
|
||||
// Set any stored breakpoints.
|
||||
let bpActors = [...this.breakpointActorMap.findActors()];
|
||||
let promises = [];
|
||||
|
||||
// Go ahead and establish the source actors for this script, which
|
||||
// fetches sourcemaps if available and sends onNewSource
|
||||
// notifications.
|
||||
let sourceActorsCreated = this.sources.createSourceActors(aSource);
|
||||
if (this._options.useSourceMaps) {
|
||||
let promises = [];
|
||||
|
||||
if (bpActors.length) {
|
||||
// We need to use unsafeSynchronize here because if the page is being reloaded,
|
||||
// this call will replace the previous set of source actors for this source
|
||||
// with a new one. If the source actors have not been replaced by the time
|
||||
// we try to reset the breakpoints below, their location objects will still
|
||||
// point to the old set of source actors, which point to different
|
||||
// scripts.
|
||||
this.unsafeSynchronize(sourceActorsCreated);
|
||||
}
|
||||
// Go ahead and establish the source actors for this script, which
|
||||
// fetches sourcemaps if available and sends onNewSource
|
||||
// notifications.
|
||||
let sourceActorsCreated = this.sources._createSourceMappedActors(aSource);
|
||||
|
||||
for (let _actor of bpActors) {
|
||||
// XXX bug 1142115: We do async work in here, so we need to create a fresh
|
||||
// binding because for/of does not yet do that in SpiderMonkey.
|
||||
let actor = _actor;
|
||||
|
||||
if (actor.isPending) {
|
||||
promises.push(actor.originalLocation.originalSourceActor._setBreakpoint(actor));
|
||||
} else {
|
||||
promises.push(this.sources.getAllGeneratedLocations(actor.originalLocation)
|
||||
.then((generatedLocations) => {
|
||||
if (generatedLocations.length > 0 &&
|
||||
generatedLocations[0].generatedSourceActor.actorID === sourceActor.actorID) {
|
||||
sourceActor._setBreakpointAtAllGeneratedLocations(
|
||||
actor,
|
||||
generatedLocations
|
||||
);
|
||||
}
|
||||
}));
|
||||
if (bpActors.length) {
|
||||
// We need to use unsafeSynchronize here because if the page is being reloaded,
|
||||
// this call will replace the previous set of source actors for this source
|
||||
// with a new one. If the source actors have not been replaced by the time
|
||||
// we try to reset the breakpoints below, their location objects will still
|
||||
// point to the old set of source actors, which point to different
|
||||
// scripts.
|
||||
this.unsafeSynchronize(sourceActorsCreated);
|
||||
}
|
||||
}
|
||||
|
||||
if (promises.length > 0) {
|
||||
this.unsafeSynchronize(promise.all(promises));
|
||||
for (let _actor of bpActors) {
|
||||
// XXX bug 1142115: We do async work in here, so we need to create a fresh
|
||||
// binding because for/of does not yet do that in SpiderMonkey.
|
||||
let actor = _actor;
|
||||
|
||||
if (actor.isPending) {
|
||||
promises.push(actor.originalLocation.originalSourceActor._setBreakpoint(actor));
|
||||
} else {
|
||||
promises.push(this.sources.getAllGeneratedLocations(actor.originalLocation)
|
||||
.then((generatedLocations) => {
|
||||
if (generatedLocations.length > 0 &&
|
||||
generatedLocations[0].generatedSourceActor.actorID === sourceActor.actorID) {
|
||||
sourceActor._setBreakpointAtAllGeneratedLocations(
|
||||
actor,
|
||||
generatedLocations
|
||||
);
|
||||
}
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
if (promises.length > 0) {
|
||||
this.unsafeSynchronize(promise.all(promises));
|
||||
}
|
||||
} else {
|
||||
// Bug 1225160: If addSource is called in response to a new script
|
||||
// notification, and this notification was triggered by loading a JSM from
|
||||
// chrome code, calling unsafeSynchronize could cause a debuggee timer to
|
||||
// fire. If this causes the JSM to be loaded a second time, the browser
|
||||
// will crash, because loading JSMS is not reentrant, and the first load
|
||||
// has not completed yet.
|
||||
//
|
||||
// The root of the problem is that unsafeSynchronize can cause debuggee
|
||||
// code to run. Unfortunately, fixing that is prohibitively difficult. The
|
||||
// best we can do at the moment is disable source maps for the browser
|
||||
// debugger, and carefully avoid the use of unsafeSynchronize in this
|
||||
// function when source maps are disabled.
|
||||
for (let actor of bpActors) {
|
||||
actor.originalLocation.originalSourceActor._setBreakpoint(actor);
|
||||
}
|
||||
}
|
||||
|
||||
this._debuggerSourcesSeen.add(aSource);
|
||||
|
|
|
@ -803,6 +803,9 @@ var StyleSheetActor = protocol.ActorClass({
|
|||
* Sets the source map's sourceRoot to be relative to the source map url.
|
||||
*/
|
||||
_setSourceMapRoot: function(aSourceMap, aAbsSourceMapURL, aScriptURL) {
|
||||
if (aScriptURL.startsWith("blob:")) {
|
||||
aScriptURL = aScriptURL.replace("blob:", "");
|
||||
}
|
||||
const base = dirname(
|
||||
aAbsSourceMapURL.startsWith("data:")
|
||||
? aScriptURL
|
||||
|
|
|
@ -329,7 +329,13 @@ TabSources.prototype = {
|
|||
}
|
||||
} catch (e) {
|
||||
// This only needs to be here because URL is not yet exposed to
|
||||
// workers.
|
||||
// workers. (BUG 1258892)
|
||||
const filename = url;
|
||||
const index = filename.lastIndexOf(".");
|
||||
const extension = index >= 0 ? filename.slice(index + 1) : "";
|
||||
if (extension === "js") {
|
||||
spec.contentType = "text/javascript";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,8 +19,7 @@ function init(msg) {
|
|||
// in the same process.
|
||||
let devtools = new DevToolsLoader();
|
||||
devtools.invisibleToDebugger = true;
|
||||
devtools.main("devtools/server/main");
|
||||
let { DebuggerServer, ActorPool } = devtools;
|
||||
let { DebuggerServer, ActorPool } = devtools.require("devtools/server/main");
|
||||
|
||||
if (!DebuggerServer.initialized) {
|
||||
DebuggerServer.init();
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
/* 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/. */
|
||||
/* globals NetUtil, FileUtils, OS */
|
||||
|
||||
"use strict";
|
||||
|
||||
|
@ -14,10 +13,6 @@ var { Constructor: CC, classes: Cc, interfaces: Ci, utils: Cu } = Components;
|
|||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "NetUtil", "resource://gre/modules/NetUtil.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "FileUtils", "resource://gre/modules/FileUtils.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "OS", "resource://gre/modules/osfile.jsm");
|
||||
|
||||
var { Loader } = Cu.import("resource://gre/modules/commonjs/toolkit/loader.js", {});
|
||||
var promise = Cu.import("resource://gre/modules/Promise.jsm", {}).Promise;
|
||||
|
||||
|
@ -135,11 +130,9 @@ BuiltinProvider.prototype = {
|
|||
var gNextLoaderID = 0;
|
||||
|
||||
/**
|
||||
* The main devtools API.
|
||||
* In addition to a few loader-related details, this object will also include all
|
||||
* exports from the main module. The standard instance of this loader is
|
||||
* exported as |devtools| below, but if a fresh copy of the loader is needed,
|
||||
* then a new one can also be created.
|
||||
* The main devtools API. The standard instance of this loader is exported as
|
||||
* |devtools| below, but if a fresh copy of the loader is needed, then a new
|
||||
* one can also be created.
|
||||
*/
|
||||
this.DevToolsLoader = function DevToolsLoader() {
|
||||
this.require = this.require.bind(this);
|
||||
|
@ -147,7 +140,8 @@ this.DevToolsLoader = function DevToolsLoader() {
|
|||
this.lazyImporter = XPCOMUtils.defineLazyModuleGetter.bind(XPCOMUtils);
|
||||
this.lazyServiceGetter = XPCOMUtils.defineLazyServiceGetter.bind(XPCOMUtils);
|
||||
this.lazyRequireGetter = this.lazyRequireGetter.bind(this);
|
||||
this.main = this.main.bind(this);
|
||||
|
||||
Services.obs.addObserver(this, "devtools-unload", false);
|
||||
};
|
||||
|
||||
DevToolsLoader.prototype = {
|
||||
|
@ -218,46 +212,6 @@ DevToolsLoader.prototype = {
|
|||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Add a URI to the loader.
|
||||
* @param string id
|
||||
* The module id that can be used within the loader to refer to this module.
|
||||
* @param string uri
|
||||
* The URI to load as a module.
|
||||
* @returns The module's exports.
|
||||
*/
|
||||
loadURI: function(id, uri) {
|
||||
let module = Loader.Module(id, uri);
|
||||
return Loader.load(this.provider.loader, module).exports;
|
||||
},
|
||||
|
||||
/**
|
||||
* Let the loader know the ID of the main module to load.
|
||||
*
|
||||
* The loader doesn't need a main module, but it's nice to have. This
|
||||
* will be called by the browser devtools to load the devtools/main module.
|
||||
*
|
||||
* When only using the server, there's no main module, and this method
|
||||
* can be ignored.
|
||||
*/
|
||||
main: function(id) {
|
||||
// Ensure the main module isn't loaded twice, because it may have observable
|
||||
// side-effects.
|
||||
if (this._mainid) {
|
||||
return;
|
||||
}
|
||||
this._mainid = id;
|
||||
this._main = Loader.main(this.provider.loader, id);
|
||||
|
||||
// Mirror the main module's exports on this object.
|
||||
Object.getOwnPropertyNames(this._main).forEach(key => {
|
||||
XPCOMUtils.defineLazyGetter(this, key, () => this._main[key]);
|
||||
});
|
||||
|
||||
var events = this.require("sdk/system/events");
|
||||
events.emit("devtools-loaded", {});
|
||||
},
|
||||
|
||||
/**
|
||||
* Override the provider used to load the tools.
|
||||
*/
|
||||
|
@ -286,8 +240,7 @@ DevToolsLoader.prototype = {
|
|||
lazyImporter: this.lazyImporter,
|
||||
lazyServiceGetter: this.lazyServiceGetter,
|
||||
lazyRequireGetter: this.lazyRequireGetter,
|
||||
id: this.id,
|
||||
main: this.main
|
||||
id: this.id
|
||||
},
|
||||
// Make sure `define` function exists. This allows defining some modules
|
||||
// in AMD format while retaining CommonJS compatibility through this hook.
|
||||
|
@ -323,18 +276,21 @@ DevToolsLoader.prototype = {
|
|||
},
|
||||
|
||||
/**
|
||||
* Reload the current provider.
|
||||
* Handles "devtools-unload" event
|
||||
*
|
||||
* @param String data
|
||||
* reason passed to modules when unloaded
|
||||
*/
|
||||
reload: function() {
|
||||
var events = this.require("sdk/system/events");
|
||||
events.emit("startupcache-invalidate", {});
|
||||
observe: function(subject, topic, data) {
|
||||
if (topic != "devtools-unload") {
|
||||
return;
|
||||
}
|
||||
Services.obs.removeObserver(this, "devtools-unload");
|
||||
|
||||
this._provider.unload("reload");
|
||||
delete this._provider;
|
||||
let mainid = this._mainid;
|
||||
delete this._mainid;
|
||||
this._loadProvider();
|
||||
this.main(mainid);
|
||||
if (this._provider) {
|
||||
this._provider.unload(data);
|
||||
delete this._provider;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -353,3 +309,11 @@ DevToolsLoader.prototype = {
|
|||
this.devtools = this.loader = new DevToolsLoader();
|
||||
|
||||
this.require = this.devtools.require.bind(this.devtools);
|
||||
|
||||
// For compatibility reasons, expose these symbols on "devtools":
|
||||
Object.defineProperty(this.devtools, "Toolbox", {
|
||||
get: () => this.require("devtools/client/framework/toolbox").Toolbox
|
||||
});
|
||||
Object.defineProperty(this.devtools, "TargetFactory", {
|
||||
get: () => this.require("devtools/client/framework/target").TargetFactory
|
||||
});
|
||||
|
|
|
@ -25,8 +25,7 @@ XPCOMUtils.defineLazyGetter(this, "debuggerServer", () => {
|
|||
// settings).
|
||||
let serverLoader = new DevToolsLoader();
|
||||
serverLoader.invisibleToDebugger = true;
|
||||
serverLoader.main("devtools/server/main");
|
||||
let debuggerServer = serverLoader.DebuggerServer;
|
||||
let { DebuggerServer: debuggerServer } = serverLoader.require("devtools/server/main");
|
||||
debuggerServer.init();
|
||||
debuggerServer.addBrowserActors();
|
||||
debuggerServer.allowChromeProcess = !l10n.hiddenByChromePref();
|
||||
|
|
|
@ -1073,7 +1073,7 @@ this.PushService = {
|
|||
Services.telemetry.getHistogramById("PUSH_API_SUBSCRIBE_FAILED").add()
|
||||
if (!reply.error) {
|
||||
console.warn("onRegisterError: Called without valid error message!",
|
||||
reply, String(reply));
|
||||
reply);
|
||||
throw new Error("Registration error");
|
||||
}
|
||||
throw reply.error;
|
||||
|
|
|
@ -945,7 +945,7 @@ nsCSPContext::SendReports(nsISupports* aBlockedContentSource,
|
|||
continue;
|
||||
}
|
||||
|
||||
rv = uploadChannel->SetUploadStream(sis, NS_LITERAL_CSTRING("application/json"), -1);
|
||||
rv = uploadChannel->SetUploadStream(sis, NS_LITERAL_CSTRING("application/csp-report"), -1);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
// if this is an HTTP channel, set the request method to post
|
||||
|
|
|
@ -33,6 +33,14 @@ function makeReportHandler(testpath, message, expectedJSON) {
|
|||
return;
|
||||
}
|
||||
|
||||
// check content-type of report is "application/csp-report"
|
||||
var contentType = request.hasHeader("Content-Type")
|
||||
? request.getHeader("Content-Type") : undefined;
|
||||
if (contentType !== "application/csp-report") {
|
||||
do_throw("violation report should have the 'application/csp-report' " +
|
||||
"content-type, when in fact it is " + contentType.toString())
|
||||
}
|
||||
|
||||
// obtain violation report
|
||||
var reportObj = JSON.parse(
|
||||
NetUtil.readInputStreamToString(
|
||||
|
|
|
@ -81,6 +81,8 @@ AccessibleCaretManager::sCaretsScriptUpdates = false;
|
|||
AccessibleCaretManager::sCaretsAllowDraggingAcrossOtherCaret = true;
|
||||
/*static*/ bool
|
||||
AccessibleCaretManager::sHapticFeedback = false;
|
||||
/*static*/ bool
|
||||
AccessibleCaretManager::sExtendSelectionForPhoneNumber = false;
|
||||
|
||||
AccessibleCaretManager::AccessibleCaretManager(nsIPresShell* aPresShell)
|
||||
: mPresShell(aPresShell)
|
||||
|
@ -110,6 +112,8 @@ AccessibleCaretManager::AccessibleCaretManager(nsIPresShell* aPresShell)
|
|||
"layout.accessiblecaret.allow_dragging_across_other_caret", true);
|
||||
Preferences::AddBoolVarCache(&sHapticFeedback,
|
||||
"layout.accessiblecaret.hapticfeedback");
|
||||
Preferences::AddBoolVarCache(&sExtendSelectionForPhoneNumber,
|
||||
"layout.accessiblecaret.extend_selection_for_phone_number");
|
||||
addedPrefs = true;
|
||||
}
|
||||
}
|
||||
|
@ -820,6 +824,11 @@ AccessibleCaretManager::SelectWord(nsIFrame* aFrame, const nsPoint& aPoint) cons
|
|||
SetSelectionDragState(false);
|
||||
ClearMaintainedSelection();
|
||||
|
||||
// Smart-select phone numbers if possible.
|
||||
if (sExtendSelectionForPhoneNumber) {
|
||||
SelectMoreIfPhoneNumber();
|
||||
}
|
||||
|
||||
return rs;
|
||||
}
|
||||
|
||||
|
@ -841,6 +850,53 @@ AccessibleCaretManager::SetSelectionDragState(bool aState) const
|
|||
#endif
|
||||
}
|
||||
|
||||
void
|
||||
AccessibleCaretManager::SelectMoreIfPhoneNumber() const
|
||||
{
|
||||
SetSelectionDirection(eDirNext);
|
||||
ExtendPhoneNumberSelection(NS_LITERAL_STRING("forward"));
|
||||
|
||||
SetSelectionDirection(eDirPrevious);
|
||||
ExtendPhoneNumberSelection(NS_LITERAL_STRING("backward"));
|
||||
}
|
||||
|
||||
void
|
||||
AccessibleCaretManager::ExtendPhoneNumberSelection(const nsAString& aDirection) const
|
||||
{
|
||||
nsIDocument* doc = mPresShell->GetDocument();
|
||||
|
||||
// Extend the phone number selection until we find a boundary.
|
||||
Selection* selection = GetSelection();
|
||||
|
||||
while (selection) {
|
||||
// Save current Focus position, and extend the selection one char.
|
||||
nsINode* focusNode = selection->GetFocusNode();
|
||||
uint32_t focusOffset = selection->FocusOffset();
|
||||
selection->Modify(NS_LITERAL_STRING("extend"),
|
||||
aDirection,
|
||||
NS_LITERAL_STRING("character"));
|
||||
|
||||
// If the selection didn't change, (can't extend further), we're done.
|
||||
if (selection->GetFocusNode() == focusNode &&
|
||||
selection->FocusOffset() == focusOffset) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If the changed selection isn't a valid phone number, we're done.
|
||||
nsAutoString selectedText;
|
||||
selection->Stringify(selectedText);
|
||||
nsAutoString phoneRegex(NS_LITERAL_STRING("(^\\+)?[0-9\\s,\\-.()*#pw]{1,30}$"));
|
||||
|
||||
if (!nsContentUtils::IsPatternMatching(selectedText, phoneRegex, doc)) {
|
||||
// Backout the undesired selection extend, (collapse to original
|
||||
// Anchor, extend to original Focus), before exit.
|
||||
selection->Collapse(selection->GetAnchorNode(), selection->AnchorOffset());
|
||||
selection->Extend(focusNode, focusOffset);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
AccessibleCaretManager::SetSelectionDirection(nsDirection aDir) const
|
||||
{
|
||||
|
|
|
@ -155,6 +155,12 @@ protected:
|
|||
|
||||
nsresult SelectWord(nsIFrame* aFrame, const nsPoint& aPoint) const;
|
||||
void SetSelectionDragState(bool aState) const;
|
||||
|
||||
// Called to extend a selection if possible that it's a phone number.
|
||||
void SelectMoreIfPhoneNumber() const;
|
||||
// Extend the current phone number selection in the requested direction.
|
||||
void ExtendPhoneNumberSelection(const nsAString& aDirection) const;
|
||||
|
||||
void SetSelectionDirection(nsDirection aDir) const;
|
||||
|
||||
// If aDirection is eDirNext, get the frame for the range start in the first
|
||||
|
@ -282,6 +288,10 @@ protected:
|
|||
// selection bar is always disabled in cursor mode.
|
||||
static bool sSelectionBarEnabled;
|
||||
|
||||
// Preference to allow smarter selection of phone numbers,
|
||||
// when user long presses text to start.
|
||||
static bool sExtendSelectionForPhoneNumber;
|
||||
|
||||
// Preference to show caret in cursor mode when long tapping on an empty
|
||||
// content. This also changes the default update behavior in cursor mode,
|
||||
// which is based on the emptiness of the content, into something more
|
||||
|
|
|
@ -936,6 +936,10 @@ pref("layout.accessiblecaret.allow_script_change_updates", true);
|
|||
// Optionally provide haptic feedback on longPress selection events.
|
||||
pref("layout.accessiblecaret.hapticfeedback", true);
|
||||
|
||||
// Initial text selection on long-press is enhanced to provide
|
||||
// a smarter phone-number selection for direct-dial ActionBar action.
|
||||
pref("layout.accessiblecaret.extend_selection_for_phone_number", true);
|
||||
|
||||
// Disable sending console to logcat on release builds.
|
||||
#ifdef RELEASE_BUILD
|
||||
pref("consoleservice.logcat", false);
|
||||
|
|
|
@ -357,6 +357,12 @@
|
|||
android:name="org.mozilla.gecko.feeds.FeedService">
|
||||
</service>
|
||||
|
||||
<!-- DON'T EXPORT THIS, please! An attacker could delete arbitrary files. -->
|
||||
<service
|
||||
android:exported="false"
|
||||
android:name="org.mozilla.gecko.cleanup.FileCleanupService">
|
||||
</service>
|
||||
|
||||
<receiver
|
||||
android:name="org.mozilla.gecko.feeds.FeedAlarmReceiver"
|
||||
android:exported="false" />
|
||||
|
|
|
@ -20,6 +20,7 @@ import org.mozilla.gecko.GeckoProfileDirectories.NoMozillaDirectoryException;
|
|||
import org.mozilla.gecko.Tabs.TabEvents;
|
||||
import org.mozilla.gecko.animation.PropertyAnimator;
|
||||
import org.mozilla.gecko.animation.ViewHelper;
|
||||
import org.mozilla.gecko.cleanup.FileCleanupController;
|
||||
import org.mozilla.gecko.db.BrowserContract;
|
||||
import org.mozilla.gecko.db.BrowserDB;
|
||||
import org.mozilla.gecko.db.SuggestedSites;
|
||||
|
@ -1062,6 +1063,11 @@ public class BrowserApp extends GeckoApp
|
|||
// have been shown.
|
||||
GuestSession.hideNotification(BrowserApp.this);
|
||||
}
|
||||
|
||||
// It'd be better to launch this once, in onCreate, but there's ambiguity for when the
|
||||
// profile is created so we run here instead. Don't worry, call start short-circuits pretty fast.
|
||||
final SharedPreferences sharedPrefs = GeckoSharedPrefs.forProfileName(BrowserApp.this, profile.getName());
|
||||
FileCleanupController.startIfReady(BrowserApp.this, sharedPrefs, profile.getDir().getAbsolutePath());
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
@ -557,13 +557,21 @@ public final class GeckoProfile {
|
|||
return CUSTOM_PROFILE.equals(mName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the directory backing the profile. This method acts
|
||||
* as a lazy initializer for the GeckoProfile instance.
|
||||
*/
|
||||
@RobocopTarget
|
||||
public synchronized File getDir() {
|
||||
forceCreate();
|
||||
return mProfileDir;
|
||||
}
|
||||
|
||||
public synchronized GeckoProfile forceCreate() {
|
||||
/**
|
||||
* Forces profile creation. Consider using {@link #getDir()} to initialize the profile instead - it is the
|
||||
* lazy initializer and, for our code reasoning abilities, we should initialize the profile in one place.
|
||||
*/
|
||||
private synchronized GeckoProfile forceCreate() {
|
||||
if (mProfileDir != null) {
|
||||
return this;
|
||||
}
|
||||
|
|
|
@ -377,7 +377,7 @@ public class GeckoThread extends Thread {
|
|||
} else {
|
||||
// Make sure a profile exists.
|
||||
final GeckoProfile profile = getProfile();
|
||||
profile.forceCreate();
|
||||
profile.getDir(); // call the lazy initializer
|
||||
|
||||
// If args don't include the profile, make sure it's included.
|
||||
if (args == null || !args.matches(".*\\B-(P|profile)\\s+\\S+.*")) {
|
||||
|
|
|
@ -0,0 +1,81 @@
|
|||
/*
|
||||
* 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.cleanup;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.support.annotation.VisibleForTesting;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* Encapsulates the code to run the {@link FileCleanupService}. Call
|
||||
* {@link #startIfReady(Context, SharedPreferences, String)} to start the clean-up.
|
||||
*
|
||||
* Note: for simplicity, the current implementation does not cache which
|
||||
* files have been cleaned up and will attempt to delete the same files
|
||||
* each time it is run. If the file deletion list grows large, consider
|
||||
* keeping a cache.
|
||||
*/
|
||||
public class FileCleanupController {
|
||||
|
||||
private static final long MILLIS_BETWEEN_CLEANUPS = TimeUnit.DAYS.toMillis(7);
|
||||
@VisibleForTesting static final String PREF_LAST_CLEANUP_MILLIS = "cleanup.lastFileCleanupMillis";
|
||||
|
||||
// These will be prepended with the path of the profile we're cleaning up.
|
||||
private static final String[] PROFILE_FILES_TO_CLEANUP = new String[] {
|
||||
"health.db",
|
||||
"health.db-journal",
|
||||
"health.db-shm",
|
||||
"health.db-wal",
|
||||
};
|
||||
|
||||
/**
|
||||
* Starts the clean-up if it's time to clean-up, otherwise returns. For simplicity,
|
||||
* it does not schedule the cleanup for some point in the future - this method will
|
||||
* have to be called again (i.e. polled) in order to run the clean-up service.
|
||||
*
|
||||
* @param context Context of the calling {@link android.app.Activity}
|
||||
* @param sharedPrefs The {@link SharedPreferences} instance to store the controller state to
|
||||
* @param profilePath The path to the profile the service should clean-up files from
|
||||
*/
|
||||
public static void startIfReady(final Context context, final SharedPreferences sharedPrefs, final String profilePath) {
|
||||
if (!isCleanupReady(sharedPrefs)) {
|
||||
return;
|
||||
}
|
||||
|
||||
recordCleanupScheduled(sharedPrefs);
|
||||
|
||||
final Intent fileCleanupIntent = new Intent(context, FileCleanupService.class);
|
||||
fileCleanupIntent.setAction(FileCleanupService.ACTION_DELETE_FILES);
|
||||
fileCleanupIntent.putExtra(FileCleanupService.EXTRA_FILE_PATHS_TO_DELETE, getFilesToCleanup(profilePath + "/"));
|
||||
context.startService(fileCleanupIntent);
|
||||
}
|
||||
|
||||
private static boolean isCleanupReady(final SharedPreferences sharedPrefs) {
|
||||
final long lastCleanupMillis = sharedPrefs.getLong(PREF_LAST_CLEANUP_MILLIS, -1);
|
||||
return lastCleanupMillis + MILLIS_BETWEEN_CLEANUPS < System.currentTimeMillis();
|
||||
}
|
||||
|
||||
private static void recordCleanupScheduled(final SharedPreferences sharedPrefs) {
|
||||
final SharedPreferences.Editor editor = sharedPrefs.edit();
|
||||
editor.putLong(PREF_LAST_CLEANUP_MILLIS, System.currentTimeMillis()).apply();
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
static ArrayList<String> getFilesToCleanup(final String profilePath) {
|
||||
final ArrayList<String> out = new ArrayList<>(PROFILE_FILES_TO_CLEANUP.length);
|
||||
for (final String path : PROFILE_FILES_TO_CLEANUP) {
|
||||
// Append a file separator, just in-case the caller didn't include one.
|
||||
out.add(profilePath + File.separator + path);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,80 @@
|
|||
/*
|
||||
* 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.cleanup;
|
||||
|
||||
import android.app.IntentService;
|
||||
import android.content.Intent;
|
||||
import android.util.Log;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* An IntentService to delete files.
|
||||
*
|
||||
* It takes an {@link ArrayList} of String file paths to delete via the extra
|
||||
* {@link #EXTRA_FILE_PATHS_TO_DELETE}. If these file paths are directories, they will
|
||||
* not be traversed recursively and will only be deleted if empty. This is to avoid accidentally
|
||||
* trashing a users' profile if a folder is accidentally listed.
|
||||
*
|
||||
* An IntentService was chosen because:
|
||||
* * It generally won't be killed when the Activity is
|
||||
* * (unlike HandlerThread) The system handles scheduling, prioritizing,
|
||||
* and shutting down the underlying background thread
|
||||
* * (unlike an existing background thread) We don't block our background operations
|
||||
* for this, which doesn't directly affect the user.
|
||||
*
|
||||
* The major trade-off is that this Service is very dangerous if it's exported... so don't do that!
|
||||
*/
|
||||
public class FileCleanupService extends IntentService {
|
||||
private static final String LOGTAG = "Gecko" + FileCleanupService.class.getSimpleName();
|
||||
private static final String WORKER_THREAD_NAME = LOGTAG + "Worker";
|
||||
|
||||
public static final String ACTION_DELETE_FILES = "org.mozilla.gecko.intent.action.DELETE_FILES";
|
||||
public static final String EXTRA_FILE_PATHS_TO_DELETE = "org.mozilla.gecko.file_paths_to_delete";
|
||||
|
||||
public FileCleanupService() {
|
||||
super(WORKER_THREAD_NAME);
|
||||
|
||||
// We're likely to get scheduled again - let's wait until then in order to avoid:
|
||||
// * The coding complexity of re-running this
|
||||
// * Consuming system resources: we were probably killed for resource conservation purposes
|
||||
setIntentRedelivery(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onHandleIntent(final Intent intent) {
|
||||
if (!isIntentValid(intent)) {
|
||||
return;
|
||||
}
|
||||
|
||||
final ArrayList<String> filesToDelete = intent.getStringArrayListExtra(EXTRA_FILE_PATHS_TO_DELETE);
|
||||
for (final String path : filesToDelete) {
|
||||
final File file = new File(path);
|
||||
file.delete();
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean isIntentValid(final Intent intent) {
|
||||
if (intent == null) {
|
||||
Log.w(LOGTAG, "Received null intent");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!intent.getAction().equals(ACTION_DELETE_FILES)) {
|
||||
Log.w(LOGTAG, "Received unknown intent action: " + intent.getAction());
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!intent.hasExtra(EXTRA_FILE_PATHS_TO_DELETE)) {
|
||||
Log.w(LOGTAG, "Received intent with no files extra");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
|
@ -24,25 +24,4 @@ public class TelemetryConstants {
|
|||
|
||||
public static final String PREF_SERVER_URL = "telemetry-serverUrl";
|
||||
public static final String PREF_SEQ_COUNT = "telemetry-seqCount";
|
||||
|
||||
public static class CorePing {
|
||||
private CorePing() { /* To prevent instantiation */ }
|
||||
|
||||
public static final String NAME = "core";
|
||||
public static final int VERSION_VALUE = 4; // For version history, see toolkit/components/telemetry/docs/core-ping.rst
|
||||
public static final String OS_VALUE = "Android";
|
||||
|
||||
public static final String ARCHITECTURE = "arch";
|
||||
public static final String CLIENT_ID = "clientId";
|
||||
public static final String DEFAULT_SEARCH_ENGINE = "defaultSearch";
|
||||
public static final String DEVICE = "device";
|
||||
public static final String DISTRIBUTION_ID = "distributionId";
|
||||
public static final String EXPERIMENTS = "experiments";
|
||||
public static final String LOCALE = "locale";
|
||||
public static final String OS_ATTR = "os";
|
||||
public static final String OS_VERSION = "osversion";
|
||||
public static final String PROFILE_CREATION_DATE = "profileDate";
|
||||
public static final String SEQ = "seq";
|
||||
public static final String VERSION_ATTR = "v";
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,107 +0,0 @@
|
|||
/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
|
||||
* 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.telemetry;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Build;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import org.mozilla.gecko.AppConstants;
|
||||
import org.mozilla.gecko.Locales;
|
||||
import org.mozilla.gecko.sync.ExtendedJSONObject;
|
||||
import org.mozilla.gecko.telemetry.TelemetryConstants.CorePing;
|
||||
import org.mozilla.gecko.util.Experiments;
|
||||
import org.mozilla.gecko.util.StringUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* A class with static methods to generate the various Java-created Telemetry pings to upload to the telemetry server.
|
||||
*/
|
||||
public class TelemetryPingGenerator {
|
||||
|
||||
// In the server url, the initial path directly after the "scheme://host:port/"
|
||||
private static final String SERVER_INITIAL_PATH = "submit/telemetry";
|
||||
|
||||
/**
|
||||
* Returns a url of the format:
|
||||
* http://hostname/submit/telemetry/docId/docType/appName/appVersion/appUpdateChannel/appBuildID
|
||||
*
|
||||
* @param docId A unique document ID for the ping associated with the upload to this server
|
||||
* @param serverURLSchemeHostPort The server url with the scheme, host, and port (e.g. "http://mozilla.org:80")
|
||||
* @param docType The name of the ping (e.g. "main")
|
||||
* @return a url at which to POST the telemetry data to
|
||||
*/
|
||||
private static String getTelemetryServerURL(final String docId, final String serverURLSchemeHostPort,
|
||||
final String docType) {
|
||||
final String appName = AppConstants.MOZ_APP_BASENAME;
|
||||
final String appVersion = AppConstants.MOZ_APP_VERSION;
|
||||
final String appUpdateChannel = AppConstants.MOZ_UPDATE_CHANNEL;
|
||||
final String appBuildId = AppConstants.MOZ_APP_BUILDID;
|
||||
|
||||
// The compiler will optimize a single String concatenation into a StringBuilder statement.
|
||||
// If you change this `return`, be sure to keep it as a single statement to keep it optimized!
|
||||
return serverURLSchemeHostPort + '/' +
|
||||
SERVER_INITIAL_PATH + '/' +
|
||||
docId + '/' +
|
||||
docType + '/' +
|
||||
appName + '/' +
|
||||
appVersion + '/' +
|
||||
appUpdateChannel + '/' +
|
||||
appBuildId;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param docId A unique document ID for the ping associated with the upload to this server
|
||||
* @param clientId The client ID of this profile (from Gecko)
|
||||
* @param serverURLSchemeHostPort The server url with the scheme, host, and port (e.g. "http://mozilla.org:80")
|
||||
* @param profileCreationDateDays The profile creation date in days to the UNIX epoch, NOT MILLIS.
|
||||
* @throws IOException when client ID could not be created
|
||||
*/
|
||||
public static TelemetryPing createCorePing(final Context context, final String docId, final String clientId,
|
||||
final String serverURLSchemeHostPort, final int seq, final long profileCreationDateDays,
|
||||
@Nullable final String distributionId, @Nullable final String defaultSearchEngine) {
|
||||
final String serverURL = getTelemetryServerURL(docId, serverURLSchemeHostPort, CorePing.NAME);
|
||||
final ExtendedJSONObject payload =
|
||||
createCorePingPayload(context, clientId, seq, profileCreationDateDays, distributionId, defaultSearchEngine);
|
||||
return new TelemetryPing(serverURL, payload);
|
||||
}
|
||||
|
||||
private static ExtendedJSONObject createCorePingPayload(final Context context, final String clientId,
|
||||
final int seq, final long profileCreationDate, @Nullable final String distributionId,
|
||||
@Nullable final String defaultSearchEngine) {
|
||||
final ExtendedJSONObject ping = new ExtendedJSONObject();
|
||||
ping.put(CorePing.VERSION_ATTR, CorePing.VERSION_VALUE);
|
||||
ping.put(CorePing.OS_ATTR, CorePing.OS_VALUE);
|
||||
|
||||
// We limit the device descriptor to 32 characters because it can get long. We give fewer characters to the
|
||||
// manufacturer because we're less likely to have manufacturers with similar names than we are for a
|
||||
// manufacturer to have two devices with the similar names (e.g. Galaxy S6 vs. Galaxy Note 6).
|
||||
final String deviceDescriptor =
|
||||
StringUtils.safeSubstring(Build.MANUFACTURER, 0, 12) + '-' + StringUtils.safeSubstring(Build.MODEL, 0, 19);
|
||||
|
||||
ping.put(CorePing.ARCHITECTURE, AppConstants.ANDROID_CPU_ARCH);
|
||||
ping.put(CorePing.CLIENT_ID, clientId);
|
||||
ping.put(CorePing.DEFAULT_SEARCH_ENGINE, TextUtils.isEmpty(defaultSearchEngine) ? null : defaultSearchEngine);
|
||||
ping.put(CorePing.DEVICE, deviceDescriptor);
|
||||
ping.put(CorePing.LOCALE, Locales.getLanguageTag(Locale.getDefault()));
|
||||
ping.put(CorePing.OS_VERSION, Integer.toString(Build.VERSION.SDK_INT)); // A String for cross-platform reasons.
|
||||
ping.put(CorePing.SEQ, seq);
|
||||
ping.putArray(CorePing.EXPERIMENTS, Experiments.getActiveExperiments(context));
|
||||
|
||||
// Optional.
|
||||
if (distributionId != null) {
|
||||
ping.put(CorePing.DISTRIBUTION_ID, distributionId);
|
||||
}
|
||||
|
||||
// `null` indicates failure more clearly than < 0.
|
||||
final Long finalProfileCreationDate = (profileCreationDate < 0) ? null : profileCreationDate;
|
||||
ping.put(CorePing.PROFILE_CREATION_DATE, finalProfileCreationDate);
|
||||
return ping;
|
||||
}
|
||||
}
|
|
@ -10,6 +10,7 @@ import android.content.SharedPreferences;
|
|||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.annotation.WorkerThread;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
import ch.boye.httpclientandroidlib.HttpResponse;
|
||||
import ch.boye.httpclientandroidlib.client.ClientProtocolException;
|
||||
|
@ -21,6 +22,8 @@ import org.mozilla.gecko.preferences.GeckoPreferences;
|
|||
import org.mozilla.gecko.sync.net.BaseResource;
|
||||
import org.mozilla.gecko.sync.net.BaseResourceDelegate;
|
||||
import org.mozilla.gecko.sync.net.Resource;
|
||||
import org.mozilla.gecko.telemetry.pings.TelemetryCorePingBuilder;
|
||||
import org.mozilla.gecko.telemetry.pings.TelemetryPing;
|
||||
import org.mozilla.gecko.util.StringUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
|
@ -170,7 +173,6 @@ public class TelemetryUploadService extends BackgroundService {
|
|||
private void uploadCorePing(@NonNull final String docId, final int seq, @NonNull final String profileName,
|
||||
@NonNull final String profilePath, @Nullable final String defaultSearchEngine) {
|
||||
final GeckoProfile profile = GeckoProfile.get(this, profileName, profilePath);
|
||||
final long profileCreationDate = getProfileCreationDate(profile);
|
||||
final String clientId;
|
||||
try {
|
||||
clientId = profile.getClientId();
|
||||
|
@ -184,9 +186,20 @@ public class TelemetryUploadService extends BackgroundService {
|
|||
// TODO (bug 1241685): Sync this preference with the gecko preference.
|
||||
final String serverURLSchemeHostPort =
|
||||
sharedPrefs.getString(TelemetryConstants.PREF_SERVER_URL, TelemetryConstants.DEFAULT_SERVER_URL);
|
||||
|
||||
final long profileCreationDate = getProfileCreationDate(profile);
|
||||
final TelemetryCorePingBuilder builder = new TelemetryCorePingBuilder(this, serverURLSchemeHostPort)
|
||||
.setClientID(clientId)
|
||||
.setDefaultSearchEngine(TextUtils.isEmpty(defaultSearchEngine) ? null : defaultSearchEngine)
|
||||
.setProfileCreationDate(profileCreationDate < 0 ? null : profileCreationDate)
|
||||
.setSequenceNumber(seq);
|
||||
|
||||
final String distributionId = sharedPrefs.getString(DistributionStoreCallback.PREF_DISTRIBUTION_ID, null);
|
||||
final TelemetryPing corePing = TelemetryPingGenerator.createCorePing(this, docId, clientId,
|
||||
serverURLSchemeHostPort, seq, profileCreationDate, distributionId, defaultSearchEngine);
|
||||
if (distributionId != null) {
|
||||
builder.setOptDistributionID(distributionId);
|
||||
}
|
||||
|
||||
final TelemetryPing corePing = builder.build();
|
||||
final CorePingResultDelegate resultDelegate = new CorePingResultDelegate();
|
||||
uploadPing(corePing, resultDelegate);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,138 @@
|
|||
/*
|
||||
* 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.telemetry.pings;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Build;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
|
||||
import org.mozilla.gecko.AppConstants;
|
||||
import org.mozilla.gecko.Locales;
|
||||
import org.mozilla.gecko.util.Experiments;
|
||||
import org.mozilla.gecko.util.StringUtils;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* Builds a {@link TelemetryPing} representing a core ping.
|
||||
*
|
||||
* See https://gecko.readthedocs.org/en/latest/toolkit/components/telemetry/telemetry/core-ping.html
|
||||
* for details on the core ping.
|
||||
*/
|
||||
public class TelemetryCorePingBuilder extends TelemetryPingBuilder {
|
||||
|
||||
private static final String NAME = "core";
|
||||
private static final int VERSION_VALUE = 4; // For version history, see toolkit/components/telemetry/docs/core-ping.rst
|
||||
private static final String OS_VALUE = "Android";
|
||||
|
||||
private static final String ARCHITECTURE = "arch";
|
||||
private static final String CLIENT_ID = "clientId";
|
||||
private static final String DEFAULT_SEARCH_ENGINE = "defaultSearch";
|
||||
private static final String DEVICE = "device";
|
||||
private static final String DISTRIBUTION_ID = "distributionId";
|
||||
private static final String EXPERIMENTS = "experiments";
|
||||
private static final String LOCALE = "locale";
|
||||
private static final String OS_ATTR = "os";
|
||||
private static final String OS_VERSION = "osversion";
|
||||
private static final String PROFILE_CREATION_DATE = "profileDate";
|
||||
private static final String SEQ = "seq";
|
||||
private static final String VERSION_ATTR = "v";
|
||||
|
||||
public TelemetryCorePingBuilder(final Context context, final String serverURLSchemeHostPort) {
|
||||
super(serverURLSchemeHostPort);
|
||||
initPayloadConstants(context);
|
||||
}
|
||||
|
||||
private void initPayloadConstants(final Context context) {
|
||||
payload.put(VERSION_ATTR, VERSION_VALUE);
|
||||
payload.put(OS_ATTR, OS_VALUE);
|
||||
|
||||
// We limit the device descriptor to 32 characters because it can get long. We give fewer characters to the
|
||||
// manufacturer because we're less likely to have manufacturers with similar names than we are for a
|
||||
// manufacturer to have two devices with the similar names (e.g. Galaxy S6 vs. Galaxy Note 6).
|
||||
final String deviceDescriptor =
|
||||
StringUtils.safeSubstring(Build.MANUFACTURER, 0, 12) + '-' + StringUtils.safeSubstring(Build.MODEL, 0, 19);
|
||||
|
||||
payload.put(ARCHITECTURE, AppConstants.ANDROID_CPU_ARCH);
|
||||
payload.put(DEVICE, deviceDescriptor);
|
||||
payload.put(LOCALE, Locales.getLanguageTag(Locale.getDefault()));
|
||||
payload.put(OS_VERSION, Integer.toString(Build.VERSION.SDK_INT)); // A String for cross-platform reasons.
|
||||
payload.putArray(EXPERIMENTS, Experiments.getActiveExperiments(context));
|
||||
}
|
||||
|
||||
@Override
|
||||
String getDocType() {
|
||||
return NAME;
|
||||
}
|
||||
|
||||
@Override
|
||||
String[] getMandatoryFields() {
|
||||
return new String[] {
|
||||
ARCHITECTURE,
|
||||
CLIENT_ID,
|
||||
DEFAULT_SEARCH_ENGINE,
|
||||
DEVICE,
|
||||
LOCALE,
|
||||
OS_ATTR,
|
||||
OS_VERSION,
|
||||
PROFILE_CREATION_DATE,
|
||||
SEQ,
|
||||
VERSION_ATTR,
|
||||
};
|
||||
}
|
||||
|
||||
public TelemetryCorePingBuilder setClientID(@NonNull final String clientID) {
|
||||
if (clientID == null) {
|
||||
throw new IllegalArgumentException("Expected non-null clientID");
|
||||
}
|
||||
payload.put(CLIENT_ID, clientID);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param engine the default search engine identifier, or null if there is an error.
|
||||
*/
|
||||
public TelemetryCorePingBuilder setDefaultSearchEngine(@Nullable final String engine) {
|
||||
if (engine != null && engine.isEmpty()) {
|
||||
throw new IllegalArgumentException("Received empty string. Expected identifier or null.");
|
||||
}
|
||||
payload.put(DEFAULT_SEARCH_ENGINE, engine);
|
||||
return this;
|
||||
}
|
||||
|
||||
public TelemetryCorePingBuilder setOptDistributionID(@NonNull final String distributionID) {
|
||||
if (distributionID == null) {
|
||||
throw new IllegalArgumentException("Expected non-null distribution ID");
|
||||
}
|
||||
payload.put(DISTRIBUTION_ID, distributionID);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param date a positive date value, or null if there is an error.
|
||||
*/
|
||||
public TelemetryCorePingBuilder setProfileCreationDate(@Nullable final Long date) {
|
||||
if (date != null && date < 0) {
|
||||
throw new IllegalArgumentException("Expect positive date value. Received: " + date);
|
||||
}
|
||||
payload.put(PROFILE_CREATION_DATE, date);
|
||||
return this;
|
||||
}
|
||||
|
||||
// TODO (mcomella): We can potentially build two pings with the same seq no if we leave seq as an argument.
|
||||
/**
|
||||
* @param seq a positive sequence number.
|
||||
*/
|
||||
public TelemetryCorePingBuilder setSequenceNumber(final int seq) {
|
||||
if (seq < 0) {
|
||||
throw new IllegalArgumentException("Expected positive sequence number. Recived: " + seq);
|
||||
}
|
||||
payload.put(SEQ, seq);
|
||||
return this;
|
||||
}
|
||||
}
|
|
@ -3,12 +3,15 @@
|
|||
* 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.telemetry;
|
||||
package org.mozilla.gecko.telemetry.pings;
|
||||
|
||||
import org.mozilla.gecko.sync.ExtendedJSONObject;
|
||||
|
||||
/**
|
||||
* Container for telemetry data and the data necessary to upload it.
|
||||
*
|
||||
* If you want to create one of these, consider extending
|
||||
* {@link TelemetryPingBuilder} or one of its descendants.
|
||||
*/
|
||||
public class TelemetryPing {
|
||||
private final String url;
|
|
@ -0,0 +1,88 @@
|
|||
/*
|
||||
* 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.telemetry.pings;
|
||||
|
||||
import org.mozilla.gecko.AppConstants;
|
||||
import org.mozilla.gecko.sync.ExtendedJSONObject;
|
||||
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* A generic Builder for {@link TelemetryPing} instances. Each overriding class is
|
||||
* expected to create a specific type of ping (e.g. "core").
|
||||
*
|
||||
* This base class handles the common ping operations under the hood:
|
||||
* * Validating mandatory fields
|
||||
* * Forming the server url
|
||||
*/
|
||||
abstract class TelemetryPingBuilder {
|
||||
// In the server url, the initial path directly after the "scheme://host:port/"
|
||||
private static final String SERVER_INITIAL_PATH = "submit/telemetry";
|
||||
|
||||
private final String serverUrl;
|
||||
protected final ExtendedJSONObject payload;
|
||||
|
||||
public TelemetryPingBuilder(final String serverURLSchemeHostPort) {
|
||||
serverUrl = getTelemetryServerURL(getDocType(), serverURLSchemeHostPort);
|
||||
payload = new ExtendedJSONObject();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the name of the ping (e.g. "core")
|
||||
*/
|
||||
abstract String getDocType();
|
||||
|
||||
/**
|
||||
* @return the fields that are mandatory for the resultant ping to be uploaded to
|
||||
* the server. These will be validated before the ping is built.
|
||||
*/
|
||||
abstract String[] getMandatoryFields();
|
||||
|
||||
public TelemetryPing build() {
|
||||
validatePayload();
|
||||
return new TelemetryPing(serverUrl, payload);
|
||||
}
|
||||
|
||||
private void validatePayload() {
|
||||
final Set<String> keySet = payload.keySet();
|
||||
for (final String mandatoryField : getMandatoryFields()) {
|
||||
if (!keySet.contains(mandatoryField)) {
|
||||
throw new IllegalArgumentException("Builder does not contain mandatory field: " +
|
||||
mandatoryField);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a url of the format:
|
||||
* http://hostname/submit/telemetry/docId/docType/appName/appVersion/appUpdateChannel/appBuildID
|
||||
*
|
||||
* @param serverURLSchemeHostPort The server url with the scheme, host, and port (e.g. "http://mozilla.org:80")
|
||||
* @param docType The name of the ping (e.g. "main")
|
||||
* @return a url at which to POST the telemetry data to
|
||||
*/
|
||||
private static String getTelemetryServerURL(final String docType,
|
||||
final String serverURLSchemeHostPort) {
|
||||
final String docId = UUID.randomUUID().toString();
|
||||
final String appName = AppConstants.MOZ_APP_BASENAME;
|
||||
final String appVersion = AppConstants.MOZ_APP_VERSION;
|
||||
final String appUpdateChannel = AppConstants.MOZ_UPDATE_CHANNEL;
|
||||
final String appBuildId = AppConstants.MOZ_APP_BUILDID;
|
||||
|
||||
// The compiler will optimize a single String concatenation into a StringBuilder statement.
|
||||
// If you change this `return`, be sure to keep it as a single statement to keep it optimized!
|
||||
return serverURLSchemeHostPort + '/' +
|
||||
SERVER_INITIAL_PATH + '/' +
|
||||
docId + '/' +
|
||||
docType + '/' +
|
||||
appName + '/' +
|
||||
appVersion + '/' +
|
||||
appUpdateChannel + '/' +
|
||||
appBuildId;
|
||||
}
|
||||
}
|
|
@ -107,7 +107,7 @@
|
|||
<!ENTITY settings "Settings">
|
||||
<!ENTITY settings_title "Settings">
|
||||
<!ENTITY pref_category_general "General">
|
||||
<!ENTITY pref_category_general_summary2 "Home, language, URL bar">
|
||||
<!ENTITY pref_category_general_summary3 "Home, language, tab queue">
|
||||
|
||||
<!-- Localization note (pref_category_language) : This is the preferences
|
||||
section in which the user picks the locale in which to display Firefox
|
||||
|
@ -146,11 +146,11 @@
|
|||
<!ENTITY overlay_no_synced_devices "No Firefox Account connected devices found">
|
||||
|
||||
<!ENTITY pref_category_search3 "Search">
|
||||
<!ENTITY pref_category_search_summary "Customize your search providers">
|
||||
<!ENTITY pref_category_search_summary2 "Add, set default, show suggestions">
|
||||
<!ENTITY pref_category_accessibility "Accessibility">
|
||||
<!ENTITY pref_category_accessibility_summary2 "Text size, zoom, voice input">
|
||||
<!ENTITY pref_category_privacy_short "Privacy">
|
||||
<!ENTITY pref_category_privacy_summary3 "Tracking, cookies, data choices">
|
||||
<!ENTITY pref_category_privacy_summary4 "Tracking, logins, data choices">
|
||||
<!ENTITY pref_category_vendor2 "&vendorShortName; &brandShortName;">
|
||||
<!ENTITY pref_category_vendor_summary2 "About &brandShortName;, FAQs, feedback">
|
||||
<!ENTITY pref_category_datareporting "Data choices">
|
||||
|
@ -168,7 +168,7 @@
|
|||
it is applicable. -->
|
||||
<!ENTITY pref_search_hint2 "TIP: Add any website to your list of search providers by long-pressing on its search field and then touching the &formatI; icon.">
|
||||
<!ENTITY pref_category_advanced "Advanced">
|
||||
<!ENTITY pref_category_advanced_summary2 "Restore tabs, plugins, developer tools">
|
||||
<!ENTITY pref_category_advanced_summary3 "Restore tabs, data saver, developer tools">
|
||||
<!ENTITY pref_category_notifications "Notifications">
|
||||
<!ENTITY pref_category_notifications_summary "New features, website updates">
|
||||
<!ENTITY pref_content_notifications "Website updates">
|
||||
|
@ -349,7 +349,7 @@ size. -->
|
|||
<!ENTITY pref_private_data_downloadFiles2 "Downloads">
|
||||
<!ENTITY pref_private_data_syncedTabs "Synced tabs">
|
||||
|
||||
|
||||
<!ENTITY pref_default_browser "Make default browser">
|
||||
<!ENTITY pref_about_firefox "About &brandShortName;">
|
||||
<!ENTITY pref_vendor_faqs "FAQs">
|
||||
<!ENTITY pref_vendor_feedback "Give feedback">
|
||||
|
|
|
@ -206,6 +206,8 @@ gbjar.sources += ['java/org/mozilla/gecko/' + x for x in [
|
|||
'BootReceiver.java',
|
||||
'BrowserApp.java',
|
||||
'BrowserLocaleManager.java',
|
||||
'cleanup/FileCleanupController.java',
|
||||
'cleanup/FileCleanupService.java',
|
||||
'ContactService.java',
|
||||
'ContextGetter.java',
|
||||
'CrashHandler.java',
|
||||
|
@ -572,9 +574,10 @@ gbjar.sources += ['java/org/mozilla/gecko/' + x for x in [
|
|||
'tabs/TabsPanel.java',
|
||||
'tabs/TabsPanelThumbnailView.java',
|
||||
'Telemetry.java',
|
||||
'telemetry/pings/TelemetryCorePingBuilder.java',
|
||||
'telemetry/pings/TelemetryPing.java',
|
||||
'telemetry/pings/TelemetryPingBuilder.java',
|
||||
'telemetry/TelemetryConstants.java',
|
||||
'telemetry/TelemetryPing.java',
|
||||
'telemetry/TelemetryPingGenerator.java',
|
||||
'telemetry/TelemetryUploadService.java',
|
||||
'TelemetryContract.java',
|
||||
'text/FloatingActionModeCallback.java',
|
||||
|
|
|
@ -70,6 +70,11 @@
|
|||
gecko:entryKeys="@array/pref_private_data_keys"
|
||||
gecko:initialValues="@array/pref_private_data_defaults" />
|
||||
|
||||
<org.mozilla.gecko.preferences.LinkPreference android:key="android.not_a_preference.default_browser.link"
|
||||
android:title="@string/pref_default_browser"
|
||||
android:persistent="false"
|
||||
url="https://support.mozilla.org/kb/make-firefox-default-browser-android?utm_source=inproduct&utm_medium=settings&utm_campaign=mobileandroid"/>
|
||||
|
||||
<PreferenceScreen android:title="@string/pref_category_vendor"
|
||||
android:summary="@string/pref_category_vendor_summary"
|
||||
android:fragment="org.mozilla.gecko.preferences.GeckoPreferenceFragment" >
|
||||
|
|
|
@ -76,13 +76,6 @@
|
|||
|
||||
<string name="url_bar_default_text">&url_bar_default_text2;</string>
|
||||
|
||||
<!-- https://input.mozilla.org/feedback/android/%VERSION%/%CHANNEL%/?utm_source=feedback-settings
|
||||
This should be kept in sync with the "app.feedbackURL" pref defined in mobile.js -->
|
||||
<string name="feedback_link">https://input.mozilla.org/feedback/android/&formatS1;/&formatS2;/?utm_source=feedback-settings</string>
|
||||
|
||||
<!-- https://support.mozilla.org/1/mobile/%VERSION%/%OS%/%LOCALE%/faq -->
|
||||
<string name="faq_link">https://support.mozilla.org/1/mobile/&formatS1;/&formatS2;/&formatS3;/faq</string>
|
||||
|
||||
<!-- https://support.mozilla.org/1/mobile/%VERSION%/%OS%/%LOCALE%/ -->
|
||||
<string name="help_link">https://support.mozilla.org/1/mobile/&formatS1;/&formatS2;/&formatS3;/</string>
|
||||
<string name="help_menu">&help_menu;</string>
|
||||
|
@ -136,14 +129,14 @@
|
|||
<string name="settings">&settings;</string>
|
||||
<string name="settings_title">&settings_title;</string>
|
||||
<string name="pref_category_general">&pref_category_general;</string>
|
||||
<string name="pref_category_general_summary">&pref_category_general_summary2;</string>
|
||||
<string name="pref_category_general_summary">&pref_category_general_summary3;</string>
|
||||
|
||||
<string name="pref_category_search">&pref_category_search3;</string>
|
||||
<string name="pref_category_search_summary">&pref_category_search_summary;</string>
|
||||
<string name="pref_category_search_summary">&pref_category_search_summary2;</string>
|
||||
<string name="pref_category_accessibility">&pref_category_accessibility;</string>
|
||||
<string name="pref_category_accessibility_summary">&pref_category_accessibility_summary2;</string>
|
||||
<string name="pref_category_privacy_short">&pref_category_privacy_short;</string>
|
||||
<string name="pref_category_privacy_summary">&pref_category_privacy_summary3;</string>
|
||||
<string name="pref_category_privacy_summary">&pref_category_privacy_summary4;</string>
|
||||
<string name="pref_category_vendor">&pref_category_vendor2;</string>
|
||||
<string name="pref_category_vendor_summary">&pref_category_vendor_summary2;</string>
|
||||
<string name="pref_category_datareporting">&pref_category_datareporting;</string>
|
||||
|
@ -161,7 +154,7 @@
|
|||
<string name="locale_system_default">&locale_system_default;</string>
|
||||
|
||||
<string name="pref_category_advanced">&pref_category_advanced;</string>
|
||||
<string name="pref_category_advanced_summary">&pref_category_advanced_summary2;</string>
|
||||
<string name="pref_category_advanced_summary">&pref_category_advanced_summary3;</string>
|
||||
<string name="pref_developer_remotedebugging_usb">&pref_developer_remotedebugging_usb;</string>
|
||||
<string name="pref_developer_remotedebugging_wifi">&pref_developer_remotedebugging_wifi;</string>
|
||||
<string name="pref_developer_remotedebugging_wifi_disabled_summary">&pref_developer_remotedebugging_wifi_disabled_summary;</string>
|
||||
|
@ -306,9 +299,18 @@
|
|||
<string name="content_notification_action_settings">&content_notification_action_settings;</string>
|
||||
<string name="content_notification_updated_on">&content_notification_updated_on;</string>
|
||||
|
||||
<string name="pref_default_browser">&pref_default_browser;</string>
|
||||
|
||||
<string name="pref_about_firefox">&pref_about_firefox;</string>
|
||||
|
||||
<string name="pref_vendor_faqs">&pref_vendor_faqs;</string>
|
||||
<!-- https://support.mozilla.org/1/mobile/%VERSION%/%OS%/%LOCALE%/faq -->
|
||||
<string name="faq_link">https://support.mozilla.org/1/mobile/&formatS1;/&formatS2;/&formatS3;/faq</string>
|
||||
|
||||
<string name="pref_vendor_feedback">&pref_vendor_feedback;</string>
|
||||
<!-- https://input.mozilla.org/feedback/android/%VERSION%/%CHANNEL%/?utm_source=feedback-settings
|
||||
This should be kept in sync with the "app.feedbackURL" pref defined in mobile.js -->
|
||||
<string name="feedback_link">https://input.mozilla.org/feedback/android/&formatS1;/&formatS2;/?utm_source=feedback-settings</string>
|
||||
|
||||
<string name="pref_dialog_set_default">&pref_dialog_set_default;</string>
|
||||
<string name="pref_default">&pref_dialog_default;</string>
|
||||
|
|
|
@ -60,6 +60,9 @@ public class AutopushClient {
|
|||
public static final String JSON_KEY_ERROR = "error";
|
||||
public static final String JSON_KEY_MESSAGE = "message";
|
||||
|
||||
protected static final String[] requiredErrorStringFields = { JSON_KEY_ERROR, JSON_KEY_MESSAGE };
|
||||
protected static final String[] requiredErrorLongFields = { JSON_KEY_CODE, JSON_KEY_ERRNO };
|
||||
|
||||
/**
|
||||
* The server's URI.
|
||||
* <p>
|
||||
|
@ -136,8 +139,8 @@ public class AutopushClient {
|
|||
if (200 <= status && status <= 299) {
|
||||
return status;
|
||||
}
|
||||
int code;
|
||||
int errno;
|
||||
long code;
|
||||
long errno;
|
||||
String error;
|
||||
String message;
|
||||
String info;
|
||||
|
@ -146,9 +149,10 @@ public class AutopushClient {
|
|||
body = new SyncStorageResponse(response).jsonObjectBody();
|
||||
// TODO: The service doesn't do the right thing yet :(
|
||||
// body.throwIfFieldsMissingOrMisTyped(requiredErrorStringFields, String.class);
|
||||
// body.throwIfFieldsMissingOrMisTyped(requiredErrorLongFields, Long.class);
|
||||
code = body.getLong(JSON_KEY_CODE).intValue();
|
||||
errno = body.getLong(JSON_KEY_ERRNO).intValue();
|
||||
body.throwIfFieldsMissingOrMisTyped(requiredErrorLongFields, Long.class);
|
||||
// Would throw above if missing; the -1 defaults quiet NPE warnings.
|
||||
code = body.getLong(JSON_KEY_CODE, -1);
|
||||
errno = body.getLong(JSON_KEY_ERRNO, -1);
|
||||
error = body.getString(JSON_KEY_ERROR);
|
||||
message = body.getString(JSON_KEY_MESSAGE);
|
||||
} catch (Exception e) {
|
||||
|
|
|
@ -0,0 +1,92 @@
|
|||
/*
|
||||
* 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.cleanup;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mozilla.gecko.background.testhelpers.TestRunner;
|
||||
import org.robolectric.RuntimeEnvironment;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.mockito.Mockito.any;
|
||||
import static org.mockito.Mockito.atMost;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
/**
|
||||
* Tests functionality of the {@link FileCleanupController}.
|
||||
*/
|
||||
@RunWith(TestRunner.class)
|
||||
public class TestFileCleanupController {
|
||||
|
||||
@Test
|
||||
public void testStartIfReadyEmptySharedPrefsRunsCleanup() {
|
||||
final Context context = mock(Context.class);
|
||||
FileCleanupController.startIfReady(context, getSharedPreferences(), "");
|
||||
verify(context).startService(any(Intent.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStartIfReadyLastRunNowDoesNotRun() {
|
||||
final SharedPreferences sharedPrefs = getSharedPreferences();
|
||||
sharedPrefs.edit()
|
||||
.putLong(FileCleanupController.PREF_LAST_CLEANUP_MILLIS, System.currentTimeMillis())
|
||||
.commit(); // synchronous to finish before test runs.
|
||||
|
||||
final Context context = mock(Context.class);
|
||||
FileCleanupController.startIfReady(context, sharedPrefs, "");
|
||||
|
||||
verify(context, never()).startService((any(Intent.class)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Depends on {@link #testStartIfReadyEmptySharedPrefsRunsCleanup()} success –
|
||||
* i.e. we expect the cleanup to run with empty prefs.
|
||||
*/
|
||||
@Test
|
||||
public void testStartIfReadyDoesNotRunTwiceInSuccession() {
|
||||
final Context context = mock(Context.class);
|
||||
final SharedPreferences sharedPrefs = getSharedPreferences();
|
||||
|
||||
FileCleanupController.startIfReady(context, sharedPrefs, "");
|
||||
verify(context).startService(any(Intent.class));
|
||||
|
||||
// Note: the Controller relies on SharedPrefs.apply, but
|
||||
// robolectric made this a synchronous call. Yay!
|
||||
FileCleanupController.startIfReady(context, sharedPrefs, "");
|
||||
verify(context, atMost(1)).startService(any(Intent.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetFilesToCleanupContainsProfilePath() {
|
||||
final String profilePath = "/a/profile/path";
|
||||
final ArrayList<String> fileList = FileCleanupController.getFilesToCleanup(profilePath);
|
||||
assertNotNull("Returned file list is non-null", fileList);
|
||||
|
||||
boolean atLeastOneStartsWithProfilePath = false;
|
||||
final String pathToCheck = profilePath + "/"; // Ensure the calling code adds a slash to divide the path.
|
||||
for (final String path : fileList) {
|
||||
if (path.startsWith(pathToCheck)) {
|
||||
// It'd be great if we could assert these individually so
|
||||
// we could display the Strings in console output.
|
||||
atLeastOneStartsWithProfilePath = true;
|
||||
}
|
||||
}
|
||||
assertTrue("At least one returned String starts with a profile path", atLeastOneStartsWithProfilePath);
|
||||
}
|
||||
|
||||
private SharedPreferences getSharedPreferences() {
|
||||
return RuntimeEnvironment.application.getSharedPreferences("TestFileCleanupController", 0);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,106 @@
|
|||
/*
|
||||
* 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.cleanup;
|
||||
|
||||
import android.content.Intent;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.TemporaryFolder;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mozilla.gecko.background.testhelpers.TestRunner;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
/**
|
||||
* Tests the methods of {@link FileCleanupService}.
|
||||
*/
|
||||
@RunWith(TestRunner.class)
|
||||
public class TestFileCleanupService {
|
||||
@Rule
|
||||
public final TemporaryFolder tempFolder = new TemporaryFolder();
|
||||
|
||||
private void assertAllFilesExist(final List<File> fileList) {
|
||||
for (final File file : fileList) {
|
||||
assertTrue("File exists", file.exists());
|
||||
}
|
||||
}
|
||||
|
||||
private void assertAllFilesDoNotExist(final List<File> fileList) {
|
||||
for (final File file : fileList) {
|
||||
assertFalse("File does not exist", file.exists());
|
||||
}
|
||||
}
|
||||
|
||||
private void onHandleIntent(final ArrayList<String> filePaths) {
|
||||
final FileCleanupService service = new FileCleanupService();
|
||||
final Intent intent = new Intent(FileCleanupService.ACTION_DELETE_FILES);
|
||||
intent.putExtra(FileCleanupService.EXTRA_FILE_PATHS_TO_DELETE, filePaths);
|
||||
service.onHandleIntent(intent);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOnHandleIntentDeleteSpecifiedFiles() throws Exception {
|
||||
final int fileListCount = 3;
|
||||
final ArrayList<File> filesToDelete = generateFileList(fileListCount);
|
||||
|
||||
final ArrayList<String> pathsToDelete = new ArrayList<>(fileListCount);
|
||||
for (final File file : filesToDelete) {
|
||||
pathsToDelete.add(file.getAbsolutePath());
|
||||
}
|
||||
|
||||
assertAllFilesExist(filesToDelete);
|
||||
onHandleIntent(pathsToDelete);
|
||||
assertAllFilesDoNotExist(filesToDelete);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOnHandleIntentDoesNotDeleteUnrelatedFiles() throws Exception {
|
||||
final ArrayList<File> filesShouldNotBeDeleted = generateFileList(3);
|
||||
assertAllFilesExist(filesShouldNotBeDeleted);
|
||||
onHandleIntent(new ArrayList<String>());
|
||||
assertAllFilesExist(filesShouldNotBeDeleted);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOnHandleIntentDeletesEmptyDirectory() throws Exception {
|
||||
final File dir = tempFolder.newFolder();
|
||||
final ArrayList<String> filesToDelete = new ArrayList<>(1);
|
||||
filesToDelete.add(dir.getAbsolutePath());
|
||||
|
||||
assertTrue("Empty directory exists", dir.exists());
|
||||
onHandleIntent(filesToDelete);
|
||||
assertFalse("Empty directory deleted by service", dir.exists());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOnHandleIntentDoesNotDeleteNonEmptyDirectory() throws Exception {
|
||||
final File dir = tempFolder.newFolder();
|
||||
final ArrayList<String> filesCannotDelete = new ArrayList<>(1);
|
||||
filesCannotDelete.add(dir.getAbsolutePath());
|
||||
assertTrue("Directory exists", dir.exists());
|
||||
|
||||
final File fileInDir = new File(dir, "file_in_dir");
|
||||
assertTrue("File in dir created", fileInDir.createNewFile());
|
||||
|
||||
onHandleIntent(filesCannotDelete);
|
||||
assertTrue("Non-empty directory not deleted", dir.exists());
|
||||
assertTrue("File in directory not deleted", fileInDir.exists());
|
||||
}
|
||||
|
||||
private ArrayList<File> generateFileList(final int size) throws IOException {
|
||||
final ArrayList<File> fileList = new ArrayList<>(size);
|
||||
for (int i = 0; i < size; ++i) {
|
||||
fileList.add(tempFolder.newFile());
|
||||
}
|
||||
return fileList;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,92 @@
|
|||
/*
|
||||
* 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.telemetry.pings;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mozilla.gecko.background.testhelpers.TestRunner;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
/**
|
||||
* Unit test methods of the {@link TelemetryPingBuilder} class.
|
||||
*/
|
||||
@RunWith(TestRunner.class)
|
||||
public class TestTelemetryPingBuilder {
|
||||
@Test
|
||||
public void testMandatoryFieldsNone() {
|
||||
final NoMandatoryFieldsBuilder builder = new NoMandatoryFieldsBuilder();
|
||||
builder.setNonMandatoryField();
|
||||
assertNotNull("Builder does not throw and returns a non-null value", builder.build());
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void testMandatoryFieldsMissing() {
|
||||
final MandatoryFieldsBuilder builder = new MandatoryFieldsBuilder();
|
||||
builder.setNonMandatoryField()
|
||||
.build(); // should throw
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMandatoryFieldsIncluded() {
|
||||
final MandatoryFieldsBuilder builder = new MandatoryFieldsBuilder();
|
||||
builder.setNonMandatoryField()
|
||||
.setMandatoryField();
|
||||
assertNotNull("Builder does not throw and returns non-null value", builder.build());
|
||||
}
|
||||
|
||||
private static class NoMandatoryFieldsBuilder extends TelemetryPingBuilder {
|
||||
public NoMandatoryFieldsBuilder() {
|
||||
super("");
|
||||
}
|
||||
|
||||
@Override
|
||||
String getDocType() {
|
||||
return "";
|
||||
}
|
||||
|
||||
@Override
|
||||
String[] getMandatoryFields() {
|
||||
return new String[0];
|
||||
}
|
||||
|
||||
public NoMandatoryFieldsBuilder setNonMandatoryField() {
|
||||
payload.put("non-mandatory", true);
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
private static class MandatoryFieldsBuilder extends TelemetryPingBuilder {
|
||||
private static final String MANDATORY_FIELD = "mandatory-field";
|
||||
|
||||
public MandatoryFieldsBuilder() {
|
||||
super("");
|
||||
}
|
||||
|
||||
@Override
|
||||
String getDocType() {
|
||||
return "";
|
||||
}
|
||||
|
||||
@Override
|
||||
String[] getMandatoryFields() {
|
||||
return new String[] {
|
||||
MANDATORY_FIELD,
|
||||
};
|
||||
}
|
||||
|
||||
public MandatoryFieldsBuilder setNonMandatoryField() {
|
||||
payload.put("non-mandatory", true);
|
||||
return this;
|
||||
}
|
||||
|
||||
public MandatoryFieldsBuilder setMandatoryField() {
|
||||
payload.put(MANDATORY_FIELD, true);
|
||||
return this;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -31,5 +31,13 @@
|
|||
rows="3" cols="8">Words in a box</textarea>
|
||||
<textarea id="RTLtextarea" style="direction: rtl;"
|
||||
rows="3" cols="8">הספר הוא טוב</textarea>
|
||||
|
||||
<br>
|
||||
<input id="LTRphone" style="direction: ltr;" size="40"
|
||||
value="09876543210 .-.)(wp#*1034103410341034X">
|
||||
<br>
|
||||
<input id="RTLphone" style="direction: rtl;" size="40"
|
||||
value="התקשר +972 3 7347514 במשך זמן טוב">
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
@ -60,18 +60,18 @@ function elementSelection(element) {
|
|||
}
|
||||
|
||||
/**
|
||||
* Select the first character of a target element, w/o affecting focus.
|
||||
* Select the requested character of a target element, w/o affecting focus.
|
||||
*/
|
||||
function selectElementFirstChar(doc, element) {
|
||||
function selectElementChar(doc, element, char) {
|
||||
if (isInputOrTextarea(element)) {
|
||||
element.setSelectionRange(0, 1);
|
||||
element.setSelectionRange(char, char + 1);
|
||||
return;
|
||||
}
|
||||
|
||||
// Simple test cases designed firstChild == #text node.
|
||||
let range = doc.createRange();
|
||||
range.setStart(element.firstChild, 0);
|
||||
range.setEnd(element.firstChild, 1);
|
||||
range.setStart(element.firstChild, char);
|
||||
range.setEnd(element.firstChild, char + 1);
|
||||
|
||||
let selection = elementSelection(element);
|
||||
selection.removeAllRanges();
|
||||
|
@ -79,14 +79,14 @@ function selectElementFirstChar(doc, element) {
|
|||
}
|
||||
|
||||
/**
|
||||
* Get longpress point. Determine the midpoint in the first character of
|
||||
* Get longpress point. Determine the midpoint in the requested character of
|
||||
* the content in the element. X will be midpoint from left to right.
|
||||
* Y will be 1/3 of the height up from the bottom to account for both
|
||||
* LTR and smaller RTL characters. ie: |X| vs. |א|
|
||||
*/
|
||||
function getFirstCharPressPoint(doc, element, expected) {
|
||||
function getCharPressPoint(doc, element, char, expected) {
|
||||
// Select the first char in the element.
|
||||
selectElementFirstChar(doc, element);
|
||||
selectElementChar(doc, element, char);
|
||||
|
||||
// Reality check selected char to expected.
|
||||
let selection = elementSelection(element);
|
||||
|
@ -162,17 +162,22 @@ add_task(function* testAccessibleCarets() {
|
|||
let i_RTL_elem = doc.getElementById("RTLinput");
|
||||
let ta_RTL_elem = doc.getElementById("RTLtextarea");
|
||||
|
||||
let ip_LTR_elem = doc.getElementById("LTRphone");
|
||||
let ip_RTL_elem = doc.getElementById("RTLphone");
|
||||
|
||||
// Locate longpress midpoints for test elements, ensure expactations.
|
||||
let ce_LTR_midPoint = getFirstCharPressPoint(doc, ce_LTR_elem, "F");
|
||||
let tc_LTR_midPoint = getFirstCharPressPoint(doc, tc_LTR_elem, "O");
|
||||
let i_LTR_midPoint = getFirstCharPressPoint(doc, i_LTR_elem, "T");
|
||||
let ta_LTR_midPoint = getFirstCharPressPoint(doc, ta_LTR_elem, "W");
|
||||
let ce_LTR_midPoint = getCharPressPoint(doc, ce_LTR_elem, 0, "F");
|
||||
let tc_LTR_midPoint = getCharPressPoint(doc, tc_LTR_elem, 0, "O");
|
||||
let i_LTR_midPoint = getCharPressPoint(doc, i_LTR_elem, 0, "T");
|
||||
let ta_LTR_midPoint = getCharPressPoint(doc, ta_LTR_elem, 0, "W");
|
||||
|
||||
let ce_RTL_midPoint = getFirstCharPressPoint(doc, ce_RTL_elem, "א");
|
||||
let tc_RTL_midPoint = getFirstCharPressPoint(doc, tc_RTL_elem, "ת");
|
||||
let i_RTL_midPoint = getFirstCharPressPoint(doc, i_RTL_elem, "ל");
|
||||
let ta_RTL_midPoint = getFirstCharPressPoint(doc, ta_RTL_elem, "ה");
|
||||
let ce_RTL_midPoint = getCharPressPoint(doc, ce_RTL_elem, 0, "א");
|
||||
let tc_RTL_midPoint = getCharPressPoint(doc, tc_RTL_elem, 0, "ת");
|
||||
let i_RTL_midPoint = getCharPressPoint(doc, i_RTL_elem, 0, "ל");
|
||||
let ta_RTL_midPoint = getCharPressPoint(doc, ta_RTL_elem, 0, "ה");
|
||||
|
||||
let ip_LTR_midPoint = getCharPressPoint(doc, ip_LTR_elem, 8, "2");
|
||||
let ip_RTL_midPoint = getCharPressPoint(doc, ip_RTL_elem, 9, "2");
|
||||
|
||||
// Longpress various LTR content elements. Test focused element against
|
||||
// expected, and selected text against expected.
|
||||
|
@ -192,6 +197,13 @@ add_task(function* testAccessibleCarets() {
|
|||
is(result.focusedElement, ta_LTR_elem, "Focused element should match expected.");
|
||||
is(result.text, "Words", "Selected text should match expected text.");
|
||||
|
||||
result = getLongPressResult(browser, ip_LTR_midPoint);
|
||||
is(result.focusedElement, ip_LTR_elem, "Focused element should match expected.");
|
||||
is(result.text, "09876543210 .-.)(wp#*103410341",
|
||||
"Selected phone number should match expected text.");
|
||||
is(result.text.length, 30,
|
||||
"Selected phone number length should match expected maximum.");
|
||||
|
||||
// Longpress various RTL content elements. Test focused element against
|
||||
// expected, and selected text against expected.
|
||||
result = getLongPressResult(browser, ce_RTL_midPoint);
|
||||
|
@ -210,6 +222,11 @@ add_task(function* testAccessibleCarets() {
|
|||
is(result.focusedElement, ta_RTL_elem, "Focused element should match expected.");
|
||||
is(result.text, "הספר", "Selected text should match expected text.");
|
||||
|
||||
result = getLongPressResult(browser, ip_RTL_midPoint);
|
||||
is(result.focusedElement, ip_RTL_elem, "Focused element should match expected.");
|
||||
is(result.text, "+972 3 7347514 ",
|
||||
"Selected phone number should match expected text.");
|
||||
|
||||
ok(true, "Finished all tests.");
|
||||
});
|
||||
|
||||
|
|
|
@ -421,7 +421,8 @@ FxAccountsInternal.prototype = {
|
|||
return Promise.reject(new Error(
|
||||
"checkEmailStatus called without a session token"));
|
||||
}
|
||||
return this.fxAccountsClient.recoveryEmailStatus(sessionToken, options);
|
||||
return this.fxAccountsClient.recoveryEmailStatus(sessionToken,
|
||||
options).catch(error => this._handleTokenError(error));
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -673,10 +674,11 @@ FxAccountsInternal.prototype = {
|
|||
return null;
|
||||
}
|
||||
|
||||
if (!this.isUserEmailVerified(data)) {
|
||||
log.trace("checkVerificationStatus - forcing verification status check");
|
||||
this.pollEmailStatus(currentState, data.sessionToken, "push");
|
||||
}
|
||||
// Always check the verification status, even if the local state indicates
|
||||
// we're already verified. If the user changed their password, the check
|
||||
// will fail, and we'll enter the reauth state.
|
||||
log.trace("checkVerificationStatus - forcing verification status check");
|
||||
return this.pollEmailStatus(currentState, data.sessionToken, "push");
|
||||
});
|
||||
},
|
||||
|
||||
|
@ -1099,7 +1101,9 @@ FxAccountsInternal.prototype = {
|
|||
}
|
||||
}
|
||||
|
||||
this.checkEmailStatus(sessionToken, { reason: why })
|
||||
// We return a promise for testing only. Other callers can ignore this,
|
||||
// since verification polling continues in the background.
|
||||
return this.checkEmailStatus(sessionToken, { reason: why })
|
||||
.then((response) => {
|
||||
log.debug("checkEmailStatus -> " + JSON.stringify(response));
|
||||
if (response && response.verified) {
|
||||
|
|
|
@ -38,6 +38,26 @@ const COMMAND_CHANGE_PASSWORD = "fxaccounts:change_password";
|
|||
const PREF_LAST_FXA_USER = "identity.fxaccounts.lastSignedInUserHash";
|
||||
const PREF_SYNC_SHOW_CUSTOMIZATION = "services.sync-setup.ui.showCustomizationDialog";
|
||||
|
||||
/**
|
||||
* A helper function that extracts the message and stack from an error object.
|
||||
* Returns a `{ message, stack }` tuple. `stack` will be null if the error
|
||||
* doesn't have a stack trace.
|
||||
*/
|
||||
function getErrorDetails(error) {
|
||||
let details = { message: String(error), stack: null };
|
||||
|
||||
// Adapted from Console.jsm.
|
||||
if (error.stack) {
|
||||
let frames = [];
|
||||
for (let frame = error.stack; frame; frame = frame.caller) {
|
||||
frames.push(String(frame).padStart(4));
|
||||
}
|
||||
details.stack = frames.join("\n");
|
||||
}
|
||||
|
||||
return details;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new FxAccountsWebChannel to listen for account updates
|
||||
*
|
||||
|
@ -116,6 +136,59 @@ this.FxAccountsWebChannel.prototype = {
|
|||
}
|
||||
},
|
||||
|
||||
_receiveMessage(message, sendingContext) {
|
||||
let command = message.command;
|
||||
let data = message.data;
|
||||
|
||||
switch (command) {
|
||||
case COMMAND_PROFILE_CHANGE:
|
||||
Services.obs.notifyObservers(null, ON_PROFILE_CHANGE_NOTIFICATION, data.uid);
|
||||
break;
|
||||
case COMMAND_LOGIN:
|
||||
this._helpers.login(data).catch(error =>
|
||||
this._sendError(error, message, sendingContext));
|
||||
break;
|
||||
case COMMAND_LOGOUT:
|
||||
case COMMAND_DELETE:
|
||||
this._helpers.logout(data.uid).catch(error =>
|
||||
this._sendError(error, message, sendingContext));
|
||||
break;
|
||||
case COMMAND_CAN_LINK_ACCOUNT:
|
||||
let canLinkAccount = this._helpers.shouldAllowRelink(data.email);
|
||||
|
||||
let response = {
|
||||
command: command,
|
||||
messageId: message.messageId,
|
||||
data: { ok: canLinkAccount }
|
||||
};
|
||||
|
||||
log.debug("FxAccountsWebChannel response", response);
|
||||
this._channel.send(response, sendingContext);
|
||||
break;
|
||||
case COMMAND_SYNC_PREFERENCES:
|
||||
this._helpers.openSyncPreferences(sendingContext.browser, data.entryPoint);
|
||||
break;
|
||||
case COMMAND_CHANGE_PASSWORD:
|
||||
this._helpers.changePassword(data).catch(error =>
|
||||
this._sendError(error, message, sendingContext));
|
||||
break;
|
||||
default:
|
||||
log.warn("Unrecognized FxAccountsWebChannel command", command);
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
_sendError(error, incomingMessage, sendingContext) {
|
||||
log.error("Failed to handle FxAccountsWebChannel message", error);
|
||||
this._channel.send({
|
||||
command: incomingMessage.command,
|
||||
messageId: incomingMessage.messageId,
|
||||
data: {
|
||||
error: getErrorDetails(error),
|
||||
},
|
||||
}, sendingContext);
|
||||
},
|
||||
|
||||
/**
|
||||
* Create a new channel with the WebChannelBroker, setup a callback listener
|
||||
* @private
|
||||
|
@ -146,41 +219,10 @@ this.FxAccountsWebChannel.prototype = {
|
|||
if (logPII) {
|
||||
log.debug("FxAccountsWebChannel message details", message);
|
||||
}
|
||||
let command = message.command;
|
||||
let data = message.data;
|
||||
|
||||
switch (command) {
|
||||
case COMMAND_PROFILE_CHANGE:
|
||||
Services.obs.notifyObservers(null, ON_PROFILE_CHANGE_NOTIFICATION, data.uid);
|
||||
break;
|
||||
case COMMAND_LOGIN:
|
||||
this._helpers.login(data);
|
||||
break;
|
||||
case COMMAND_LOGOUT:
|
||||
case COMMAND_DELETE:
|
||||
this._helpers.logout(data.uid);
|
||||
break;
|
||||
case COMMAND_CAN_LINK_ACCOUNT:
|
||||
let canLinkAccount = this._helpers.shouldAllowRelink(data.email);
|
||||
|
||||
let response = {
|
||||
command: command,
|
||||
messageId: message.messageId,
|
||||
data: { ok: canLinkAccount }
|
||||
};
|
||||
|
||||
log.debug("FxAccountsWebChannel response", response);
|
||||
this._channel.send(response, sendingContext);
|
||||
break;
|
||||
case COMMAND_SYNC_PREFERENCES:
|
||||
this._helpers.openSyncPreferences(sendingContext.browser, data.entryPoint);
|
||||
break;
|
||||
case COMMAND_CHANGE_PASSWORD:
|
||||
this._helpers.changePassword(data);
|
||||
break;
|
||||
default:
|
||||
log.warn("Unrecognized FxAccountsWebChannel command", command);
|
||||
break;
|
||||
try {
|
||||
this._receiveMessage(message, sendingContext);
|
||||
} catch (error) {
|
||||
this._sendError(error, message, sendingContext);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -299,9 +341,7 @@ this.FxAccountsWebChannelHelpers.prototype = {
|
|||
log.info("changePassword ignoring unsupported field", name);
|
||||
}
|
||||
}
|
||||
this._fxAccounts.updateUserAccountData(newCredentials).catch(err => {
|
||||
log.error("Failed to update account data on password change", err);
|
||||
});
|
||||
return this._fxAccounts.updateUserAccountData(newCredentials);
|
||||
},
|
||||
|
||||
/**
|
||||
|
|
|
@ -1438,6 +1438,33 @@ add_test(function test_getSignedInUserProfile_no_account_data() {
|
|||
|
||||
});
|
||||
|
||||
add_task(function* test_checkVerificationStatusFailed() {
|
||||
let fxa = new MockFxAccounts();
|
||||
let alice = getTestUser("alice");
|
||||
alice.verified = true;
|
||||
|
||||
let client = fxa.internal.fxAccountsClient;
|
||||
client.recoveryEmailStatus = () => {
|
||||
return Promise.reject({
|
||||
code: 401,
|
||||
errno: ERRNO_INVALID_AUTH_TOKEN,
|
||||
});
|
||||
};
|
||||
client.accountStatus = () => Promise.resolve(true);
|
||||
|
||||
yield fxa.setSignedInUser(alice);
|
||||
let user = yield fxa.internal.getUserAccountData();
|
||||
do_check_neq(alice.sessionToken, null);
|
||||
do_check_eq(user.email, alice.email);
|
||||
do_check_eq(user.verified, true);
|
||||
|
||||
yield fxa.checkVerificationStatus();
|
||||
|
||||
user = yield fxa.internal.getUserAccountData();
|
||||
do_check_eq(user.email, alice.email);
|
||||
do_check_eq(user.sessionToken, null);
|
||||
});
|
||||
|
||||
/*
|
||||
* End of tests.
|
||||
* Utility functions follow.
|
||||
|
|
|
@ -38,6 +38,80 @@ add_test(function () {
|
|||
run_next_test();
|
||||
});
|
||||
|
||||
add_task(function* test_rejection_reporting() {
|
||||
let mockMessage = {
|
||||
command: 'fxaccounts:login',
|
||||
messageId: '1234',
|
||||
data: { email: 'testuser@testuser.com' },
|
||||
};
|
||||
|
||||
let channel = new FxAccountsWebChannel({
|
||||
channel_id: WEBCHANNEL_ID,
|
||||
content_uri: URL_STRING,
|
||||
helpers: {
|
||||
login(accountData) {
|
||||
equal(accountData.email, 'testuser@testuser.com',
|
||||
'Should forward incoming message data to the helper');
|
||||
return Promise.reject(new Error('oops'));
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
let promiseSend = new Promise(resolve => {
|
||||
channel._channel.send = (message, context) => {
|
||||
resolve({ message, context });
|
||||
};
|
||||
});
|
||||
|
||||
channel._channelCallback(WEBCHANNEL_ID, mockMessage, mockSendingContext);
|
||||
|
||||
let { message, context } = yield promiseSend;
|
||||
|
||||
equal(context, mockSendingContext, 'Should forward the original context');
|
||||
equal(message.command, 'fxaccounts:login',
|
||||
'Should include the incoming command');
|
||||
equal(message.messageId, '1234', 'Should include the message ID');
|
||||
equal(message.data.error.message, 'Error: oops',
|
||||
'Should convert the error message to a string');
|
||||
notStrictEqual(message.data.error.stack, null,
|
||||
'Should include the stack for JS error rejections');
|
||||
});
|
||||
|
||||
add_test(function test_exception_reporting() {
|
||||
let mockMessage = {
|
||||
command: 'fxaccounts:sync_preferences',
|
||||
messageId: '5678',
|
||||
data: { entryPoint: 'fxa:verification_complete' }
|
||||
};
|
||||
|
||||
let channel = new FxAccountsWebChannel({
|
||||
channel_id: WEBCHANNEL_ID,
|
||||
content_uri: URL_STRING,
|
||||
helpers: {
|
||||
openSyncPreferences(browser, entryPoint) {
|
||||
equal(entryPoint, 'fxa:verification_complete',
|
||||
'Should forward incoming message data to the helper');
|
||||
throw new TypeError('splines not reticulated');
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
channel._channel.send = (message, context) => {
|
||||
equal(context, mockSendingContext, 'Should forward the original context');
|
||||
equal(message.command, 'fxaccounts:sync_preferences',
|
||||
'Should include the incoming command');
|
||||
equal(message.messageId, '5678', 'Should include the message ID');
|
||||
equal(message.data.error.message, 'TypeError: splines not reticulated',
|
||||
'Should convert the exception to a string');
|
||||
notStrictEqual(message.data.error.stack, null,
|
||||
'Should include the stack for JS exceptions');
|
||||
|
||||
run_next_test();
|
||||
};
|
||||
|
||||
channel._channelCallback(WEBCHANNEL_ID, mockMessage, mockSendingContext);
|
||||
});
|
||||
|
||||
add_test(function test_profile_image_change_message() {
|
||||
var mockMessage = {
|
||||
command: "profile:change",
|
||||
|
@ -70,6 +144,7 @@ add_test(function test_login_message() {
|
|||
login: function (accountData) {
|
||||
do_check_eq(accountData.email, 'testuser@testuser.com');
|
||||
run_next_test();
|
||||
return Promise.resolve();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -90,6 +165,7 @@ add_test(function test_logout_message() {
|
|||
logout: function (uid) {
|
||||
do_check_eq(uid, 'foo');
|
||||
run_next_test();
|
||||
return Promise.resolve();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -110,6 +186,7 @@ add_test(function test_delete_message() {
|
|||
logout: function (uid) {
|
||||
do_check_eq(uid, 'foo');
|
||||
run_next_test();
|
||||
return Promise.resolve();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -199,23 +276,25 @@ add_test(function test_helpers_should_allow_relink_different_email() {
|
|||
run_next_test();
|
||||
});
|
||||
|
||||
add_test(function test_helpers_login_without_customize_sync() {
|
||||
add_task(function* test_helpers_login_without_customize_sync() {
|
||||
let helpers = new FxAccountsWebChannelHelpers({
|
||||
fxAccounts: {
|
||||
setSignedInUser: function(accountData) {
|
||||
// ensure fxAccounts is informed of the new user being signed in.
|
||||
do_check_eq(accountData.email, 'testuser@testuser.com');
|
||||
return new Promise(resolve => {
|
||||
// ensure fxAccounts is informed of the new user being signed in.
|
||||
do_check_eq(accountData.email, 'testuser@testuser.com');
|
||||
|
||||
// verifiedCanLinkAccount should be stripped in the data.
|
||||
do_check_false('verifiedCanLinkAccount' in accountData);
|
||||
// verifiedCanLinkAccount should be stripped in the data.
|
||||
do_check_false('verifiedCanLinkAccount' in accountData);
|
||||
|
||||
// the customizeSync pref should not update
|
||||
do_check_false(helpers.getShowCustomizeSyncPref());
|
||||
// the customizeSync pref should not update
|
||||
do_check_false(helpers.getShowCustomizeSyncPref());
|
||||
|
||||
// previously signed in user preference is updated.
|
||||
do_check_eq(helpers.getPreviousAccountNameHashPref(), helpers.sha256('testuser@testuser.com'));
|
||||
// previously signed in user preference is updated.
|
||||
do_check_eq(helpers.getPreviousAccountNameHashPref(), helpers.sha256('testuser@testuser.com'));
|
||||
|
||||
run_next_test();
|
||||
resolve();
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -226,27 +305,29 @@ add_test(function test_helpers_login_without_customize_sync() {
|
|||
// ensure the previous account pref is overwritten.
|
||||
helpers.setPreviousAccountNameHashPref('lastuser@testuser.com');
|
||||
|
||||
helpers.login({
|
||||
yield helpers.login({
|
||||
email: 'testuser@testuser.com',
|
||||
verifiedCanLinkAccount: true,
|
||||
customizeSync: false
|
||||
});
|
||||
});
|
||||
|
||||
add_test(function test_helpers_login_with_customize_sync() {
|
||||
add_task(function* test_helpers_login_with_customize_sync() {
|
||||
let helpers = new FxAccountsWebChannelHelpers({
|
||||
fxAccounts: {
|
||||
setSignedInUser: function(accountData) {
|
||||
// ensure fxAccounts is informed of the new user being signed in.
|
||||
do_check_eq(accountData.email, 'testuser@testuser.com');
|
||||
return new Promise(resolve => {
|
||||
// ensure fxAccounts is informed of the new user being signed in.
|
||||
do_check_eq(accountData.email, 'testuser@testuser.com');
|
||||
|
||||
// customizeSync should be stripped in the data.
|
||||
do_check_false('customizeSync' in accountData);
|
||||
// customizeSync should be stripped in the data.
|
||||
do_check_false('customizeSync' in accountData);
|
||||
|
||||
// the customizeSync pref should not update
|
||||
do_check_true(helpers.getShowCustomizeSyncPref());
|
||||
// the customizeSync pref should not update
|
||||
do_check_true(helpers.getShowCustomizeSyncPref());
|
||||
|
||||
run_next_test();
|
||||
resolve();
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -254,34 +335,36 @@ add_test(function test_helpers_login_with_customize_sync() {
|
|||
// the customize sync pref should be overwritten
|
||||
helpers.setShowCustomizeSyncPref(false);
|
||||
|
||||
helpers.login({
|
||||
yield helpers.login({
|
||||
email: 'testuser@testuser.com',
|
||||
verifiedCanLinkAccount: true,
|
||||
customizeSync: true
|
||||
});
|
||||
});
|
||||
|
||||
add_test(function test_helpers_login_with_customize_sync_and_declined_engines() {
|
||||
add_task(function* test_helpers_login_with_customize_sync_and_declined_engines() {
|
||||
let helpers = new FxAccountsWebChannelHelpers({
|
||||
fxAccounts: {
|
||||
setSignedInUser: function(accountData) {
|
||||
// ensure fxAccounts is informed of the new user being signed in.
|
||||
do_check_eq(accountData.email, 'testuser@testuser.com');
|
||||
return new Promise(resolve => {
|
||||
// ensure fxAccounts is informed of the new user being signed in.
|
||||
do_check_eq(accountData.email, 'testuser@testuser.com');
|
||||
|
||||
// customizeSync should be stripped in the data.
|
||||
do_check_false('customizeSync' in accountData);
|
||||
do_check_false('declinedSyncEngines' in accountData);
|
||||
do_check_eq(Services.prefs.getBoolPref("services.sync.engine.addons"), false);
|
||||
do_check_eq(Services.prefs.getBoolPref("services.sync.engine.bookmarks"), true);
|
||||
do_check_eq(Services.prefs.getBoolPref("services.sync.engine.history"), true);
|
||||
do_check_eq(Services.prefs.getBoolPref("services.sync.engine.passwords"), true);
|
||||
do_check_eq(Services.prefs.getBoolPref("services.sync.engine.prefs"), false);
|
||||
do_check_eq(Services.prefs.getBoolPref("services.sync.engine.tabs"), true);
|
||||
// customizeSync should be stripped in the data.
|
||||
do_check_false('customizeSync' in accountData);
|
||||
do_check_false('declinedSyncEngines' in accountData);
|
||||
do_check_eq(Services.prefs.getBoolPref("services.sync.engine.addons"), false);
|
||||
do_check_eq(Services.prefs.getBoolPref("services.sync.engine.bookmarks"), true);
|
||||
do_check_eq(Services.prefs.getBoolPref("services.sync.engine.history"), true);
|
||||
do_check_eq(Services.prefs.getBoolPref("services.sync.engine.passwords"), true);
|
||||
do_check_eq(Services.prefs.getBoolPref("services.sync.engine.prefs"), false);
|
||||
do_check_eq(Services.prefs.getBoolPref("services.sync.engine.tabs"), true);
|
||||
|
||||
// the customizeSync pref should be disabled
|
||||
do_check_false(helpers.getShowCustomizeSyncPref());
|
||||
// the customizeSync pref should be disabled
|
||||
do_check_false(helpers.getShowCustomizeSyncPref());
|
||||
|
||||
run_next_test();
|
||||
resolve();
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -295,7 +378,7 @@ add_test(function test_helpers_login_with_customize_sync_and_declined_engines()
|
|||
do_check_eq(Services.prefs.getBoolPref("services.sync.engine.passwords"), true);
|
||||
do_check_eq(Services.prefs.getBoolPref("services.sync.engine.prefs"), true);
|
||||
do_check_eq(Services.prefs.getBoolPref("services.sync.engine.tabs"), true);
|
||||
helpers.login({
|
||||
yield helpers.login({
|
||||
email: 'testuser@testuser.com',
|
||||
verifiedCanLinkAccount: true,
|
||||
customizeSync: true,
|
||||
|
@ -319,24 +402,26 @@ add_test(function test_helpers_open_sync_preferences() {
|
|||
helpers.openSyncPreferences(mockBrowser, "fxa:verification_complete");
|
||||
});
|
||||
|
||||
add_test(function test_helpers_change_password() {
|
||||
add_task(function* test_helpers_change_password() {
|
||||
let updateCalled = false;
|
||||
let helpers = new FxAccountsWebChannelHelpers({
|
||||
fxAccounts: {
|
||||
updateUserAccountData(credentials) {
|
||||
do_check_true(credentials.hasOwnProperty("email"));
|
||||
do_check_true(credentials.hasOwnProperty("uid"));
|
||||
do_check_true(credentials.hasOwnProperty("kA"));
|
||||
// "foo" isn't a field known by storage, so should be dropped.
|
||||
do_check_false(credentials.hasOwnProperty("foo"));
|
||||
updateCalled = true;
|
||||
return Promise.resolve();
|
||||
return new Promise(resolve => {
|
||||
do_check_true(credentials.hasOwnProperty("email"));
|
||||
do_check_true(credentials.hasOwnProperty("uid"));
|
||||
do_check_true(credentials.hasOwnProperty("kA"));
|
||||
// "foo" isn't a field known by storage, so should be dropped.
|
||||
do_check_false(credentials.hasOwnProperty("foo"));
|
||||
updateCalled = true;
|
||||
|
||||
resolve();
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
helpers.changePassword({ email: "email", uid: "uid", kA: "kA", foo: "foo" });
|
||||
yield helpers.changePassword({ email: "email", uid: "uid", kA: "kA", foo: "foo" });
|
||||
do_check_true(updateCalled);
|
||||
run_next_test();
|
||||
});
|
||||
|
||||
function run_test() {
|
||||
|
|
|
@ -22,12 +22,18 @@ var cpows = [
|
|||
/^window\.content/
|
||||
];
|
||||
|
||||
var isInContentTask = false;
|
||||
|
||||
module.exports = function(context) {
|
||||
// ---------------------------------------------------------------------------
|
||||
// Helpers
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
function showError(node, identifier) {
|
||||
if (isInContentTask) {
|
||||
return;
|
||||
}
|
||||
|
||||
context.report({
|
||||
node: node,
|
||||
message: identifier +
|
||||
|
@ -35,11 +41,32 @@ module.exports = function(context) {
|
|||
});
|
||||
}
|
||||
|
||||
function isContentTask(node) {
|
||||
return node &&
|
||||
node.type === "MemberExpression" &&
|
||||
node.property.type === "Identifier" &&
|
||||
node.property.name === "spawn" &&
|
||||
node.object.type === "Identifier" &&
|
||||
node.object.name === "ContentTask";
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Public
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
return {
|
||||
CallExpression: function(node) {
|
||||
if (isContentTask(node.callee)) {
|
||||
isInContentTask = true;
|
||||
}
|
||||
},
|
||||
|
||||
"CallExpression:exit": function(node) {
|
||||
if (isContentTask(node.callee)) {
|
||||
isInContentTask = false;
|
||||
}
|
||||
},
|
||||
|
||||
MemberExpression: function(node) {
|
||||
if (!helpers.getIsBrowserMochitest(this)) {
|
||||
return;
|
||||
|
@ -77,7 +104,6 @@ module.exports = function(context) {
|
|||
node.parent.object.name != "content") {
|
||||
return;
|
||||
}
|
||||
|
||||
showError(node, expression);
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -38,6 +38,10 @@ extensions.registerSchemaAPI("extension", null, (extension, context) => {
|
|||
isAllowedIncognitoAccess() {
|
||||
return Promise.resolve(true);
|
||||
},
|
||||
|
||||
isAllowedFileSchemeAccess() {
|
||||
return Promise.resolve(true);
|
||||
},
|
||||
},
|
||||
};
|
||||
});
|
||||
|
|
|
@ -117,7 +117,6 @@
|
|||
},
|
||||
{
|
||||
"name": "isAllowedFileSchemeAccess",
|
||||
"unsupported": true,
|
||||
"type": "function",
|
||||
"description": "Retrieves the state of the extension's access to the 'file://' scheme (as determined by the user-controlled 'Allow access to File URLs' checkbox.",
|
||||
"async": "callback",
|
||||
|
|
|
@ -270,7 +270,7 @@ function backgroundScript() {
|
|||
return browser.bookmarks.search("Menu Item");
|
||||
}).then(results => {
|
||||
browser.test.assertEq(1, results.length, "Expected number of results returned for menu item search");
|
||||
checkBookmark({title: "Menu Item", url: "http://menu.org/", index: 4, parentId: bookmarkGuids.menuGuid}, results[0]);
|
||||
checkBookmark({title: "Menu Item", url: "http://menu.org/", index: 3, parentId: bookmarkGuids.menuGuid}, results[0]);
|
||||
|
||||
// finds toolbar items
|
||||
return browser.bookmarks.search("Toolbar Item");
|
||||
|
|
|
@ -33,6 +33,26 @@ add_task(function* test_is_allowed_incognito_access() {
|
|||
info("extension unloaded");
|
||||
});
|
||||
|
||||
add_task(function* test_is_allowed_file_scheme_access() {
|
||||
function backgroundScript() {
|
||||
browser.extension.isAllowedFileSchemeAccess().then(isAllowedFileSchemeAccess => {
|
||||
browser.test.assertEq(true, isAllowedFileSchemeAccess, "isAllowedFileSchemeAccess is true");
|
||||
browser.test.notifyPass("isAllowedFileSchemeAccess");
|
||||
});
|
||||
}
|
||||
|
||||
let extension = ExtensionTestUtils.loadExtension({
|
||||
background: `(${backgroundScript})()`,
|
||||
manifest: {},
|
||||
});
|
||||
|
||||
yield extension.startup();
|
||||
info("extension loaded");
|
||||
yield extension.awaitFinish("isAllowedFileSchemeAccess");
|
||||
yield extension.unload();
|
||||
info("extension unloaded");
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
</body>
|
||||
|
|
|
@ -5,69 +5,83 @@
|
|||
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
|
||||
|
||||
<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
|
||||
<?xml-stylesheet
|
||||
href="chrome://mochikit/content/tests/SimpleTest/test.css"
|
||||
type="text/css"?>
|
||||
|
||||
<window id="360437Test"
|
||||
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
|
||||
width="600"
|
||||
height="600"
|
||||
onload="onLoad();"
|
||||
onload="startTest();"
|
||||
title="360437 test">
|
||||
|
||||
<script type="application/javascript"><![CDATA[
|
||||
const Ci = Components.interfaces;
|
||||
const Cc = Components.classes;
|
||||
const Cr = Components.results;
|
||||
const {interfaces: Ci, classes: Cc, results: Cr, utils: Cu} = Components;
|
||||
Cu.import("resource://gre/modules/Task.jsm");
|
||||
Cu.import("resource://testing-common/ContentTask.jsm");
|
||||
ContentTask.setTestScope(window.opener.wrappedJSObject);
|
||||
|
||||
var gFindBar = null;
|
||||
var gBrowser;
|
||||
|
||||
function ok(condition, message) {
|
||||
window.opener.wrappedJSObject.SimpleTest.ok(condition, message);
|
||||
}
|
||||
function finish() {
|
||||
window.close();
|
||||
window.opener.wrappedJSObject.SimpleTest.finish();
|
||||
var imports = ["SimpleTest", "ok", "is", "info"];
|
||||
for (var name of imports) {
|
||||
window[name] = window.opener.wrappedJSObject[name];
|
||||
}
|
||||
|
||||
function onLoad() {
|
||||
var _delayedOnLoad = function() {
|
||||
function startTest() {
|
||||
Task.spawn(function* () {
|
||||
gFindBar = document.getElementById("FindToolbar");
|
||||
gBrowser = document.getElementById("content");
|
||||
gBrowser.addEventListener("pageshow", onPageShow, false);
|
||||
gBrowser.loadURI("data:text/html,<form><input id='input' type='text' value='text inside an input element'></form>");
|
||||
}
|
||||
setTimeout(_delayedOnLoad, 1000);
|
||||
for (let browserId of ["content", "content-remote"]) {
|
||||
yield startTestWithBrowser(browserId);
|
||||
}
|
||||
}).then(() => {
|
||||
window.close();
|
||||
SimpleTest.finish();
|
||||
});
|
||||
}
|
||||
|
||||
function onPageShow() {
|
||||
testNormalFind();
|
||||
function* startTestWithBrowser(browserId) {
|
||||
info("Starting test with browser '" + browserId + "'");
|
||||
gBrowser = document.getElementById(browserId);
|
||||
gFindBar.browser = gBrowser;
|
||||
let promise = ContentTask.spawn(gBrowser, null, function* () {
|
||||
return new Promise(resolve => {
|
||||
addEventListener("DOMContentLoaded", function listener() {
|
||||
removeEventListener("DOMContentLoaded", listener);
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
});
|
||||
gBrowser.loadURI("data:text/html,<form><input id='input' type='text' value='text inside an input element'></form>");
|
||||
yield promise;
|
||||
yield onDocumentLoaded();
|
||||
}
|
||||
|
||||
function enterStringIntoFindField(aString) {
|
||||
for (var i=0; i < aString.length; i++) {
|
||||
var event = document.createEvent("KeyEvents");
|
||||
event.initKeyEvent("keypress", true, true, null, false, false,
|
||||
false, false, 0, aString.charCodeAt(i));
|
||||
gFindBar._findField.inputField.dispatchEvent(event);
|
||||
}
|
||||
}
|
||||
|
||||
function testNormalFind() {
|
||||
function* onDocumentLoaded() {
|
||||
gFindBar.onFindCommand();
|
||||
|
||||
// Make sure the findfield is correctly focused on open
|
||||
var searchStr = "text inside an input element";
|
||||
enterStringIntoFindField(searchStr);
|
||||
ok(document.commandDispatcher.focusedElement ==
|
||||
yield* enterStringIntoFindField(searchStr);
|
||||
is(document.commandDispatcher.focusedElement,
|
||||
gFindBar._findField.inputField, "Find field isn't focused");
|
||||
|
||||
// Make sure "find again" correctly transfers focus to the content element
|
||||
// when the find bar is closed.
|
||||
gFindBar.close();
|
||||
gFindBar.onFindAgainCommand(false);
|
||||
ok(document.commandDispatcher.focusedElement ==
|
||||
gBrowser.contentDocument.getElementById("input"),
|
||||
"Input Element isn't focused");
|
||||
// For remote browsers, the content document DOM tree is not accessible, thus
|
||||
// the focused element should fall back to the browser element.
|
||||
if (gBrowser.hasAttribute("remote")) {
|
||||
is(document.commandDispatcher.focusedElement, gBrowser,
|
||||
"Browser element isn't focused");
|
||||
}
|
||||
yield ContentTask.spawn(gBrowser, null, function* () {
|
||||
Assert.equal(content.document.activeElement,
|
||||
content.document.getElementById("input"), "Input Element isn't focused");
|
||||
});
|
||||
|
||||
// Make sure "find again" doesn't focus the content element if focus
|
||||
// isn't in the content document.
|
||||
|
@ -77,10 +91,29 @@
|
|||
gFindBar.onFindAgainCommand(false);
|
||||
ok(textbox.hasAttribute("focused"),
|
||||
"Focus was stolen from a chrome element");
|
||||
finish();
|
||||
}
|
||||
|
||||
function* enterStringIntoFindField(aString) {
|
||||
for (let i = 0; i < aString.length; i++) {
|
||||
let event = document.createEvent("KeyEvents");
|
||||
let promise = new Promise(resolve => {
|
||||
let listener = {
|
||||
onFindResult: function() {
|
||||
gFindBar.browser.finder.removeResultListener(listener);
|
||||
resolve();
|
||||
}
|
||||
};
|
||||
gFindBar.browser.finder.addResultListener(listener);
|
||||
});
|
||||
event.initKeyEvent("keypress", true, true, null, false, false,
|
||||
false, false, 0, aString.charCodeAt(i));
|
||||
gFindBar._findField.inputField.dispatchEvent(event);
|
||||
yield promise;
|
||||
}
|
||||
}
|
||||
]]></script>
|
||||
<textbox id="textbox"/>
|
||||
<browser type="content-primary" flex="1" id="content" src="about:blank"/>
|
||||
<browser type="content-primary" flex="1" id="content-remote" remote="true" src="about:blank"/>
|
||||
<findbar id="FindToolbar" browserid="content"/>
|
||||
</window>
|
||||
|
|
|
@ -68,6 +68,7 @@ skip-if = buildapp == 'mulet'
|
|||
[test_bug331215.xul]
|
||||
[test_bug360220.xul]
|
||||
[test_bug360437.xul]
|
||||
skip-if = os == 'linux' # Bug 1264604
|
||||
[test_bug365773.xul]
|
||||
[test_bug366992.xul]
|
||||
[test_bug382990.xul]
|
||||
|
|
|
@ -5,6 +5,9 @@
|
|||
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
|
||||
|
||||
<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
|
||||
<?xml-stylesheet
|
||||
href="chrome://mochikit/content/tests/SimpleTest/test.css"
|
||||
type="text/css"?>
|
||||
|
||||
<window id="FindbarTest"
|
||||
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
|
||||
|
@ -14,151 +17,163 @@
|
|||
title="findbar events test">
|
||||
|
||||
<script type="application/javascript"><![CDATA[
|
||||
const Ci = Components.interfaces;
|
||||
const Cc = Components.classes;
|
||||
const Cr = Components.results;
|
||||
const {interfaces: Ci, classes: Cc, results: Cr, utils: Cu} = Components;
|
||||
Cu.import("resource://gre/modules/Task.jsm");
|
||||
Cu.import("resource://testing-common/ContentTask.jsm");
|
||||
ContentTask.setTestScope(window.opener.wrappedJSObject);
|
||||
|
||||
var gFindBar = null;
|
||||
var gBrowser;
|
||||
const kTimeout = 5000; // 5 seconds.
|
||||
|
||||
var imports = ["SimpleTest", "ok", "is"];
|
||||
var imports = ["SimpleTest", "ok", "is", "info"];
|
||||
for (var name of imports) {
|
||||
window[name] = window.opener.wrappedJSObject[name];
|
||||
}
|
||||
|
||||
function finish() {
|
||||
window.close();
|
||||
SimpleTest.finish();
|
||||
}
|
||||
SimpleTest.requestLongerTimeout(2);
|
||||
|
||||
function startTest() {
|
||||
gFindBar = document.getElementById("FindToolbar");
|
||||
gBrowser = document.getElementById("content");
|
||||
gBrowser.addEventListener("pageshow", onPageShow, false);
|
||||
gBrowser.loadURI('data:text/html,hello there');
|
||||
Task.spawn(function* () {
|
||||
gFindBar = document.getElementById("FindToolbar");
|
||||
for (let browserId of ["content", "content-remote"]) {
|
||||
yield startTestWithBrowser(browserId);
|
||||
}
|
||||
}).then(() => {
|
||||
window.close();
|
||||
SimpleTest.finish();
|
||||
});
|
||||
}
|
||||
|
||||
var tests = [
|
||||
testFind,
|
||||
testFindAgain,
|
||||
testCaseSensitivity,
|
||||
testHighlight,
|
||||
finish
|
||||
];
|
||||
|
||||
// Iterates through the above tests and takes care of passing the done
|
||||
// callback for any async tests.
|
||||
function nextTest() {
|
||||
if (!tests.length) {
|
||||
return;
|
||||
}
|
||||
var func = tests.shift();
|
||||
if (!func.length) {
|
||||
// Test isn't async advance to the next test here.
|
||||
func();
|
||||
SimpleTest.executeSoon(nextTest);
|
||||
} else {
|
||||
func(nextTest);
|
||||
}
|
||||
function* startTestWithBrowser(browserId) {
|
||||
info("Starting test with browser '" + browserId + "'");
|
||||
gBrowser = document.getElementById(browserId);
|
||||
gFindBar.browser = gBrowser;
|
||||
let promise = ContentTask.spawn(gBrowser, null, function* () {
|
||||
return new Promise(resolve => {
|
||||
addEventListener("DOMContentLoaded", function listener() {
|
||||
removeEventListener("DOMContentLoaded", listener);
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
});
|
||||
gBrowser.loadURI("data:text/html,hello there");
|
||||
yield promise;
|
||||
yield onDocumentLoaded();
|
||||
}
|
||||
|
||||
function onPageShow() {
|
||||
function* onDocumentLoaded() {
|
||||
gFindBar.open();
|
||||
gFindBar.onFindCommand();
|
||||
nextTest();
|
||||
|
||||
yield testFind();
|
||||
yield testFindAgain();
|
||||
yield testCaseSensitivity();
|
||||
yield testHighlight();
|
||||
}
|
||||
|
||||
function checkSelection(done) {
|
||||
SimpleTest.executeSoon(function() {
|
||||
var selected = gBrowser.contentWindow.getSelection();
|
||||
is(String(selected), "", "No text is selected");
|
||||
function checkSelection() {
|
||||
return new Promise(resolve => {
|
||||
SimpleTest.executeSoon(() => {
|
||||
ContentTask.spawn(gBrowser, null, function* () {
|
||||
let selected = content.getSelection();
|
||||
Assert.equal(String(selected), "", "No text is selected");
|
||||
|
||||
var controller = gFindBar.browser.docShell.QueryInterface(Ci.nsIInterfaceRequestor)
|
||||
.getInterface(Ci.nsISelectionDisplay)
|
||||
.QueryInterface(Ci.nsISelectionController);
|
||||
var selection = controller.getSelection(controller.SELECTION_FIND);
|
||||
is(selection.rangeCount, 0, "No text is highlighted");
|
||||
done();
|
||||
let controller = docShell.QueryInterface(Ci.nsIInterfaceRequestor)
|
||||
.getInterface(Ci.nsISelectionDisplay)
|
||||
.QueryInterface(Ci.nsISelectionController);
|
||||
let selection = controller.getSelection(controller.SELECTION_FIND);
|
||||
Assert.equal(selection.rangeCount, 0, "No text is highlighted");
|
||||
}).then(resolve);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function once(node, eventName, callback) {
|
||||
node.addEventListener(eventName, function clb(e) {
|
||||
node.removeEventListener(eventName, clb);
|
||||
callback(e);
|
||||
})
|
||||
function once(node, eventName, preventDefault = true) {
|
||||
return new Promise((resolve, reject) => {
|
||||
let timeout = window.setTimeout(() => {
|
||||
reject("Event wasn't fired within " + kTimeout + "ms for event '" +
|
||||
eventName + "'.");
|
||||
}, kTimeout);
|
||||
|
||||
node.addEventListener(eventName, function clb(e) {
|
||||
window.clearTimeout(timeout);
|
||||
node.removeEventListener(eventName, clb);
|
||||
if (preventDefault)
|
||||
e.preventDefault();
|
||||
resolve(e);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function testFind(done) {
|
||||
var eventTriggered = false;
|
||||
var query = "t";
|
||||
once(gFindBar, "find", function(e) {
|
||||
eventTriggered = true;
|
||||
ok(e.detail.query === query, "find event query should match '" + query + "'");
|
||||
e.preventDefault();
|
||||
// Since we're preventing the default make sure nothing was selected.
|
||||
checkSelection(done);
|
||||
});
|
||||
function* testFind() {
|
||||
info("Testing normal find.");
|
||||
let query = "t";
|
||||
let promise = once(gFindBar, "find");
|
||||
|
||||
// Put some text in the find box.
|
||||
var event = document.createEvent("KeyEvents");
|
||||
let event = document.createEvent("KeyEvents");
|
||||
event.initKeyEvent("keypress", true, true, null, false, false,
|
||||
false, false, 0, query.charCodeAt(0));
|
||||
gFindBar._findField.inputField.dispatchEvent(event);
|
||||
ok(eventTriggered, "find event should be triggered");
|
||||
|
||||
let e = yield promise;
|
||||
ok(e.detail.query === query, "find event query should match '" + query + "'");
|
||||
// Since we're preventing the default make sure nothing was selected.
|
||||
yield checkSelection();
|
||||
}
|
||||
|
||||
function testFindAgain(done) {
|
||||
var eventTriggered = false;
|
||||
once(gFindBar, "findagain", function(e) {
|
||||
eventTriggered = true;
|
||||
e.preventDefault();
|
||||
// Since we're preventing the default make sure nothing was selected.
|
||||
checkSelection(done);
|
||||
});
|
||||
function testFindAgain() {
|
||||
info("Testing repeating normal find.");
|
||||
let promise = once(gFindBar, "findagain");
|
||||
|
||||
gFindBar.onFindAgainCommand();
|
||||
ok(eventTriggered, "findagain event should be triggered");
|
||||
|
||||
yield promise;
|
||||
// Since we're preventing the default make sure nothing was selected.
|
||||
yield checkSelection();
|
||||
}
|
||||
|
||||
function testCaseSensitivity() {
|
||||
var eventTriggered = false;
|
||||
once(gFindBar, "findcasesensitivitychange", function(e) {
|
||||
eventTriggered = true;
|
||||
ok(e.detail.caseSensitive, "find should be case sensitive");
|
||||
});
|
||||
function* testCaseSensitivity() {
|
||||
info("Testing normal case sensitivity.");
|
||||
let promise = once(gFindBar, "findcasesensitivitychange", false);
|
||||
|
||||
var matchCaseCheckbox = gFindBar.getElement("find-case-sensitive");
|
||||
let matchCaseCheckbox = gFindBar.getElement("find-case-sensitive");
|
||||
matchCaseCheckbox.click();
|
||||
|
||||
let e = yield promise;
|
||||
ok(e.detail.caseSensitive, "find should be case sensitive");
|
||||
|
||||
// Toggle it back to the original setting.
|
||||
matchCaseCheckbox.click();
|
||||
ok(eventTriggered, "findcasesensitivitychange should be triggered");
|
||||
|
||||
// Changing case sensitivity does the search so clear the selected text
|
||||
// before the next test.
|
||||
gBrowser.contentWindow.getSelection().removeAllRanges();
|
||||
yield ContentTask.spawn(gBrowser, null, () => content.getSelection().removeAllRanges());
|
||||
}
|
||||
|
||||
function testHighlight(done) {
|
||||
function* testHighlight() {
|
||||
info("Testing find with highlight all.");
|
||||
// Update the find state so the highlight button is clickable.
|
||||
gFindBar.updateControlState(Ci.nsITypeAheadFind.FIND_FOUND, false);
|
||||
var eventTriggered = false;
|
||||
once(gFindBar, "findhighlightallchange", function(e) {
|
||||
eventTriggered = true;
|
||||
ok(e.detail.highlightAll, "find event should have highlight all set");
|
||||
e.preventDefault();
|
||||
// Since we're preventing the default make sure nothing was highlighted.
|
||||
SimpleTest.executeSoon(function() {
|
||||
checkSelection(done);
|
||||
});
|
||||
});
|
||||
|
||||
var highlightButton = gFindBar.getElement("highlight");
|
||||
if (!highlightButton.checked) {
|
||||
let promise = once(gFindBar, "findhighlightallchange");
|
||||
|
||||
let highlightButton = gFindBar.getElement("highlight");
|
||||
if (!highlightButton.checked)
|
||||
highlightButton.click();
|
||||
|
||||
let e = yield promise;
|
||||
ok(e.detail.highlightAll, "find event should have highlight all set");
|
||||
// Since we're preventing the default make sure nothing was highlighted.
|
||||
yield checkSelection();
|
||||
|
||||
// Toggle it back to the original setting.
|
||||
if (highlightButton.checked)
|
||||
highlightButton.click();
|
||||
}
|
||||
ok(eventTriggered, "findhighlightallchange should be triggered");
|
||||
}
|
||||
]]></script>
|
||||
|
||||
<browser type="content-primary" flex="1" id="content" src="about:blank"/>
|
||||
<browser type="content-primary" flex="1" id="content-remote" remote="true" src="about:blank"/>
|
||||
<findbar id="FindToolbar" browserid="content"/>
|
||||
</window>
|
||||
|
|
|
@ -105,6 +105,21 @@ function getCtorName(aObj) {
|
|||
return Object.prototype.toString.call(aObj).slice(8, -1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates whether an object is a JS or `Components.Exception` error.
|
||||
*
|
||||
* @param {object} aThing
|
||||
The object to check
|
||||
* @return {boolean}
|
||||
Is this object an error?
|
||||
*/
|
||||
function isError(aThing) {
|
||||
return aThing && (
|
||||
(typeof aThing.name == "string" &&
|
||||
aThing.name.startsWith("NS_ERROR_")) ||
|
||||
getCtorName(aThing).endsWith("Error"));
|
||||
}
|
||||
|
||||
/**
|
||||
* A single line stringification of an object designed for use by humans
|
||||
*
|
||||
|
@ -124,6 +139,10 @@ function stringify(aThing, aAllowNewLines) {
|
|||
return "null";
|
||||
}
|
||||
|
||||
if (isError(aThing)) {
|
||||
return "Message: " + aThing;
|
||||
}
|
||||
|
||||
if (typeof aThing == "object") {
|
||||
let type = getCtorName(aThing);
|
||||
if (aThing instanceof Components.interfaces.nsIDOMNode && aThing.tagName) {
|
||||
|
@ -203,9 +222,7 @@ function log(aThing) {
|
|||
i++;
|
||||
}
|
||||
}
|
||||
else if (type.match("Error$") ||
|
||||
(typeof aThing.name == "string" &&
|
||||
aThing.name.match("NS_ERROR_"))) {
|
||||
else if (isError(aThing)) {
|
||||
reply += " Message: " + aThing + "\n";
|
||||
if (aThing.stack) {
|
||||
reply += " Stack:\n";
|
||||
|
|
|
@ -3842,6 +3842,7 @@ GetManifestContents(const NS_tchar *manifest)
|
|||
size_t c = fread(rb, 1, count, mfile);
|
||||
if (c != count) {
|
||||
LOG(("GetManifestContents: error reading manifest file: " LOG_S, manifest));
|
||||
free(mbuf);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
|
@ -3855,8 +3856,10 @@ GetManifestContents(const NS_tchar *manifest)
|
|||
return rb;
|
||||
#else
|
||||
NS_tchar *wrb = (NS_tchar *) malloc((ms.st_size + 1) * sizeof(NS_tchar));
|
||||
if (!wrb)
|
||||
if (!wrb) {
|
||||
free(mbuf);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (!MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, rb, -1, wrb,
|
||||
ms.st_size + 1)) {
|
||||
|
|
Загрузка…
Ссылка в новой задаче