mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
Merge fx-team to m-c.
This commit is contained in:
commit
ef4c86e7c4
@ -1386,6 +1386,12 @@ let CustomizableUIInternal = {
|
||||
menuitemCloseMenu = (closemenuVal == "single" || closemenuVal == "none") ?
|
||||
closemenuVal : "auto";
|
||||
}
|
||||
// Break out of the loop immediately for disabled items, as we need to
|
||||
// keep the menu open in that case.
|
||||
if (target.getAttribute("disabled") == "true") {
|
||||
return true;
|
||||
}
|
||||
|
||||
// This isn't in the loop condition because we want to break before
|
||||
// changing |target| if any of these conditions are true
|
||||
if (inInput || inItem || target == panel) {
|
||||
@ -1401,6 +1407,7 @@ let CustomizableUIInternal = {
|
||||
target = target.parentNode;
|
||||
}
|
||||
}
|
||||
|
||||
// If the user clicked a menu item...
|
||||
if (inMenu) {
|
||||
// We care if we're in an input also,
|
||||
|
@ -59,7 +59,7 @@ add_task(function() {
|
||||
menuButton.remove();
|
||||
});
|
||||
|
||||
add_task(function() {
|
||||
add_task(function*() {
|
||||
let searchbar = document.getElementById("searchbar");
|
||||
gCustomizeMode.addToPanel(searchbar);
|
||||
let placement = CustomizableUI.getPlacementOfWidget("search-container");
|
||||
@ -91,6 +91,22 @@ add_task(function() {
|
||||
ok(!isPanelUIOpen(), "Panel should no longer be open");
|
||||
});
|
||||
|
||||
add_task(function*() {
|
||||
button = document.createElement("toolbarbutton");
|
||||
button.id = "browser_946166_button_disabled";
|
||||
button.setAttribute("disabled", "true");
|
||||
button.setAttribute("label", "Button");
|
||||
PanelUI.contents.appendChild(button);
|
||||
yield PanelUI.show();
|
||||
EventUtils.synthesizeMouseAtCenter(button, {});
|
||||
is(PanelUI.panel.state, "open", "Popup stays open");
|
||||
button.removeAttribute("disabled");
|
||||
let hiddenAgain = promisePanelHidden(window);
|
||||
EventUtils.synthesizeMouseAtCenter(button, {});
|
||||
yield hiddenAgain;
|
||||
button.remove();
|
||||
});
|
||||
|
||||
registerCleanupFunction(function() {
|
||||
if (button && button.parentNode) {
|
||||
button.remove();
|
||||
|
@ -82,7 +82,10 @@ TranslationUI.prototype = {
|
||||
let callback = aTopic => {
|
||||
if (aTopic != "showing")
|
||||
return false;
|
||||
if (!this.notificationBox.getNotificationWithValue("translation"))
|
||||
let notification = this.notificationBox.getNotificationWithValue("translation");
|
||||
if (notification)
|
||||
notification.close();
|
||||
else
|
||||
this.showTranslationInfoBar();
|
||||
return true;
|
||||
};
|
||||
|
@ -61,6 +61,10 @@ function showTranslationUI(aDetectedLanguage) {
|
||||
return ui.notificationBox.getNotificationWithValue("translation");
|
||||
}
|
||||
|
||||
function hasTranslationInfoBar() {
|
||||
return !!gBrowser.getNotificationBox().getNotificationWithValue("translation");
|
||||
}
|
||||
|
||||
function test() {
|
||||
waitForExplicitFinish();
|
||||
|
||||
@ -183,18 +187,23 @@ function run_tests(aFinishCallback) {
|
||||
info("Reopen to check the 'Not Now' button closes the notification.");
|
||||
notif = showTranslationUI("fr");
|
||||
let notificationBox = gBrowser.getNotificationBox();
|
||||
ok(!!notificationBox.getNotificationWithValue("translation"), "there's a 'translate' notification");
|
||||
is(hasTranslationInfoBar(), true, "there's a 'translate' notification");
|
||||
notif._getAnonElt("notNow").click();
|
||||
ok(!notificationBox.getNotificationWithValue("translation"), "no 'translate' notification after clicking 'not now'");
|
||||
is(hasTranslationInfoBar(), false, "no 'translate' notification after clicking 'not now'");
|
||||
|
||||
info("Reopen to check the url bar icon closes the notification.");
|
||||
notif = showTranslationUI("fr");
|
||||
is(hasTranslationInfoBar(), true, "there's a 'translate' notification");
|
||||
PopupNotifications.getNotification("translate").anchorElement.click();
|
||||
is(hasTranslationInfoBar(), false, "no 'translate' notification after clicking the url bar icon");
|
||||
|
||||
info("Check that clicking the url bar icon reopens the info bar");
|
||||
checkURLBarIcon();
|
||||
// Clicking the anchor element causes a 'showing' event to be sent
|
||||
// asynchronously to our callback that will then show the infobar.
|
||||
PopupNotifications.getNotification("translate").anchorElement.click();
|
||||
waitForCondition(() => !!notificationBox.getNotificationWithValue("translation"), () => {
|
||||
ok(!!notificationBox.getNotificationWithValue("translation"),
|
||||
"there's a 'translate' notification");
|
||||
waitForCondition(hasTranslationInfoBar, () => {
|
||||
ok(hasTranslationInfoBar(), "there's a 'translate' notification");
|
||||
aFinishCallback();
|
||||
}, "timeout waiting for the info bar to reappear");
|
||||
}
|
||||
|
@ -689,7 +689,7 @@ function initChromeDebugger(aOnClose) {
|
||||
let deferred = promise.defer();
|
||||
|
||||
// Wait for the toolbox process to start...
|
||||
BrowserToolboxProcess.init(aOnClose, aProcess => {
|
||||
BrowserToolboxProcess.init(aOnClose, (aEvent, aProcess) => {
|
||||
info("Browser toolbox process started successfully.");
|
||||
|
||||
prepareDebugger(aProcess);
|
||||
|
@ -398,16 +398,12 @@ SelectorSearch.prototype = {
|
||||
/**
|
||||
* Populates the suggestions list and show the suggestion popup.
|
||||
*/
|
||||
_showPopup: function(aList, aFirstPart) {
|
||||
_showPopup: function(aList, aFirstPart, aState) {
|
||||
let total = 0;
|
||||
let query = this.searchBox.value;
|
||||
let toLowerCase = false;
|
||||
let items = [];
|
||||
// In case of tagNames, change the case to small.
|
||||
if (query.match(/.*[\.#][^\.#]{0,}$/) == null) {
|
||||
toLowerCase = true;
|
||||
}
|
||||
for (let [value, count] of aList) {
|
||||
|
||||
for (let [value, count, state] of aList) {
|
||||
// for cases like 'div ' or 'div >' or 'div+'
|
||||
if (query.match(/[\s>+]$/)) {
|
||||
value = query + value;
|
||||
@ -422,14 +418,27 @@ SelectorSearch.prototype = {
|
||||
let lastPart = query.match(/[a-zA-Z][#\.][^#\.\s>+]*$/)[0];
|
||||
value = query.slice(0, -1 * lastPart.length + 1) + value;
|
||||
}
|
||||
|
||||
let item = {
|
||||
preLabel: query,
|
||||
label: value,
|
||||
count: count
|
||||
};
|
||||
if (toLowerCase) {
|
||||
|
||||
// In case of tagNames, change te case to small
|
||||
if (value.match(/.*[\.#][^\.#]{0,}$/) == null) {
|
||||
item.label = value.toLowerCase();
|
||||
}
|
||||
|
||||
// In case the query's state is tag and the item's state is id or class
|
||||
// adjust the preLabel
|
||||
if (aState === this.States.TAG && state === this.States.CLASS) {
|
||||
item.preLabel = "." + item.preLabel;
|
||||
}
|
||||
if (aState === this.States.TAG && state === this.States.ID) {
|
||||
item.preLabel = "#" + item.preLabel;
|
||||
}
|
||||
|
||||
items.unshift(item);
|
||||
if (++total > MAX_SUGGESTIONS - 1) {
|
||||
break;
|
||||
@ -450,19 +459,21 @@ SelectorSearch.prototype = {
|
||||
*/
|
||||
showSuggestions: function() {
|
||||
let query = this.searchBox.value;
|
||||
let state = this.state;
|
||||
let firstPart = "";
|
||||
if (this.state == this.States.TAG) {
|
||||
|
||||
if (state == this.States.TAG) {
|
||||
// gets the tag that is being completed. For ex. 'div.foo > s' returns 's',
|
||||
// 'di' returns 'di' and likewise.
|
||||
firstPart = (query.match(/[\s>+]?([a-zA-Z]*)$/) || ["", query])[1];
|
||||
query = query.slice(0, query.length - firstPart.length);
|
||||
}
|
||||
else if (this.state == this.States.CLASS) {
|
||||
else if (state == this.States.CLASS) {
|
||||
// gets the class that is being completed. For ex. '.foo.b' returns 'b'
|
||||
firstPart = query.match(/\.([^\.]*)$/)[1];
|
||||
query = query.slice(0, query.length - firstPart.length - 1);
|
||||
}
|
||||
else if (this.state == this.States.ID) {
|
||||
else if (state == this.States.ID) {
|
||||
// gets the id that is being completed. For ex. '.foo#b' returns 'b'
|
||||
firstPart = query.match(/#([^#]*)$/)[1];
|
||||
query = query.slice(0, query.length - firstPart.length - 1);
|
||||
@ -472,21 +483,24 @@ SelectorSearch.prototype = {
|
||||
if (/[\s+>~]$/.test(query)) {
|
||||
query += "*";
|
||||
}
|
||||
|
||||
this._currentSuggesting = query;
|
||||
return this.walker.getSuggestionsForQuery(query, firstPart, this.state).then(result => {
|
||||
return this.walker.getSuggestionsForQuery(query, firstPart, state).then(result => {
|
||||
if (this._currentSuggesting != result.query) {
|
||||
// This means that this response is for a previous request and the user
|
||||
// as since typed something extra leading to a new request.
|
||||
return;
|
||||
}
|
||||
this._lastToLastValidSearch = this._lastValidSearch;
|
||||
if (this.state == this.States.CLASS) {
|
||||
|
||||
if (state == this.States.CLASS) {
|
||||
firstPart = "." + firstPart;
|
||||
}
|
||||
else if (this.state == this.States.ID) {
|
||||
else if (state == this.States.ID) {
|
||||
firstPart = "#" + firstPart;
|
||||
}
|
||||
this._showPopup(result.suggestions, firstPart);
|
||||
|
||||
this._showPopup(result.suggestions, firstPart, state);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
@ -50,3 +50,4 @@ support-files =
|
||||
[browser_inspector_bug_958169_switch_to_inspector_on_pick.js]
|
||||
[browser_inspector_bug_961771_picker_stops_on_tool_select.js]
|
||||
[browser_inspector_bug_962478_picker_stops_on_destroy.js]
|
||||
[browser_inspector_search-suggests-ids-and-classes.js]
|
||||
|
@ -15,7 +15,7 @@ function test()
|
||||
// ] count can be left to represent 1
|
||||
// ]
|
||||
let keyStates = [
|
||||
["d", [["div", 4]]],
|
||||
["d", [["div", 4], ["#d1", 1], ["#d2", 1]]],
|
||||
["i", [["div", 4]]],
|
||||
["v", []],
|
||||
[" ", [["div div", 2], ["div span", 2]]],
|
||||
@ -25,7 +25,7 @@ function test()
|
||||
["VK_BACK_SPACE", [["div div", 2], ["div span", 2]]],
|
||||
["VK_BACK_SPACE", []],
|
||||
["VK_BACK_SPACE", [["div", 4]]],
|
||||
["VK_BACK_SPACE", [["div", 4]]],
|
||||
["VK_BACK_SPACE", [["div", 4], ["#d1", 1], ["#d2", 1]]],
|
||||
["VK_BACK_SPACE", []],
|
||||
["p", []],
|
||||
[" ", [["p strong"]]],
|
||||
|
@ -15,7 +15,7 @@ function test()
|
||||
// ] count can be left to represent 1
|
||||
// ]
|
||||
let keyStates = [
|
||||
["d", [["div", 2]]],
|
||||
["d", [["div", 2], ["#d1", 1], ["#d2", 1]]],
|
||||
["i", [["div", 2]]],
|
||||
["v", []],
|
||||
[".", [["div.c1"]]],
|
||||
@ -23,7 +23,7 @@ function test()
|
||||
["#", [["div#d1"], ["div#d2"]]],
|
||||
["VK_BACK_SPACE", []],
|
||||
["VK_BACK_SPACE", [["div", 2]]],
|
||||
["VK_BACK_SPACE", [["div", 2]]],
|
||||
["VK_BACK_SPACE", [["div", 2], ["#d1", 1], ["#d2", 1]]],
|
||||
["VK_BACK_SPACE", []],
|
||||
[".", [[".c1", 3], [".c2"]]],
|
||||
["c", [[".c1", 3], [".c2"]]],
|
||||
|
@ -0,0 +1,125 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
// Test that the selector-search input proposes ids and classes even when . and
|
||||
// # is missing, but that this only occurs when the query is one word (no
|
||||
// selector combination)
|
||||
|
||||
function test()
|
||||
{
|
||||
waitForExplicitFinish();
|
||||
|
||||
let inspector, searchBox, state, popup;
|
||||
|
||||
// The various states of the inspector: [key, suggestions array]
|
||||
// [
|
||||
// what key to press,
|
||||
// suggestions array with count [
|
||||
// [suggestion1, count1], [suggestion2] ...
|
||||
// ] count can be left to represent 1
|
||||
// ]
|
||||
let keyStates = [
|
||||
["s", [["span", 1], [".span", 1], ["#span", 1]]],
|
||||
["p", [["span", 1], [".span", 1], ["#span", 1]]],
|
||||
["a", [["span", 1], [".span", 1], ["#span", 1]]],
|
||||
["n", []],
|
||||
[" ", [["span div", 1]]],
|
||||
["d", [["span div", 1]]], // mixed tag/class/id suggestions only work for the first word
|
||||
["VK_BACK_SPACE", [["span div", 1]]],
|
||||
["VK_BACK_SPACE", []],
|
||||
["VK_BACK_SPACE", [["span", 1], [".span", 1], ["#span", 1]]],
|
||||
["VK_BACK_SPACE", [["span", 1], [".span", 1], ["#span", 1]]],
|
||||
["VK_BACK_SPACE", [["span", 1], [".span", 1], ["#span", 1]]],
|
||||
["VK_BACK_SPACE", []],
|
||||
// Test that mixed tags, classes and ids are grouped by types, sorted by
|
||||
// count and alphabetical order
|
||||
["b", [
|
||||
["button", 3],
|
||||
["body", 1],
|
||||
[".bc", 3],
|
||||
[".ba", 1],
|
||||
[".bb", 1],
|
||||
["#ba", 1],
|
||||
["#bb", 1],
|
||||
["#bc", 1]
|
||||
]],
|
||||
];
|
||||
|
||||
gBrowser.selectedTab = gBrowser.addTab();
|
||||
gBrowser.selectedBrowser.addEventListener("load", function onload() {
|
||||
gBrowser.selectedBrowser.removeEventListener("load", onload, true);
|
||||
waitForFocus(setupTest, content);
|
||||
}, true);
|
||||
|
||||
content.location = "data:text/html," +
|
||||
"<span class='span' id='span'>" +
|
||||
" <div class='div' id='div'></div>" +
|
||||
"</span>" +
|
||||
"<button class='ba bc' id='bc'></button>" +
|
||||
"<button class='bb bc' id='bb'></button>" +
|
||||
"<button class='bc' id='ba'></button>";
|
||||
|
||||
function $(id) {
|
||||
if (id == null) return null;
|
||||
return content.document.getElementById(id);
|
||||
}
|
||||
|
||||
function setupTest()
|
||||
{
|
||||
openInspector(startTest);
|
||||
}
|
||||
|
||||
function startTest(aInspector)
|
||||
{
|
||||
inspector = aInspector;
|
||||
|
||||
searchBox =
|
||||
inspector.panelWin.document.getElementById("inspector-searchbox");
|
||||
popup = inspector.searchSuggestions.searchPopup;
|
||||
|
||||
focusSearchBoxUsingShortcut(inspector.panelWin, function() {
|
||||
searchBox.addEventListener("command", checkState, true);
|
||||
checkStateAndMoveOn(0);
|
||||
});
|
||||
}
|
||||
|
||||
function checkStateAndMoveOn(index) {
|
||||
if (index == keyStates.length) {
|
||||
finishUp();
|
||||
return;
|
||||
}
|
||||
|
||||
let [key, suggestions] = keyStates[index];
|
||||
state = index;
|
||||
|
||||
info("pressing key " + key + " to get suggestions " +
|
||||
JSON.stringify(suggestions));
|
||||
EventUtils.synthesizeKey(key, {}, inspector.panelWin);
|
||||
}
|
||||
|
||||
function checkState(event) {
|
||||
inspector.searchSuggestions._lastQuery.then(() => {
|
||||
let [key, suggestions] = keyStates[state];
|
||||
let actualSuggestions = popup.getItems();
|
||||
is(popup.isOpen ? actualSuggestions.length: 0, suggestions.length,
|
||||
"There are expected number of suggestions at " + state + "th step.");
|
||||
actualSuggestions.reverse();
|
||||
for (let i = 0; i < suggestions.length; i++) {
|
||||
is(suggestions[i][0], actualSuggestions[i].label,
|
||||
"The suggestion at " + i + "th index for " + state +
|
||||
"th step is correct.")
|
||||
is(suggestions[i][1] || 1, actualSuggestions[i].count,
|
||||
"The count for suggestion at " + i + "th index for " + state +
|
||||
"th step is correct.")
|
||||
}
|
||||
checkStateAndMoveOn(state + 1);
|
||||
});
|
||||
}
|
||||
|
||||
function finishUp() {
|
||||
searchBox = null;
|
||||
popup = null;
|
||||
gBrowser.removeCurrentTab();
|
||||
finish();
|
||||
}
|
||||
}
|
@ -757,39 +757,54 @@ CSSCompleter.prototype = {
|
||||
result = result.suggestions;
|
||||
let query = this.selector;
|
||||
let completion = [];
|
||||
for (let value of result) {
|
||||
for (let [value, count, state] of result) {
|
||||
switch(this.selectorState) {
|
||||
case SELECTOR_STATES.id:
|
||||
case SELECTOR_STATES.class:
|
||||
case SELECTOR_STATES.pseudo:
|
||||
if (/^[.:#]$/.test(this.completing)) {
|
||||
value[0] = query.slice(0, query.length - this.completing.length) +
|
||||
value[0];
|
||||
value = query.slice(0, query.length - this.completing.length) +
|
||||
value;
|
||||
} else {
|
||||
value[0] = query.slice(0, query.length - this.completing.length - 1) +
|
||||
value[0];
|
||||
value = query.slice(0, query.length - this.completing.length - 1) +
|
||||
value;
|
||||
}
|
||||
break;
|
||||
|
||||
case SELECTOR_STATES.tag:
|
||||
value[0] = query.slice(0, query.length - this.completing.length) +
|
||||
value[0];
|
||||
value = query.slice(0, query.length - this.completing.length) +
|
||||
value;
|
||||
break;
|
||||
|
||||
case SELECTOR_STATES.null:
|
||||
value[0] = query + value[0];
|
||||
value = query + value;
|
||||
break;
|
||||
|
||||
default:
|
||||
value[0] = query.slice(0, query.length - this.completing.length) +
|
||||
value[0];
|
||||
value = query.slice(0, query.length - this.completing.length) +
|
||||
value;
|
||||
}
|
||||
completion.push({
|
||||
label: value[0],
|
||||
|
||||
let item = {
|
||||
label: value,
|
||||
preLabel: query,
|
||||
text: value[0],
|
||||
score: value[1]
|
||||
});
|
||||
text: value,
|
||||
score: count
|
||||
};
|
||||
|
||||
// In case the query's state is tag and the item's state is id or class
|
||||
// adjust the preLabel
|
||||
if (this.selectorState === SELECTOR_STATES.tag &&
|
||||
state === SELECTOR_STATES.class) {
|
||||
item.preLabel = "." + item.preLabel;
|
||||
}
|
||||
if (this.selectorState === SELECTOR_STATES.tag &&
|
||||
state === SELECTOR_STATES.id) {
|
||||
item.preLabel = "#" + item.preLabel;
|
||||
}
|
||||
|
||||
completion.push(item);
|
||||
|
||||
if (completion.length > this.maxEntries - 1)
|
||||
break;
|
||||
}
|
||||
|
@ -21,8 +21,8 @@
|
||||
[[21, 9], ["-moz-calc", "auto", "calc", "inherit", "initial","unset"]],
|
||||
[[22, 5], ['color', 'color-interpolation', 'color-interpolation-filters']],
|
||||
[[25, 26], ['.devtools-toolbarbutton > tab',
|
||||
'.devtools-toolbarbutton > .toolbarbutton-menubutton-button',
|
||||
'.devtools-toolbarbutton > hbox']],
|
||||
'.devtools-toolbarbutton > hbox',
|
||||
'.devtools-toolbarbutton > .toolbarbutton-menubutton-button']],
|
||||
[[25, 31], ['.devtools-toolbarbutton > hbox.toolbarbutton-menubutton-button']],
|
||||
[[29, 20], ['.devtools-menulist:after', '.devtools-menulist:active']],
|
||||
[[30, 10], ['#devtools-anotherone', '#devtools-itjustgoeson', '#devtools-menu',
|
||||
@ -31,6 +31,6 @@
|
||||
[[43, 51], ['.devtools-toolbarbutton:not([checked=true]):hover:after',
|
||||
'.devtools-toolbarbutton:not([checked=true]):hover:active']],
|
||||
[[58, 36], ['!important;']],
|
||||
[[73, 42], [':last-child', ':lang(', ':last-of-type', ':link']],
|
||||
[[73, 42], [':lang(', ':last-of-type', ':link', ':last-child']],
|
||||
[[77, 25], ['.visible']],
|
||||
]
|
||||
|
@ -60,6 +60,5 @@
|
||||
}
|
||||
|
||||
.ruleview-colorswatch {
|
||||
display: inline-block;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
@ -382,6 +382,10 @@ Experiments.Experiments = function (policy=new Experiments.Policy()) {
|
||||
|
||||
this._shutdown = false;
|
||||
|
||||
// We need to tell when we first evaluated the experiments to fire an
|
||||
// experiments-changed notification when we only loaded completed experiments.
|
||||
this._firstEvaluate = true;
|
||||
|
||||
this.init();
|
||||
};
|
||||
|
||||
@ -1175,8 +1179,9 @@ Experiments.Experiments.prototype = {
|
||||
|
||||
gPrefs.set(PREF_ACTIVE_EXPERIMENT, activeExperiment != null);
|
||||
|
||||
if (activeChanged) {
|
||||
if (activeChanged || this._firstEvaluate) {
|
||||
Services.obs.notifyObservers(null, EXPERIMENTS_CHANGED_TOPIC, null);
|
||||
this._firstEvaluate = false;
|
||||
}
|
||||
|
||||
if ("@mozilla.org/toolkit/crash-reporter;1" in Cc && activeExperiment) {
|
||||
|
@ -72,6 +72,7 @@ ExperimentsService.prototype = {
|
||||
if (gExperimentsEnabled) {
|
||||
Services.obs.addObserver(this, "quit-application", false);
|
||||
Services.obs.addObserver(this, "sessionstore-state-finalized", false);
|
||||
Services.obs.addObserver(this, "EM-loaded", false);
|
||||
|
||||
if (gActiveExperiment) {
|
||||
this._initialized = true;
|
||||
@ -84,9 +85,20 @@ ExperimentsService.prototype = {
|
||||
CommonUtils.namedTimer(this._delayedInit, DELAY_INIT_MS, this, "_delayedInitTimer");
|
||||
}
|
||||
break;
|
||||
case "EM-loaded":
|
||||
if (!this._initialized) {
|
||||
Experiments.instance(); // for side effects
|
||||
this._initialized = true;
|
||||
|
||||
if (this._delayedInitTimer) {
|
||||
this._delayedInitTimer.clear();
|
||||
}
|
||||
}
|
||||
break;
|
||||
case "quit-application":
|
||||
Services.obs.removeObserver(this, "quit-application");
|
||||
Services.obs.removeObserver(this, "sessionstore-state-finalized");
|
||||
Services.obs.removeObserver(this, "EM-loaded");
|
||||
if (this._delayedInitTimer) {
|
||||
this._delayedInitTimer.clear();
|
||||
}
|
||||
|
@ -161,8 +161,8 @@ add_task(function* test_getExperiments() {
|
||||
defineNow(gPolicy, now);
|
||||
|
||||
yield experiments.updateManifest();
|
||||
Assert.equal(observerFireCount, 0,
|
||||
"Experiments observer should not have been called yet.");
|
||||
Assert.equal(observerFireCount, ++expectedObserverFireCount,
|
||||
"Experiments observer should have been called.");
|
||||
Assert.equal(experiments.getActiveExperimentID(), null,
|
||||
"getActiveExperimentID should return null");
|
||||
|
||||
@ -377,8 +377,8 @@ add_task(function* test_addonAlreadyInstalled() {
|
||||
let now = baseDate;
|
||||
defineNow(gPolicy, now);
|
||||
yield experiments.updateManifest();
|
||||
Assert.equal(observerFireCount, 0,
|
||||
"Experiments observer should not have been called yet.");
|
||||
Assert.equal(observerFireCount, ++expectedObserverFireCount,
|
||||
"Experiments observer should have been called.");
|
||||
let list = yield experiments.getExperiments();
|
||||
Assert.equal(list.length, 0, "Experiment list should be empty.");
|
||||
|
||||
@ -681,8 +681,8 @@ add_task(function* test_installFailure() {
|
||||
let now = baseDate;
|
||||
defineNow(gPolicy, now);
|
||||
yield experiments.updateManifest();
|
||||
Assert.equal(observerFireCount, 0,
|
||||
"Experiments observer should not have been called yet.");
|
||||
Assert.equal(observerFireCount, ++expectedObserverFireCount,
|
||||
"Experiments observer should have been called.");
|
||||
let list = yield experiments.getExperiments();
|
||||
Assert.equal(list.length, 0, "Experiment list should be empty.");
|
||||
|
||||
@ -787,8 +787,8 @@ add_task(function* test_userDisabledAndUpdated() {
|
||||
let now = baseDate;
|
||||
defineNow(gPolicy, now);
|
||||
yield experiments.updateManifest();
|
||||
Assert.equal(observerFireCount, 0,
|
||||
"Experiments observer should not have been called yet.");
|
||||
Assert.equal(observerFireCount, ++expectedObserverFireCount,
|
||||
"Experiments observer should have been called.");
|
||||
let list = yield experiments.getExperiments();
|
||||
Assert.equal(list.length, 0, "Experiment list should be empty.");
|
||||
|
||||
@ -886,8 +886,8 @@ add_task(function* test_updateActiveExperiment() {
|
||||
let now = baseDate;
|
||||
defineNow(gPolicy, now);
|
||||
yield experiments.updateManifest();
|
||||
Assert.equal(observerFireCount, 0,
|
||||
"Experiments observer should not have been called yet.");
|
||||
Assert.equal(observerFireCount, ++expectedObserverFireCount,
|
||||
"Experiments observer should have been called.");
|
||||
let list = yield experiments.getExperiments();
|
||||
Assert.equal(list.length, 0, "Experiment list should be empty.");
|
||||
|
||||
@ -977,8 +977,8 @@ add_task(function* test_disableActiveExperiment() {
|
||||
let now = baseDate;
|
||||
defineNow(gPolicy, now);
|
||||
yield experiments.updateManifest();
|
||||
Assert.equal(observerFireCount, 0,
|
||||
"Experiments observer should not have been called yet.");
|
||||
Assert.equal(observerFireCount, ++expectedObserverFireCount,
|
||||
"Experiments observer should have been called.");
|
||||
let list = yield experiments.getExperiments();
|
||||
Assert.equal(list.length, 0, "Experiment list should be empty.");
|
||||
|
||||
@ -1070,8 +1070,8 @@ add_task(function* test_freezePendingExperiment() {
|
||||
let now = baseDate;
|
||||
defineNow(gPolicy, now);
|
||||
yield experiments.updateManifest();
|
||||
Assert.equal(observerFireCount, 0,
|
||||
"Experiments observer should not have been called yet.");
|
||||
Assert.equal(observerFireCount, ++expectedObserverFireCount,
|
||||
"Experiments observer should have been called.");
|
||||
let list = yield experiments.getExperiments();
|
||||
Assert.equal(list.length, 0, "Experiment list should be empty.");
|
||||
|
||||
@ -1081,8 +1081,8 @@ add_task(function* test_freezePendingExperiment() {
|
||||
defineNow(gPolicy, now);
|
||||
gManifestObject.experiments[0].frozen = true;
|
||||
yield experiments.updateManifest();
|
||||
Assert.equal(observerFireCount, 0,
|
||||
"Experiments observer should not have been called.");
|
||||
Assert.equal(observerFireCount, expectedObserverFireCount,
|
||||
"Experiments observer should have not been called.");
|
||||
|
||||
list = yield experiments.getExperiments();
|
||||
Assert.equal(list.length, 0, "Experiment list should have no entries yet.");
|
||||
@ -1148,8 +1148,8 @@ add_task(function* test_freezeActiveExperiment() {
|
||||
let now = baseDate;
|
||||
defineNow(gPolicy, now);
|
||||
yield experiments.updateManifest();
|
||||
Assert.equal(observerFireCount, 0,
|
||||
"Experiments observer should not have been called yet.");
|
||||
Assert.equal(observerFireCount, ++expectedObserverFireCount,
|
||||
"Experiments observer should have been called.");
|
||||
let list = yield experiments.getExperiments();
|
||||
Assert.equal(list.length, 0, "Experiment list should be empty.");
|
||||
|
||||
@ -1240,8 +1240,8 @@ add_task(function* test_removeActiveExperiment() {
|
||||
let now = baseDate;
|
||||
defineNow(gPolicy, now);
|
||||
yield experiments.updateManifest();
|
||||
Assert.equal(observerFireCount, 0,
|
||||
"Experiments observer should not have been called yet.");
|
||||
Assert.equal(observerFireCount, ++expectedObserverFireCount,
|
||||
"Experiments observer should have been called.");
|
||||
let list = yield experiments.getExperiments();
|
||||
Assert.equal(list.length, 0, "Experiment list should be empty.");
|
||||
|
||||
@ -1321,8 +1321,8 @@ add_task(function* test_invalidUrl() {
|
||||
gTimerScheduleOffset = null;
|
||||
|
||||
yield experiments.updateManifest();
|
||||
Assert.equal(observerFireCount, expectedObserverFireCount,
|
||||
"Experiments observer should not have been called.");
|
||||
Assert.equal(observerFireCount, ++expectedObserverFireCount,
|
||||
"Experiments observer should have been called.");
|
||||
Assert.equal(gTimerScheduleOffset, null, "No new timer should have been scheduled.");
|
||||
|
||||
let list = yield experiments.getExperiments();
|
||||
@ -1375,8 +1375,8 @@ add_task(function* test_unexpectedUninstall() {
|
||||
let now = baseDate;
|
||||
defineNow(gPolicy, now);
|
||||
yield experiments.updateManifest();
|
||||
Assert.equal(observerFireCount, 0,
|
||||
"Experiments observer should not have been called yet.");
|
||||
Assert.equal(observerFireCount, ++expectedObserverFireCount,
|
||||
"Experiments observer should have been called.");
|
||||
let list = yield experiments.getExperiments();
|
||||
Assert.equal(list.length, 0, "Experiment list should be empty.");
|
||||
|
||||
|
@ -122,8 +122,8 @@ add_task(function* test_disableExperiments() {
|
||||
defineNow(gPolicy, now);
|
||||
|
||||
yield experiments.updateManifest();
|
||||
Assert.equal(observerFireCount, 0,
|
||||
"Experiments observer should not have been called yet.");
|
||||
Assert.equal(observerFireCount, ++expectedObserverFireCount,
|
||||
"Experiments observer should have been called.");
|
||||
let list = yield experiments.getExperiments();
|
||||
Assert.equal(list.length, 0, "Experiment list should be empty.");
|
||||
let addons = yield getExperimentAddons();
|
||||
|
@ -150,10 +150,24 @@ body {
|
||||
}
|
||||
|
||||
.computedview-colorswatch {
|
||||
display: inline-block;
|
||||
border-radius: 50%;
|
||||
width: 1em;
|
||||
height: 1em;
|
||||
vertical-align: text-top;
|
||||
-moz-margin-end: 5px;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.computedview-colorswatch::before {
|
||||
content: '';
|
||||
background-color: #eee;
|
||||
background-image: linear-gradient(45deg, #ccc 25%, transparent 25%, transparent 75%, #ccc 75%, #ccc),
|
||||
linear-gradient(45deg, #ccc 25%, transparent 25%, transparent 75%, #ccc 75%, #ccc);
|
||||
background-size: 12px 12px;
|
||||
background-position: 0 0, 6px 6px;
|
||||
position: absolute;
|
||||
border-radius: 50%;
|
||||
width: 1em;
|
||||
height: 1em;
|
||||
z-index: -1;
|
||||
}
|
||||
|
@ -168,10 +168,24 @@ body {
|
||||
}
|
||||
|
||||
.computedview-colorswatch {
|
||||
display: inline-block;
|
||||
border-radius: 50%;
|
||||
width: 1em;
|
||||
height: 1em;
|
||||
vertical-align: text-top;
|
||||
-moz-margin-end: 5px;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.computedview-colorswatch::before {
|
||||
content: '';
|
||||
background-color: #eee;
|
||||
background-image: linear-gradient(45deg, #ccc 25%, transparent 25%, transparent 75%, #ccc 75%, #ccc),
|
||||
linear-gradient(45deg, #ccc 25%, transparent 25%, transparent 75%, #ccc 75%, #ccc);
|
||||
background-size: 12px 12px;
|
||||
background-position: 0 0, 6px 6px;
|
||||
position: absolute;
|
||||
border-radius: 50%;
|
||||
width: 1em;
|
||||
height: 1em;
|
||||
z-index: -1;
|
||||
}
|
||||
|
@ -139,8 +139,7 @@
|
||||
}
|
||||
|
||||
.ruleview-colorswatch,
|
||||
.computedview-colorswatch,
|
||||
.markupview-colorswatch {
|
||||
.computedview-colorswatch {
|
||||
box-shadow: 0 0 0 1px #818181;
|
||||
}
|
||||
|
||||
|
@ -138,8 +138,7 @@
|
||||
}
|
||||
|
||||
.ruleview-colorswatch,
|
||||
.computedview-colorswatch,
|
||||
.markupview-colorswatch {
|
||||
.computedview-colorswatch {
|
||||
box-shadow: 0 0 0 1px #c4c4c4;
|
||||
}
|
||||
|
||||
|
@ -118,6 +118,21 @@
|
||||
height: 1em;
|
||||
vertical-align: text-top;
|
||||
-moz-margin-end: 5px;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.ruleview-colorswatch::before {
|
||||
content: '';
|
||||
background-color: #eee;
|
||||
background-image: linear-gradient(45deg, #ccc 25%, transparent 25%, transparent 75%, #ccc 75%, #ccc),
|
||||
linear-gradient(45deg, #ccc 25%, transparent 25%, transparent 75%, #ccc 75%, #ccc);
|
||||
background-size: 12px 12px;
|
||||
background-position: 0 0, 6px 6px;
|
||||
position: absolute;
|
||||
border-radius: 50%;
|
||||
width: 1em;
|
||||
height: 1em;
|
||||
z-index: -1;
|
||||
}
|
||||
|
||||
.ruleview-overridden {
|
||||
|
@ -769,6 +769,7 @@ toolbarbutton[sdk-button="true"][cui-areatype="toolbar"] > .toolbarbutton-icon {
|
||||
border-bottom-left-radius: 0;
|
||||
}
|
||||
|
||||
#nav-bar .toolbarbutton-1:not([disabled=true]) > .toolbarbutton-menubutton-button[open] + .toolbarbutton-menubutton-dropmarker > .dropmarker-icon,
|
||||
#nav-bar .toolbarbutton-1:not([disabled]):-moz-any(:hover,[open]) > .toolbarbutton-menubutton-button > .toolbarbutton-icon,
|
||||
#nav-bar .toolbarbutton-1:not([disabled]):hover > .toolbarbutton-menubutton-dropmarker > .dropmarker-icon,
|
||||
#nav-bar .toolbarbutton-1:not([disabled]):not([checked]):not([open]):not(:active):hover > .toolbarbutton-icon,
|
||||
@ -811,7 +812,7 @@ toolbarbutton[sdk-button="true"][cui-areatype="toolbar"] > .toolbarbutton-icon {
|
||||
(-moz-os-version: windows-win7) {
|
||||
%endif
|
||||
/* < Win8 */
|
||||
#nav-bar .toolbarbutton-1 > .toolbarbutton-menubutton-button:not([disabled]):hover:active > .toolbarbutton-icon,
|
||||
#nav-bar .toolbarbutton-1 > .toolbarbutton-menubutton-button:not([disabled=true]):-moz-any(:hover:active, [open]) > .toolbarbutton-icon,
|
||||
#nav-bar .toolbarbutton-1[open] > .toolbarbutton-menubutton-dropmarker:not([disabled]) > .dropmarker-icon,
|
||||
#nav-bar .toolbarbutton-1:not([disabled]):-moz-any([open],[checked],:hover:active) > .toolbarbutton-icon,
|
||||
#nav-bar .toolbarbutton-1:not([disabled]):-moz-any([open],[checked],:hover:active) > .toolbarbutton-text,
|
||||
|
@ -168,10 +168,24 @@ body {
|
||||
}
|
||||
|
||||
.computedview-colorswatch {
|
||||
display: inline-block;
|
||||
border-radius: 50%;
|
||||
width: 1em;
|
||||
height: 1em;
|
||||
vertical-align: text-top;
|
||||
-moz-margin-end: 5px;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.computedview-colorswatch::before {
|
||||
content: '';
|
||||
background-color: #eee;
|
||||
background-image: linear-gradient(45deg, #ccc 25%, transparent 25%, transparent 75%, #ccc 75%, #ccc),
|
||||
linear-gradient(45deg, #ccc 25%, transparent 25%, transparent 75%, #ccc 75%, #ccc);
|
||||
background-size: 12px 12px;
|
||||
background-position: 0 0, 6px 6px;
|
||||
position: absolute;
|
||||
border-radius: 50%;
|
||||
width: 1em;
|
||||
height: 1em;
|
||||
z-index: -1;
|
||||
}
|
||||
|
@ -598,12 +598,8 @@ public class SUTAgentAndroid extends Activity
|
||||
wc.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.IEEE8021X);
|
||||
}
|
||||
|
||||
wc.hiddenSSID = false;
|
||||
wc.status = WifiConfiguration.Status.ENABLED;
|
||||
|
||||
wc.password.setValue("\"password\"");
|
||||
wc.identity.setValue("\"bmoss@mozilla.com\"");
|
||||
|
||||
if (!wifi.isWifiEnabled())
|
||||
wifi.setWifiEnabled(true);
|
||||
|
||||
|
@ -30,8 +30,7 @@ public class PanelsPreference extends CustomListPreference {
|
||||
|
||||
/**
|
||||
* Index of the context menu button for controlling display options.
|
||||
* For (removable) Dynamic panels, this button removes the panel.
|
||||
* For built-in panels, this button toggles showing or hiding the panel.
|
||||
* This button toggles showing or hiding the panel.
|
||||
*/
|
||||
private static final int INDEX_DISPLAY_BUTTON = 1;
|
||||
private static final int INDEX_REORDER_BUTTON = 2;
|
||||
@ -45,7 +44,6 @@ public class PanelsPreference extends CustomListPreference {
|
||||
|
||||
private View preferenceView;
|
||||
protected boolean mIsHidden = false;
|
||||
private boolean mIsRemovable;
|
||||
|
||||
private boolean mAnimate;
|
||||
private static final int ANIMATION_DURATION_MS = 400;
|
||||
@ -54,9 +52,8 @@ public class PanelsPreference extends CustomListPreference {
|
||||
private int mPositionState = -1;
|
||||
private final int mIndex;
|
||||
|
||||
public PanelsPreference(Context context, CustomListCategory parentCategory, boolean isRemovable, int index, boolean animate) {
|
||||
public PanelsPreference(Context context, CustomListCategory parentCategory, int index, boolean animate) {
|
||||
super(context, parentCategory);
|
||||
mIsRemovable = isRemovable;
|
||||
mIndex = index;
|
||||
mAnimate = animate;
|
||||
}
|
||||
@ -98,11 +95,6 @@ public class PanelsPreference extends CustomListPreference {
|
||||
final Resources res = getContext().getResources();
|
||||
final String labelReorder = res.getString(R.string.pref_panels_reorder);
|
||||
|
||||
if (mIsRemovable) {
|
||||
return new String[] { LABEL_SET_AS_DEFAULT, LABEL_REMOVE, labelReorder };
|
||||
}
|
||||
|
||||
// Built-in panels can't be removed, so use show/hide options.
|
||||
LABEL_HIDE = res.getString(R.string.pref_panels_hide);
|
||||
LABEL_SHOW = res.getString(R.string.pref_panels_show);
|
||||
|
||||
@ -131,14 +123,8 @@ public class PanelsPreference extends CustomListPreference {
|
||||
break;
|
||||
|
||||
case INDEX_DISPLAY_BUTTON:
|
||||
// Handle display options for the panel.
|
||||
if (mIsRemovable) {
|
||||
// For removable panels, the button displays text for removing the panel.
|
||||
mParentCategory.uninstall(this);
|
||||
} else {
|
||||
// Otherwise, the button toggles between text for showing or hiding the panel.
|
||||
((PanelsPreferenceCategory) mParentCategory).setHidden(this, !mIsHidden);
|
||||
}
|
||||
// The button toggles between text for showing or hiding the panel.
|
||||
((PanelsPreferenceCategory) mParentCategory).setHidden(this, !mIsHidden);
|
||||
break;
|
||||
|
||||
case INDEX_REORDER_BUTTON:
|
||||
@ -157,10 +143,8 @@ public class PanelsPreference extends CustomListPreference {
|
||||
super.configureShownDialog();
|
||||
|
||||
// Handle Show/Hide buttons.
|
||||
if (!mIsRemovable) {
|
||||
final TextView hideButton = (TextView) mDialog.getListView().getChildAt(INDEX_DISPLAY_BUTTON);
|
||||
hideButton.setText(mIsHidden ? LABEL_SHOW : LABEL_HIDE);
|
||||
}
|
||||
final TextView hideButton = (TextView) mDialog.getListView().getChildAt(INDEX_DISPLAY_BUTTON);
|
||||
hideButton.setText(mIsHidden ? LABEL_SHOW : LABEL_HIDE);
|
||||
}
|
||||
|
||||
|
||||
|
@ -22,9 +22,6 @@ public class PanelsPreferenceCategory extends CustomListCategory {
|
||||
protected HomeConfig mHomeConfig;
|
||||
protected HomeConfig.Editor mConfigEditor;
|
||||
|
||||
// Account for the fake "Add Panel" preference in preference counting.
|
||||
private static final int PANEL_PREFS_OFFSET = 1;
|
||||
|
||||
protected UiAsyncTask<Void, Void, HomeConfig.State> mLoadTask;
|
||||
|
||||
public PanelsPreferenceCategory(Context context) {
|
||||
@ -87,13 +84,8 @@ public class PanelsPreferenceCategory extends CustomListCategory {
|
||||
* @param String panelId of panel to be animated.
|
||||
*/
|
||||
public void refresh(State state, String animatePanelId) {
|
||||
// Clear all the existing home panels, but leave the
|
||||
// first item (Add panels).
|
||||
int prefCount = getPreferenceCount();
|
||||
while (prefCount > 1) {
|
||||
removePreference(getPreference(1));
|
||||
prefCount--;
|
||||
}
|
||||
// Clear all the existing home panels.
|
||||
removeAll();
|
||||
|
||||
if (state == null) {
|
||||
loadHomeConfig(animatePanelId);
|
||||
@ -105,13 +97,11 @@ public class PanelsPreferenceCategory extends CustomListCategory {
|
||||
private void displayHomeConfig(HomeConfig.State configState, String animatePanelId) {
|
||||
int index = 0;
|
||||
for (PanelConfig panelConfig : configState) {
|
||||
final boolean isRemovable = panelConfig.isDynamic();
|
||||
|
||||
// Create and add the pref.
|
||||
final String panelId = panelConfig.getId();
|
||||
final boolean animate = TextUtils.equals(animatePanelId, panelId);
|
||||
|
||||
final PanelsPreference pref = new PanelsPreference(getContext(), PanelsPreferenceCategory.this, isRemovable, index, animate);
|
||||
final PanelsPreference pref = new PanelsPreference(getContext(), PanelsPreferenceCategory.this, index, animate);
|
||||
pref.setTitle(panelConfig.getTitle());
|
||||
pref.setKey(panelConfig.getId());
|
||||
// XXX: Pull icon from PanelInfo.
|
||||
@ -132,7 +122,7 @@ public class PanelsPreferenceCategory extends CustomListCategory {
|
||||
final int prefCount = getPreferenceCount();
|
||||
|
||||
// Pass in position state to first and last preference.
|
||||
final PanelsPreference firstPref = (PanelsPreference) getPreference(PANEL_PREFS_OFFSET);
|
||||
final PanelsPreference firstPref = (PanelsPreference) getPreference(0);
|
||||
firstPref.setIsFirst();
|
||||
|
||||
final PanelsPreference lastPref = (PanelsPreference) getPreference(prefCount - 1);
|
||||
@ -148,8 +138,7 @@ public class PanelsPreferenceCategory extends CustomListCategory {
|
||||
|
||||
final int prefCount = getPreferenceCount();
|
||||
|
||||
// First preference (index 0) is Preference to add panels.
|
||||
for (int i = 1; i < prefCount; i++) {
|
||||
for (int i = 0; i < prefCount; i++) {
|
||||
final PanelsPreference pref = (PanelsPreference) getPreference(i);
|
||||
|
||||
if (defaultPanelId.equals(pref.getKey())) {
|
||||
|
@ -9,13 +9,7 @@
|
||||
android:enabled="false">
|
||||
|
||||
<org.mozilla.gecko.preferences.PanelsPreferenceCategory
|
||||
android:title="@string/pref_category_home_panels">
|
||||
|
||||
<Preference android:key="android.not_a_preference.home.add_panel"
|
||||
android:title="@string/pref_home_add_panel"
|
||||
android:icon="@drawable/icon_new_home_panel" />
|
||||
|
||||
</org.mozilla.gecko.preferences.PanelsPreferenceCategory>
|
||||
android:title="@string/pref_category_home_panels"/>
|
||||
|
||||
<PreferenceCategory android:title="@string/pref_category_home_content_settings">
|
||||
|
||||
|
@ -1,354 +1,617 @@
|
||||
/* 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/. */
|
||||
* 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";
|
||||
let Ci = Components.interfaces, Cc = Components.classes, Cu = Components.utils;
|
||||
|
||||
const Cu = Components.utils;
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
Cu.import("resource://gre/modules/DownloadUtils.jsm");
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
Cu.import("resource://gre/modules/PluralForm.jsm");
|
||||
Cu.import("resource://gre/modules/PrivateBrowsingUtils.jsm");
|
||||
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm", this);
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "Downloads", "resource://gre/modules/Downloads.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "DownloadUtils", "resource://gre/modules/DownloadUtils.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "OS", "resource://gre/modules/osfile.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "PluralForm", "resource://gre/modules/PluralForm.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "Services", "resource://gre/modules/Services.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyGetter(this, "strings",
|
||||
() => Services.strings.createBundle("chrome://browser/locale/aboutDownloads.properties"));
|
||||
let gStrings = Services.strings.createBundle("chrome://browser/locale/aboutDownloads.properties");
|
||||
|
||||
function deleteDownload(download) {
|
||||
download.finalize(true).then(null, Cu.reportError);
|
||||
OS.File.remove(download.target.path).then(null, ex => {
|
||||
if (!(ex instanceof OS.File.Error && ex.becauseNoSuchFile)) {
|
||||
Cu.reportError(ex);
|
||||
}
|
||||
});
|
||||
}
|
||||
let downloadTemplate =
|
||||
"<li downloadGUID='{guid}' class='list-item' role='button' state='{state}' contextmenu='downloadmenu'>" +
|
||||
"<img class='icon' src='{icon}'/>" +
|
||||
"<div class='details'>" +
|
||||
"<div class='row'>" +
|
||||
// This is a hack so that we can crop this label in its center
|
||||
"<xul:label class='title' crop='center' value='{target}'/>" +
|
||||
"<div class='date'>{date}</div>" +
|
||||
"</div>" +
|
||||
"<div class='size'>{size}</div>" +
|
||||
"<div class='domain'>{domain}</div>" +
|
||||
"<div class='displayState'>{displayState}</div>" +
|
||||
"</div>" +
|
||||
"</li>";
|
||||
|
||||
let contextMenu = {
|
||||
_items: [],
|
||||
_targetDownload: null,
|
||||
XPCOMUtils.defineLazyGetter(window, "gChromeWin", function ()
|
||||
window.QueryInterface(Ci.nsIInterfaceRequestor)
|
||||
.getInterface(Ci.nsIWebNavigation)
|
||||
.QueryInterface(Ci.nsIDocShellTreeItem)
|
||||
.rootTreeItem
|
||||
.QueryInterface(Ci.nsIInterfaceRequestor)
|
||||
.getInterface(Ci.nsIDOMWindow)
|
||||
.QueryInterface(Ci.nsIDOMChromeWindow));
|
||||
|
||||
init: function () {
|
||||
let element = document.getElementById("downloadmenu");
|
||||
element.addEventListener("click",
|
||||
event => event.download = this._targetDownload,
|
||||
true);
|
||||
|
||||
this._items = [
|
||||
new ContextMenuItem("open",
|
||||
download => download.succeeded,
|
||||
download => download.launch().then(null, Cu.reportError)),
|
||||
new ContextMenuItem("retry",
|
||||
download => download.error ||
|
||||
(download.canceled && !download.hasPartialData),
|
||||
download => download.start().then(null, Cu.reportError)),
|
||||
new ContextMenuItem("remove",
|
||||
download => download.stopped,
|
||||
download => {
|
||||
Downloads.getList(Downloads.ALL)
|
||||
.then(list => list.remove(download))
|
||||
.then(null, Cu.reportError);
|
||||
deleteDownload(download);
|
||||
}),
|
||||
new ContextMenuItem("pause",
|
||||
download => !download.stopped,
|
||||
download => download.cancel().then(null, Cu.reportError)),
|
||||
new ContextMenuItem("resume",
|
||||
download => download.canceled && download.hasPartialData,
|
||||
download => download.start().then(null, Cu.reportError)),
|
||||
new ContextMenuItem("cancel",
|
||||
download => !download.stopped ||
|
||||
(download.canceled && download.hasPartialData),
|
||||
download => {
|
||||
download.cancel().then(null, Cu.reportError);
|
||||
download.removePartialData().then(null, Cu.reportError);
|
||||
}),
|
||||
// following menu item is a global action
|
||||
new ContextMenuItem("removeall",
|
||||
() => downloadLists.finished.length > 0,
|
||||
() => downloadLists.removeFinished())
|
||||
var ContextMenus = {
|
||||
target: null,
|
||||
|
||||
init: function() {
|
||||
document.addEventListener("contextmenu", this, false);
|
||||
document.getElementById("contextmenu-open").addEventListener("click", this.open.bind(this), false);
|
||||
document.getElementById("contextmenu-retry").addEventListener("click", this.retry.bind(this), false);
|
||||
document.getElementById("contextmenu-remove").addEventListener("click", this.remove.bind(this), false);
|
||||
document.getElementById("contextmenu-pause").addEventListener("click", this.pause.bind(this), false);
|
||||
document.getElementById("contextmenu-resume").addEventListener("click", this.resume.bind(this), false);
|
||||
document.getElementById("contextmenu-cancel").addEventListener("click", this.cancel.bind(this), false);
|
||||
document.getElementById("contextmenu-removeall").addEventListener("click", this.removeAll.bind(this), false);
|
||||
this.items = [
|
||||
{ name: "open", states: [Downloads._dlmgr.DOWNLOAD_FINISHED] },
|
||||
{ name: "retry", states: [Downloads._dlmgr.DOWNLOAD_FAILED, Downloads._dlmgr.DOWNLOAD_CANCELED] },
|
||||
{ name: "remove", states: [Downloads._dlmgr.DOWNLOAD_FINISHED,Downloads._dlmgr.DOWNLOAD_FAILED, Downloads._dlmgr.DOWNLOAD_CANCELED] },
|
||||
{ name: "removeall", states: [Downloads._dlmgr.DOWNLOAD_FINISHED,Downloads._dlmgr.DOWNLOAD_FAILED, Downloads._dlmgr.DOWNLOAD_CANCELED] },
|
||||
{ name: "pause", states: [Downloads._dlmgr.DOWNLOAD_DOWNLOADING] },
|
||||
{ name: "resume", states: [Downloads._dlmgr.DOWNLOAD_PAUSED] },
|
||||
{ name: "cancel", states: [Downloads._dlmgr.DOWNLOAD_DOWNLOADING, Downloads._dlmgr.DOWNLOAD_NOTSTARTED, Downloads._dlmgr.DOWNLOAD_QUEUED, Downloads._dlmgr.DOWNLOAD_PAUSED] },
|
||||
];
|
||||
},
|
||||
|
||||
addContextMenuEventListener: function (element) {
|
||||
element.addEventListener("contextmenu", this.onContextMenu.bind(this));
|
||||
handleEvent: function(event) {
|
||||
// store the target of context menu events so that we know which app to act on
|
||||
this.target = event.target;
|
||||
while (!this.target.hasAttribute("contextmenu")) {
|
||||
this.target = this.target.parentNode;
|
||||
}
|
||||
if (!this.target)
|
||||
return;
|
||||
|
||||
let state = parseInt(this.target.getAttribute("state"));
|
||||
for (let i = 0; i < this.items.length; i++) {
|
||||
var item = this.items[i];
|
||||
let enabled = (item.states.indexOf(state) > -1);
|
||||
if (enabled)
|
||||
document.getElementById("contextmenu-" + item.name).removeAttribute("hidden");
|
||||
else
|
||||
document.getElementById("contextmenu-" + item.name).setAttribute("hidden", "true");
|
||||
}
|
||||
},
|
||||
|
||||
onContextMenu: function (event) {
|
||||
let target = event.target;
|
||||
while (target && !target.download) {
|
||||
target = target.parentNode;
|
||||
}
|
||||
if (!target) {
|
||||
Cu.reportError("No download found for context menu target");
|
||||
event.preventDefault();
|
||||
return;
|
||||
}
|
||||
// Open shown only for downloads that completed successfully
|
||||
open: function(event) {
|
||||
Downloads.openDownload(this.target);
|
||||
this.target = null;
|
||||
},
|
||||
|
||||
// capture the target download for menu items to use in a click event
|
||||
this._targetDownload = target.download;
|
||||
for (let item of this._items) {
|
||||
item.updateVisibility(target.download);
|
||||
}
|
||||
// Retry shown when its failed, canceled, blocked(covered in failed, see _getState())
|
||||
retry: function (event) {
|
||||
Downloads.retryDownload(this.target);
|
||||
this.target = null;
|
||||
},
|
||||
|
||||
// Remove shown when its canceled, finished, failed(failed includes blocked and dirty, see _getState())
|
||||
remove: function (event) {
|
||||
Downloads.removeDownload(this.target);
|
||||
this.target = null;
|
||||
},
|
||||
|
||||
// Pause shown when item is currently downloading
|
||||
pause: function (event) {
|
||||
Downloads.pauseDownload(this.target);
|
||||
this.target = null;
|
||||
},
|
||||
|
||||
// Resume shown for paused items only
|
||||
resume: function (event) {
|
||||
Downloads.resumeDownload(this.target);
|
||||
this.target = null;
|
||||
},
|
||||
|
||||
// Cancel shown when its downloading, notstarted, queued or paused
|
||||
cancel: function (event) {
|
||||
Downloads.cancelDownload(this.target);
|
||||
this.target = null;
|
||||
},
|
||||
|
||||
removeAll: function(event) {
|
||||
Downloads.removeAll();
|
||||
this.target = null;
|
||||
}
|
||||
};
|
||||
|
||||
function ContextMenuItem(name, isVisible, action) {
|
||||
this.element = document.getElementById("contextmenu-" + name);
|
||||
this.isVisible = isVisible;
|
||||
|
||||
this.element.addEventListener("click", event => action(event.download));
|
||||
}
|
||||
|
||||
ContextMenuItem.prototype = {
|
||||
updateVisibility: function (download) {
|
||||
this.element.hidden = !this.isVisible(download);
|
||||
|
||||
let Downloads = {
|
||||
init: function dl_init() {
|
||||
function onClick(evt) {
|
||||
let target = evt.target;
|
||||
while (target.nodeName != "li") {
|
||||
target = target.parentNode;
|
||||
if (!target)
|
||||
return;
|
||||
}
|
||||
|
||||
Downloads.openDownload(target);
|
||||
}
|
||||
|
||||
this._normalList = document.getElementById("normal-downloads-list");
|
||||
this._privateList = document.getElementById("private-downloads-list");
|
||||
|
||||
this._normalList.addEventListener("click", onClick, false);
|
||||
this._privateList.addEventListener("click", onClick, false);
|
||||
|
||||
this._dlmgr = Cc["@mozilla.org/download-manager;1"].getService(Ci.nsIDownloadManager);
|
||||
this._dlmgr.addPrivacyAwareListener(this);
|
||||
|
||||
Services.obs.addObserver(this, "last-pb-context-exited", false);
|
||||
Services.obs.addObserver(this, "download-manager-remove-download-guid", false);
|
||||
|
||||
// If we have private downloads, show them all immediately. If we were to
|
||||
// add them asynchronously, there's a small chance we could get a
|
||||
// "last-pb-context-exited" notification before downloads are added to the
|
||||
// list, meaning we'd show private downloads without any private tabs open.
|
||||
let privateEntries = this.getDownloads({ isPrivate: true });
|
||||
this._stepAddEntries(privateEntries, this._privateList, privateEntries.length);
|
||||
|
||||
// Add non-private downloads
|
||||
let normalEntries = this.getDownloads({ isPrivate: false });
|
||||
this._stepAddEntries(normalEntries, this._normalList, 1, this._scrollToSelectedDownload.bind(this));
|
||||
ContextMenus.init();
|
||||
},
|
||||
|
||||
uninit: function dl_uninit() {
|
||||
let contextmenus = gChromeWin.NativeWindow.contextmenus;
|
||||
contextmenus.remove(this.openMenuItem);
|
||||
contextmenus.remove(this.removeMenuItem);
|
||||
contextmenus.remove(this.pauseMenuItem);
|
||||
contextmenus.remove(this.resumeMenuItem);
|
||||
contextmenus.remove(this.retryMenuItem);
|
||||
contextmenus.remove(this.cancelMenuItem);
|
||||
contextmenus.remove(this.deleteAllMenuItem);
|
||||
|
||||
this._dlmgr.removeListener(this);
|
||||
Services.obs.removeObserver(this, "last-pb-context-exited");
|
||||
Services.obs.removeObserver(this, "download-manager-remove-download-guid");
|
||||
},
|
||||
|
||||
onProgressChange: function(aWebProgress, aRequest, aCurSelfProgress, aMaxSelfProgress,
|
||||
aCurTotalProgress, aMaxTotalProgress, aDownload) { },
|
||||
onDownloadStateChange: function(aState, aDownload) {
|
||||
switch (aDownload.state) {
|
||||
case Ci.nsIDownloadManager.DOWNLOAD_FAILED:
|
||||
case Ci.nsIDownloadManager.DOWNLOAD_CANCELED:
|
||||
case Ci.nsIDownloadManager.DOWNLOAD_BLOCKED_PARENTAL:
|
||||
case Ci.nsIDownloadManager.DOWNLOAD_DIRTY:
|
||||
case Ci.nsIDownloadManager.DOWNLOAD_FINISHED:
|
||||
// For all "completed" states, move them after active downloads
|
||||
this._moveDownloadAfterActive(this._getElementForDownload(aDownload.guid));
|
||||
|
||||
// Fall-through the rest
|
||||
case Ci.nsIDownloadManager.DOWNLOAD_SCANNING:
|
||||
case Ci.nsIDownloadManager.DOWNLOAD_QUEUED:
|
||||
case Ci.nsIDownloadManager.DOWNLOAD_DOWNLOADING:
|
||||
let item = this._getElementForDownload(aDownload.guid);
|
||||
if (item)
|
||||
this._updateDownloadRow(item, aDownload);
|
||||
else
|
||||
this._insertDownloadRow(aDownload);
|
||||
break;
|
||||
}
|
||||
},
|
||||
onStateChange: function(aWebProgress, aRequest, aState, aStatus, aDownload) { },
|
||||
onSecurityChange: function(aWebProgress, aRequest, aState, aDownload) { },
|
||||
|
||||
observe: function (aSubject, aTopic, aData) {
|
||||
switch (aTopic) {
|
||||
case "last-pb-context-exited":
|
||||
this._privateList.innerHTML = "";
|
||||
break;
|
||||
case "download-manager-remove-download-guid": {
|
||||
let guid = aSubject.QueryInterface(Ci.nsISupportsCString).data;
|
||||
this._removeItem(this._getElementForDownload(guid));
|
||||
break;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
_moveDownloadAfterActive: function dl_moveDownloadAfterActive(aItem) {
|
||||
// Move downloads that just reached a "completed" state below any active
|
||||
try {
|
||||
// Iterate down until we find a non-active download
|
||||
let next = aItem.nextElementSibling;
|
||||
while (next && this._inProgress(next.getAttribute("state")))
|
||||
next = next.nextElementSibling;
|
||||
// Move the item
|
||||
aItem.parentNode.insertBefore(aItem, next);
|
||||
} catch (ex) {
|
||||
this.logError("_moveDownloadAfterActive() " + ex);
|
||||
}
|
||||
},
|
||||
|
||||
_inProgress: function dl_inProgress(aState) {
|
||||
return [
|
||||
this._dlmgr.DOWNLOAD_NOTSTARTED,
|
||||
this._dlmgr.DOWNLOAD_QUEUED,
|
||||
this._dlmgr.DOWNLOAD_DOWNLOADING,
|
||||
this._dlmgr.DOWNLOAD_PAUSED,
|
||||
this._dlmgr.DOWNLOAD_SCANNING,
|
||||
].indexOf(parseInt(aState)) != -1;
|
||||
},
|
||||
|
||||
_insertDownloadRow: function dl_insertDownloadRow(aDownload) {
|
||||
let updatedState = this._getState(aDownload.state);
|
||||
let item = this._createItem(downloadTemplate, {
|
||||
guid: aDownload.guid,
|
||||
target: aDownload.displayName,
|
||||
icon: "moz-icon://" + aDownload.displayName + "?size=64",
|
||||
date: DownloadUtils.getReadableDates(new Date())[0],
|
||||
domain: DownloadUtils.getURIHost(aDownload.source.spec)[0],
|
||||
size: this._getDownloadSize(aDownload.size),
|
||||
displayState: this._getStateString(updatedState),
|
||||
state: updatedState
|
||||
});
|
||||
list = aDownload.isPrivate ? this._privateList : this._normalList;
|
||||
list.insertAdjacentHTML("afterbegin", item);
|
||||
},
|
||||
|
||||
_getDownloadSize: function dl_getDownloadSize(aSize) {
|
||||
if (aSize > 0) {
|
||||
let displaySize = DownloadUtils.convertByteUnits(aSize);
|
||||
return displaySize.join(""); // [0] is size, [1] is units
|
||||
}
|
||||
return gStrings.GetStringFromName("downloadState.unknownSize");
|
||||
},
|
||||
|
||||
// Not all states are displayed as-is on mobile, some are translated to a generic state
|
||||
_getState: function dl_getState(aState) {
|
||||
let str;
|
||||
switch (aState) {
|
||||
// Downloading and Scanning states show up as "Downloading"
|
||||
case this._dlmgr.DOWNLOAD_DOWNLOADING:
|
||||
case this._dlmgr.DOWNLOAD_SCANNING:
|
||||
str = this._dlmgr.DOWNLOAD_DOWNLOADING;
|
||||
break;
|
||||
|
||||
// Failed, Dirty and Blocked states show up as "Failed"
|
||||
case this._dlmgr.DOWNLOAD_FAILED:
|
||||
case this._dlmgr.DOWNLOAD_DIRTY:
|
||||
case this._dlmgr.DOWNLOAD_BLOCKED_POLICY:
|
||||
case this._dlmgr.DOWNLOAD_BLOCKED_PARENTAL:
|
||||
str = this._dlmgr.DOWNLOAD_FAILED;
|
||||
break;
|
||||
|
||||
/* QUEUED and NOTSTARTED are not translated as they
|
||||
dont fall under a common state but we still need
|
||||
to display a common "status" on the UI */
|
||||
|
||||
default:
|
||||
str = aState;
|
||||
}
|
||||
return str;
|
||||
},
|
||||
|
||||
// Note: This doesn't cover all states as some of the states are translated in _getState()
|
||||
_getStateString: function dl_getStateString(aState) {
|
||||
let str;
|
||||
switch (aState) {
|
||||
case this._dlmgr.DOWNLOAD_DOWNLOADING:
|
||||
str = "downloadState.downloading";
|
||||
break;
|
||||
case this._dlmgr.DOWNLOAD_CANCELED:
|
||||
str = "downloadState.canceled";
|
||||
break;
|
||||
case this._dlmgr.DOWNLOAD_FAILED:
|
||||
str = "downloadState.failed";
|
||||
break;
|
||||
case this._dlmgr.DOWNLOAD_PAUSED:
|
||||
str = "downloadState.paused";
|
||||
break;
|
||||
|
||||
// Queued and Notstarted show up as "Starting..."
|
||||
case this._dlmgr.DOWNLOAD_QUEUED:
|
||||
case this._dlmgr.DOWNLOAD_NOTSTARTED:
|
||||
str = "downloadState.starting";
|
||||
break;
|
||||
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
return gStrings.GetStringFromName(str);
|
||||
},
|
||||
|
||||
_updateItem: function dl_updateItem(aItem, aValues) {
|
||||
for (let i in aValues) {
|
||||
aItem.querySelector("." + i).textContent = aValues[i];
|
||||
}
|
||||
},
|
||||
|
||||
_initStatement: function dv__initStatement(aIsPrivate) {
|
||||
let dbConn = aIsPrivate ? this._dlmgr.privateDBConnection : this._dlmgr.DBConnection;
|
||||
return dbConn.createStatement(
|
||||
"SELECT guid, name, source, state, startTime, endTime, referrer, " +
|
||||
"currBytes, maxBytes, state IN (?1, ?2, ?3, ?4, ?5) isActive " +
|
||||
"FROM moz_downloads " +
|
||||
"ORDER BY isActive DESC, endTime DESC, startTime DESC");
|
||||
},
|
||||
|
||||
_createItem: function _createItem(aTemplate, aValues) {
|
||||
function htmlEscape(s) {
|
||||
s = s.replace(/&/g, "&");
|
||||
s = s.replace(/>/g, ">");
|
||||
s = s.replace(/</g, "<");
|
||||
s = s.replace(/"/g, """);
|
||||
s = s.replace(/'/g, "'");
|
||||
return s;
|
||||
}
|
||||
|
||||
let t = aTemplate;
|
||||
for (let key in aValues) {
|
||||
if (aValues.hasOwnProperty(key)) {
|
||||
let regEx = new RegExp("{" + key + "}", "g");
|
||||
let value = htmlEscape(aValues[key].toString());
|
||||
t = t.replace(regEx, value);
|
||||
}
|
||||
}
|
||||
return t;
|
||||
},
|
||||
|
||||
_getEntry: function dv__getEntry(aStmt) {
|
||||
try {
|
||||
if (!aStmt.executeStep()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let updatedState = this._getState(aStmt.row.state);
|
||||
// Try to get the attribute values from the statement
|
||||
|
||||
return {
|
||||
guid: aStmt.row.guid,
|
||||
target: aStmt.row.name,
|
||||
icon: "moz-icon://" + aStmt.row.name + "?size=64",
|
||||
date: DownloadUtils.getReadableDates(new Date(aStmt.row.endTime / 1000))[0],
|
||||
domain: DownloadUtils.getURIHost(aStmt.row.source)[0],
|
||||
size: this._getDownloadSize(aStmt.row.maxBytes),
|
||||
displayState: this._getStateString(updatedState),
|
||||
state: updatedState
|
||||
};
|
||||
|
||||
} catch (e) {
|
||||
// Something went wrong when stepping or getting values, so clear and quit
|
||||
this.logError("_getEntry() " + e);
|
||||
aStmt.reset();
|
||||
return null;
|
||||
}
|
||||
},
|
||||
|
||||
_stepAddEntries: function dv__stepAddEntries(aEntries, aList, aNumItems, aCallback) {
|
||||
|
||||
if (aEntries.length == 0){
|
||||
if (aCallback)
|
||||
aCallback();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
let attrs = aEntries.shift();
|
||||
let item = this._createItem(downloadTemplate, attrs);
|
||||
aList.insertAdjacentHTML("beforeend", item);
|
||||
|
||||
// Add another item to the list if we should; otherwise, let the UI update
|
||||
// and continue later
|
||||
if (aNumItems > 1) {
|
||||
this._stepAddEntries(aEntries, aList, aNumItems - 1, aCallback);
|
||||
} else {
|
||||
// Use a shorter delay for earlier downloads to display them faster
|
||||
let delay = Math.min(aList.itemCount * 10, 300);
|
||||
setTimeout(function () {
|
||||
this._stepAddEntries(aEntries, aList, 5, aCallback);
|
||||
}.bind(this), delay);
|
||||
}
|
||||
},
|
||||
|
||||
getDownloads: function dl_getDownloads(aParams) {
|
||||
aParams = aParams || {};
|
||||
let stmt = this._initStatement(aParams.isPrivate);
|
||||
|
||||
stmt.reset();
|
||||
stmt.bindInt32Parameter(0, Ci.nsIDownloadManager.DOWNLOAD_NOTSTARTED);
|
||||
stmt.bindInt32Parameter(1, Ci.nsIDownloadManager.DOWNLOAD_DOWNLOADING);
|
||||
stmt.bindInt32Parameter(2, Ci.nsIDownloadManager.DOWNLOAD_PAUSED);
|
||||
stmt.bindInt32Parameter(3, Ci.nsIDownloadManager.DOWNLOAD_QUEUED);
|
||||
stmt.bindInt32Parameter(4, Ci.nsIDownloadManager.DOWNLOAD_SCANNING);
|
||||
|
||||
let entries = [];
|
||||
while (entry = this._getEntry(stmt)) {
|
||||
entries.push(entry);
|
||||
}
|
||||
|
||||
stmt.finalize();
|
||||
|
||||
return entries;
|
||||
},
|
||||
|
||||
_getElementForDownload: function dl_getElementForDownload(aKey) {
|
||||
return document.body.querySelector("li[downloadGUID='" + aKey + "']");
|
||||
},
|
||||
|
||||
_getDownloadForElement: function dl_getDownloadForElement(aElement, aCallback) {
|
||||
let guid = aElement.getAttribute("downloadGUID");
|
||||
this._dlmgr.getDownloadByGUID(guid, function(status, download) {
|
||||
if (!Components.isSuccessCode(status)) {
|
||||
return;
|
||||
}
|
||||
aCallback(download);
|
||||
});
|
||||
},
|
||||
|
||||
_removeItem: function dl_removeItem(aItem) {
|
||||
// Make sure we have an item to remove
|
||||
if (!aItem)
|
||||
return;
|
||||
|
||||
aItem.parentNode.removeChild(aItem);
|
||||
},
|
||||
|
||||
openDownload: function dl_openDownload(aItem) {
|
||||
this._getDownloadForElement(aItem, function(aDownload) {
|
||||
if (aDownload.state !== Ci.nsIDownloadManager.DOWNLOAD_FINISHED) {
|
||||
// Do not open unfinished downloads.
|
||||
return;
|
||||
}
|
||||
try {
|
||||
let f = aDownload.targetFile;
|
||||
if (f) f.launch();
|
||||
} catch (ex) {
|
||||
this.logError("openDownload() " + ex, aDownload);
|
||||
}
|
||||
}.bind(this));
|
||||
},
|
||||
|
||||
removeDownload: function dl_removeDownload(aItem) {
|
||||
this._getDownloadForElement(aItem, function(aDownload) {
|
||||
if (aDownload.targetFile) {
|
||||
OS.File.remove(aDownload.targetFile.path).then(null, function onError(reason) {
|
||||
if (!(reason instanceof OS.File.Error && reason.becauseNoSuchFile)) {
|
||||
this.logError("removeDownload() " + reason, aDownload);
|
||||
}
|
||||
}.bind(this));
|
||||
}
|
||||
|
||||
aDownload.remove();
|
||||
}.bind(this));
|
||||
},
|
||||
|
||||
removeAll: function dl_removeAll() {
|
||||
let title = gStrings.GetStringFromName("downloadAction.deleteAll");
|
||||
let messageForm = gStrings.GetStringFromName("downloadMessage.deleteAll");
|
||||
let elements = document.body.querySelectorAll("li[state='" + this._dlmgr.DOWNLOAD_FINISHED + "']," +
|
||||
"li[state='" + this._dlmgr.DOWNLOAD_CANCELED + "']," +
|
||||
"li[state='" + this._dlmgr.DOWNLOAD_FAILED + "']");
|
||||
let message = PluralForm.get(elements.length, messageForm)
|
||||
.replace("#1", elements.length);
|
||||
let flags = Services.prompt.BUTTON_POS_0 * Services.prompt.BUTTON_TITLE_OK +
|
||||
Services.prompt.BUTTON_POS_1 * Services.prompt.BUTTON_TITLE_CANCEL;
|
||||
let choice = Services.prompt.confirmEx(null, title, message, flags,
|
||||
null, null, null, null, {});
|
||||
if (choice == 0) {
|
||||
for (let i = 0; i < elements.length; i++) {
|
||||
this.removeDownload(elements[i]);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
pauseDownload: function dl_pauseDownload(aItem) {
|
||||
this._getDownloadForElement(aItem, function(aDownload) {
|
||||
try {
|
||||
aDownload.pause();
|
||||
this._updateDownloadRow(aItem, aDownload);
|
||||
} catch (ex) {
|
||||
this.logError("Error: pauseDownload() " + ex, aDownload);
|
||||
}
|
||||
}.bind(this));
|
||||
},
|
||||
|
||||
resumeDownload: function dl_resumeDownload(aItem) {
|
||||
this._getDownloadForElement(aItem, function(aDownload) {
|
||||
try {
|
||||
aDownload.resume();
|
||||
this._updateDownloadRow(aItem, aDownload);
|
||||
} catch (ex) {
|
||||
this.logError("resumeDownload() " + ex, aDownload);
|
||||
}
|
||||
}.bind(this));
|
||||
},
|
||||
|
||||
retryDownload: function dl_retryDownload(aItem) {
|
||||
this._getDownloadForElement(aItem, function(aDownload) {
|
||||
try {
|
||||
this._removeItem(aItem);
|
||||
aDownload.retry();
|
||||
} catch (ex) {
|
||||
this.logError("retryDownload() " + ex, aDownload);
|
||||
}
|
||||
}.bind(this));
|
||||
},
|
||||
|
||||
cancelDownload: function dl_cancelDownload(aItem) {
|
||||
this._getDownloadForElement(aItem, function(aDownload) {
|
||||
OS.File.remove(aDownload.targetFile.path).then(null, function onError(reason) {
|
||||
if (!(reason instanceof OS.File.Error && reason.becauseNoSuchFile)) {
|
||||
this.logError("cancelDownload() " + reason, aDownload);
|
||||
}
|
||||
}.bind(this));
|
||||
|
||||
aDownload.cancel();
|
||||
|
||||
this._updateDownloadRow(aItem, aDownload);
|
||||
}.bind(this));
|
||||
},
|
||||
|
||||
_updateDownloadRow: function dl_updateDownloadRow(aItem, aDownload) {
|
||||
try {
|
||||
let updatedState = this._getState(aDownload.state);
|
||||
aItem.setAttribute("state", updatedState);
|
||||
this._updateItem(aItem, {
|
||||
size: this._getDownloadSize(aDownload.size),
|
||||
displayState: this._getStateString(updatedState),
|
||||
date: DownloadUtils.getReadableDates(new Date())[0]
|
||||
});
|
||||
} catch (ex) {
|
||||
this.logError("_updateDownloadRow() " + ex, aDownload);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* In case a specific downloadId was passed while opening, scrolls the list to
|
||||
* the given elemenet
|
||||
*/
|
||||
|
||||
_scrollToSelectedDownload : function dl_scrollToSelected() {
|
||||
let spec = document.location.href;
|
||||
let pos = spec.indexOf("?");
|
||||
let query = "";
|
||||
if (pos >= 0)
|
||||
query = spec.substring(pos + 1);
|
||||
|
||||
// Just assume the query is "id=<id>"
|
||||
let id = query.substring(3);
|
||||
if (!id) {
|
||||
return;
|
||||
}
|
||||
downloadElement = this._getElementForDownload(id);
|
||||
if (!downloadElement) {
|
||||
return;
|
||||
}
|
||||
|
||||
downloadElement.scrollIntoView();
|
||||
},
|
||||
|
||||
/**
|
||||
* Logs the error to the console.
|
||||
*
|
||||
* @param aMessage error message to log
|
||||
* @param aDownload (optional) if given, and if the download is private, the
|
||||
* log message is suppressed
|
||||
*/
|
||||
logError: function dl_logError(aMessage, aDownload) {
|
||||
if (!aDownload || !aDownload.isPrivate) {
|
||||
console.log("Error: " + aMessage);
|
||||
}
|
||||
},
|
||||
|
||||
QueryInterface: function (aIID) {
|
||||
if (!aIID.equals(Ci.nsIDownloadProgressListener) &&
|
||||
!aIID.equals(Ci.nsISupports))
|
||||
throw Components.results.NS_ERROR_NO_INTERFACE;
|
||||
return this;
|
||||
}
|
||||
};
|
||||
|
||||
function DownloadListView(type, listElementId) {
|
||||
this.listElement = document.getElementById(listElementId);
|
||||
contextMenu.addContextMenuEventListener(this.listElement);
|
||||
|
||||
this.items = new Map();
|
||||
|
||||
Downloads.getList(type)
|
||||
.then(list => list.addView(this))
|
||||
.then(null, Cu.reportError);
|
||||
|
||||
window.addEventListener("unload", event => {
|
||||
Downloads.getList(type)
|
||||
.then(list => list.removeView(this))
|
||||
.then(null, Cu.reportError);
|
||||
});
|
||||
}
|
||||
|
||||
DownloadListView.prototype = {
|
||||
get finished() {
|
||||
let finished = [];
|
||||
for (let download of this.items.keys()) {
|
||||
if (download.stopped && (!download.hasPartialData || download.error)) {
|
||||
finished.push(download);
|
||||
}
|
||||
}
|
||||
document.addEventListener("DOMContentLoaded", Downloads.init.bind(Downloads), true);
|
||||
window.addEventListener("unload", Downloads.uninit.bind(Downloads), false);
|
||||
|
||||
return finished;
|
||||
},
|
||||
|
||||
insertOrMoveItem: function (item) {
|
||||
var compare = (a, b) => {
|
||||
// active downloads always before stopped downloads
|
||||
if (a.stopped != b.stopped) {
|
||||
return b.stopped ? -1 : 1
|
||||
}
|
||||
// most recent downloads first
|
||||
return b.startTime - a.startTime;
|
||||
};
|
||||
|
||||
let insertLocation = this.listElement.firstChild;
|
||||
while (insertLocation && compare(item.download, insertLocation.download) > 0) {
|
||||
insertLocation = insertLocation.nextElementSibling;
|
||||
}
|
||||
this.listElement.insertBefore(item.element, insertLocation);
|
||||
},
|
||||
|
||||
onDownloadAdded: function (download) {
|
||||
let item = new DownloadItem(download);
|
||||
this.items.set(download, item);
|
||||
this.insertOrMoveItem(item);
|
||||
},
|
||||
|
||||
onDownloadChanged: function (download) {
|
||||
let item = this.items.get(download);
|
||||
if (!item) {
|
||||
Cu.reportError("No DownloadItem found for download");
|
||||
return;
|
||||
}
|
||||
|
||||
if (item.stateChanged) {
|
||||
this.insertOrMoveItem(item);
|
||||
}
|
||||
|
||||
item.onDownloadChanged();
|
||||
},
|
||||
|
||||
onDownloadRemoved: function (download) {
|
||||
let item = this.items.get(download);
|
||||
if (!item) {
|
||||
Cu.reportError("No DownloadItem found for download");
|
||||
return;
|
||||
}
|
||||
|
||||
this.items.delete(download);
|
||||
this.listElement.removeChild(item.element);
|
||||
}
|
||||
};
|
||||
|
||||
let downloadLists = {
|
||||
init: function () {
|
||||
this.publicDownloads = new DownloadListView(Downloads.PUBLIC, "public-downloads-list");
|
||||
this.privateDownloads = new DownloadListView(Downloads.PRIVATE, "private-downloads-list");
|
||||
},
|
||||
|
||||
get finished() {
|
||||
return this.publicDownloads.finished.concat(this.privateDownloads.finished);
|
||||
},
|
||||
|
||||
removeFinished: function () {
|
||||
let finished = this.finished;
|
||||
if (finished.length == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
let title = strings.GetStringFromName("downloadAction.deleteAll");
|
||||
let messageForm = strings.GetStringFromName("downloadMessage.deleteAll");
|
||||
let message = PluralForm.get(finished.length, messageForm).replace("#1", finished.length);
|
||||
|
||||
if (Services.prompt.confirm(null, title, message)) {
|
||||
Downloads.getList(Downloads.ALL)
|
||||
.then(list => {
|
||||
for (let download of finished) {
|
||||
list.remove(download).then(null, Cu.reportError);
|
||||
deleteDownload(download);
|
||||
}
|
||||
}, Cu.reportError);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
function DownloadItem(download) {
|
||||
this._download = download;
|
||||
this._updateFromDownload();
|
||||
|
||||
this._domain = DownloadUtils.getURIHost(download.source.url)[0];
|
||||
this._fileName = this._htmlEscape(OS.Path.basename(download.target.path));
|
||||
this._iconUrl = "moz-icon://" + this._fileName + "?size=64";
|
||||
this._startDate = this._htmlEscape(DownloadUtils.getReadableDates(download.startTime)[0]);
|
||||
|
||||
this._element = this.createElement();
|
||||
}
|
||||
|
||||
const kDownloadStatePropertyNames = [
|
||||
"stopped",
|
||||
"succeeded",
|
||||
"canceled",
|
||||
"error",
|
||||
"startTime"
|
||||
];
|
||||
|
||||
DownloadItem.prototype = {
|
||||
_htmlEscape : function (s) {
|
||||
s = s.replace(/&/g, "&");
|
||||
s = s.replace(/>/g, ">");
|
||||
s = s.replace(/</g, "<");
|
||||
s = s.replace(/"/g, """);
|
||||
s = s.replace(/'/g, "'");
|
||||
return s;
|
||||
},
|
||||
|
||||
_updateFromDownload: function () {
|
||||
this._state = {};
|
||||
kDownloadStatePropertyNames.forEach(
|
||||
name => this._state[name] = this._download[name],
|
||||
this);
|
||||
},
|
||||
|
||||
get stateChanged() {
|
||||
return kDownloadStatePropertyNames.some(
|
||||
name => this._state[name] != this._download[name],
|
||||
this);
|
||||
},
|
||||
|
||||
get download() this._download,
|
||||
get element() this._element,
|
||||
|
||||
createElement: function() {
|
||||
let template = document.getElementById("download-item");
|
||||
// TODO: use this once <template> is working
|
||||
// let element = document.importNode(template.content, true);
|
||||
|
||||
// simulate a <template> node...
|
||||
let element = template.cloneNode(true);
|
||||
element.removeAttribute("id");
|
||||
element.removeAttribute("style");
|
||||
|
||||
// launch the download if clicked
|
||||
element.addEventListener("click", this.onClick.bind(this));
|
||||
|
||||
// set download as an expando property for the context menu
|
||||
element.download = this.download;
|
||||
|
||||
// fill in template placeholders
|
||||
this.updateElement(element);
|
||||
|
||||
return element;
|
||||
},
|
||||
|
||||
updateElement: function (element) {
|
||||
element.querySelector(".date").textContent = this.startDate;
|
||||
element.querySelector(".domain").textContent = this.domain;
|
||||
element.querySelector(".icon").src = this.iconUrl;
|
||||
element.querySelector(".size").textContent = this.size;
|
||||
element.querySelector(".state").textContent = this.stateDescription;
|
||||
element.querySelector(".title").setAttribute("value", this.fileName);
|
||||
},
|
||||
|
||||
onClick: function (event) {
|
||||
if (this.download.succeeded) {
|
||||
this.download.launch().then(null, Cu.reportError);
|
||||
}
|
||||
},
|
||||
|
||||
onDownloadChanged: function () {
|
||||
this._updateFromDownload();
|
||||
this.updateElement(this.element);
|
||||
},
|
||||
|
||||
// template properties below
|
||||
get domain() this._domain,
|
||||
get fileName() this._fileName,
|
||||
get id() this._id,
|
||||
get iconUrl() this._iconUrl,
|
||||
|
||||
get size() {
|
||||
if (this.download.hasProgress) {
|
||||
return DownloadUtils.convertByteUnits(this.download.totalBytes).join("");
|
||||
}
|
||||
return strings.GetStringFromName("downloadState.unknownSize");
|
||||
},
|
||||
|
||||
get startDate() {
|
||||
return this._startDate;
|
||||
},
|
||||
|
||||
get stateDescription() {
|
||||
let name;
|
||||
if (this.download.error) {
|
||||
name = "downloadState.failed";
|
||||
} else if (this.download.canceled) {
|
||||
if (this.download.hasPartialData) {
|
||||
name = "downloadState.paused";
|
||||
} else {
|
||||
name = "downloadState.canceled";
|
||||
}
|
||||
} else if (!this.download.stopped) {
|
||||
if (this.download.currentBytes > 0) {
|
||||
name = "downloadState.downloading";
|
||||
} else {
|
||||
name = "downloadState.starting";
|
||||
}
|
||||
}
|
||||
|
||||
if (name) {
|
||||
return strings.GetStringFromName(name);
|
||||
}
|
||||
return "";
|
||||
}
|
||||
};
|
||||
|
||||
window.addEventListener("DOMContentLoaded", event => {
|
||||
contextMenu.init();
|
||||
downloadLists.init()
|
||||
});
|
||||
|
@ -35,27 +35,11 @@
|
||||
<menuitem id="contextmenu-removeall" label="&aboutDownloads.removeAll;"></menuitem>
|
||||
</menu>
|
||||
|
||||
<!--template id="download-item"-->
|
||||
<li id="download-item" class="list-item" role="button" contextmenu="downloadmenu" style="display: none">
|
||||
<img class="icon" src=""/>
|
||||
<div class="details">
|
||||
<div class="row">
|
||||
<!-- This is a hack so that we can crop this label in its center -->
|
||||
<xul:label class="title" crop="center" value=""/>
|
||||
<div class="date"></div>
|
||||
</div>
|
||||
<div class="size"></div>
|
||||
<div class="domain"></div>
|
||||
<div class="state"></div>
|
||||
</div>
|
||||
</li>
|
||||
<!--/template-->
|
||||
|
||||
<div class="header">
|
||||
<div>&aboutDownloads.header;</div>
|
||||
</div>
|
||||
<ul id="private-downloads-list" class="list"></ul>
|
||||
<ul id="public-downloads-list" class="list"></ul>
|
||||
<ul id="normal-downloads-list" class="list"></ul>
|
||||
<span id="no-downloads-indicator">&aboutDownloads.empty;</span>
|
||||
<script type="application/javascript;version=1.8" src="chrome://browser/content/aboutDownloads.js"/>
|
||||
</body>
|
||||
|
@ -13,7 +13,6 @@ let Cr = Components.results;
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
Cu.import("resource://gre/modules/AddonManager.jsm");
|
||||
Cu.import("resource://gre/modules/DownloadNotifications.jsm");
|
||||
Cu.import("resource://gre/modules/FileUtils.jsm");
|
||||
Cu.import("resource://gre/modules/JNI.jsm");
|
||||
Cu.import('resource://gre/modules/Payment.jsm');
|
||||
@ -370,7 +369,7 @@ var BrowserApp = {
|
||||
|
||||
NativeWindow.init();
|
||||
LightWeightThemeWebInstaller.init();
|
||||
DownloadNotifications.init();
|
||||
Downloads.init();
|
||||
FormAssistant.init();
|
||||
IndexedDB.init();
|
||||
HealthReportStatusListener.init();
|
||||
@ -658,7 +657,7 @@ var BrowserApp = {
|
||||
function(aTarget) {
|
||||
aTarget.muted = true;
|
||||
});
|
||||
|
||||
|
||||
NativeWindow.contextmenus.add(Strings.browser.GetStringFromName("contextmenu.unmute"),
|
||||
NativeWindow.contextmenus.mediaContext("media-muted"),
|
||||
function(aTarget) {
|
||||
@ -751,7 +750,6 @@ var BrowserApp = {
|
||||
|
||||
shutdown: function shutdown() {
|
||||
NativeWindow.uninit();
|
||||
DownloadNotifications.uninit();
|
||||
LightWeightThemeWebInstaller.uninit();
|
||||
FormAssistant.uninit();
|
||||
IndexedDB.uninit();
|
||||
@ -1825,7 +1823,7 @@ var NativeWindow = {
|
||||
return;
|
||||
|
||||
sendMessageToJava({
|
||||
type: "Menu:Update",
|
||||
type: "Menu:Update",
|
||||
id: aId,
|
||||
options: aOptions
|
||||
});
|
||||
@ -1851,7 +1849,7 @@ var NativeWindow = {
|
||||
* automatically dismiss before this time.
|
||||
* checkbox: A string to appear next to a checkbox under the notification
|
||||
* message. The button callback functions will be called with
|
||||
* the checked state as an argument.
|
||||
* the checked state as an argument.
|
||||
*/
|
||||
show: function(aMessage, aValue, aButtons, aTabID, aOptions) {
|
||||
if (aButtons == null) {
|
||||
@ -2253,7 +2251,7 @@ var NativeWindow = {
|
||||
mode: SelectionHandler.SELECT_AT_POINT,
|
||||
x: x,
|
||||
y: y
|
||||
})) {
|
||||
})) {
|
||||
SelectionHandler.attachCaret(target);
|
||||
}
|
||||
}
|
||||
@ -3147,7 +3145,7 @@ Tab.prototype = {
|
||||
viewportWidth - 15);
|
||||
},
|
||||
|
||||
/**
|
||||
/**
|
||||
* Reloads the tab with the desktop mode setting.
|
||||
*/
|
||||
reloadWithMode: function (aDesktopMode) {
|
||||
@ -3782,7 +3780,7 @@ Tab.prototype = {
|
||||
|
||||
if (sizes == "any") {
|
||||
// Since Java expects an integer, use -1 to represent icons with sizes="any"
|
||||
maxSize = -1;
|
||||
maxSize = -1;
|
||||
} else {
|
||||
let tokens = sizes.split(" ");
|
||||
tokens.forEach(function(token) {
|
||||
@ -3797,9 +3795,6 @@ Tab.prototype = {
|
||||
type: "Link:Favicon",
|
||||
tabID: this.id,
|
||||
href: resolveGeckoURI(target.href),
|
||||
charset: target.ownerDocument.characterSet,
|
||||
title: target.title,
|
||||
rel: list.join(" "),
|
||||
size: maxSize
|
||||
};
|
||||
sendMessageToJava(json);
|
||||
@ -6666,7 +6661,7 @@ var IdentityHandler = {
|
||||
.QueryInterface(Components.interfaces.nsISSLStatusProvider)
|
||||
.SSLStatus;
|
||||
|
||||
// Don't pass in the actual location object, since it can cause us to
|
||||
// Don't pass in the actual location object, since it can cause us to
|
||||
// hold on to the window object too long. Just pass in the fields we
|
||||
// care about. (bug 424829)
|
||||
let locationObj = {};
|
||||
@ -6716,7 +6711,7 @@ var IdentityHandler = {
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
// Otherwise, we don't know the cert owner
|
||||
result.owner = Strings.browser.GetStringFromName("identity.ownerUnknown3");
|
||||
|
||||
@ -7328,7 +7323,7 @@ var WebappsUI = {
|
||||
favicon.src = WebappsUI.DEFAULT_ICON;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
favicon.src = aIconURL;
|
||||
},
|
||||
|
||||
@ -8528,21 +8523,3 @@ HTMLContextMenuItem.prototype = Object.create(ContextMenuItem.prototype, {
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* CID of Downloads.jsm's implementation of nsITransfer.
|
||||
*/
|
||||
const kTransferCid = Components.ID("{1b4c85df-cbdd-4bb6-b04e-613caece083c}");
|
||||
|
||||
/**
|
||||
* Contract ID of the service implementing nsITransfer.
|
||||
*/
|
||||
const kTransferContractId = "@mozilla.org/transfer;1";
|
||||
|
||||
// Override Toolkit's nsITransfer implementation with the one from the
|
||||
// JavaScript API for downloads. This will eventually be removed when
|
||||
// nsIDownloadManager will not be available anymore (bug 851471). The
|
||||
// old code in this module will be removed in bug 899110.
|
||||
Components.manager.QueryInterface(Ci.nsIComponentRegistrar)
|
||||
.registerFactory(kTransferCid, "",
|
||||
kTransferContractId, null);
|
||||
|
@ -11,6 +11,7 @@
|
||||
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
|
||||
|
||||
<script type="application/javascript" src="chrome://browser/content/browser.js"/>
|
||||
<script type="application/javascript" src="chrome://browser/content/downloads.js"/>
|
||||
|
||||
<deck id="browsers" flex="1"/>
|
||||
|
||||
|
298
mobile/android/chrome/content/downloads.js
Normal file
298
mobile/android/chrome/content/downloads.js
Normal file
@ -0,0 +1,298 @@
|
||||
// -*- Mode: js2; tab-width: 2; indent-tabs-mode: nil; js2-basic-offset: 2; js2-skip-preprocessor-directives: t; -*-
|
||||
/* 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";
|
||||
|
||||
let Cu = Components.utils;
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
|
||||
function dump(a) {
|
||||
Cc["@mozilla.org/consoleservice;1"].getService(Ci.nsIConsoleService).logStringMessage(a);
|
||||
}
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "Notifications",
|
||||
"resource://gre/modules/Notifications.jsm");
|
||||
|
||||
const URI_GENERIC_ICON_DOWNLOAD = "drawable://alert_download";
|
||||
const URI_PAUSE_ICON = "drawable://pause";
|
||||
const URI_CANCEL_ICON = "drawable://close";
|
||||
const URI_RESUME_ICON = "drawable://play";
|
||||
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "OS", "resource://gre/modules/osfile.jsm");
|
||||
|
||||
var Downloads = {
|
||||
_initialized: false,
|
||||
_dlmgr: null,
|
||||
_progressAlert: null,
|
||||
_privateDownloads: [],
|
||||
_showingPrompt: false,
|
||||
_downloadsIdMap: {},
|
||||
|
||||
_getLocalFile: function dl__getLocalFile(aFileURI) {
|
||||
// if this is a URL, get the file from that
|
||||
// XXX it's possible that using a null char-set here is bad
|
||||
const fileUrl = Services.io.newURI(aFileURI, null, null).QueryInterface(Ci.nsIFileURL);
|
||||
return fileUrl.file.clone().QueryInterface(Ci.nsILocalFile);
|
||||
},
|
||||
|
||||
init: function dl_init() {
|
||||
if (this._initialized)
|
||||
return;
|
||||
this._initialized = true;
|
||||
|
||||
// Monitor downloads and display alerts
|
||||
this._dlmgr = Cc["@mozilla.org/download-manager;1"].getService(Ci.nsIDownloadManager);
|
||||
this._progressAlert = new AlertDownloadProgressListener();
|
||||
this._dlmgr.addPrivacyAwareListener(this._progressAlert);
|
||||
Services.obs.addObserver(this, "last-pb-context-exited", true);
|
||||
},
|
||||
|
||||
openDownload: function dl_openDownload(aDownload) {
|
||||
let fileUri = aDownload.target.spec;
|
||||
let guid = aDownload.guid;
|
||||
let f = this._getLocalFile(fileUri);
|
||||
try {
|
||||
f.launch();
|
||||
} catch (ex) {
|
||||
// in case we are not able to open the file (i.e. there is no app able to handle it)
|
||||
// we just open the browser tab showing it
|
||||
BrowserApp.addTab("about:downloads?id=" + guid);
|
||||
}
|
||||
},
|
||||
|
||||
cancelDownload: function dl_cancelDownload(aDownload) {
|
||||
aDownload.cancel();
|
||||
let fileURI = aDownload.target.spec;
|
||||
let f = this._getLocalFile(fileURI);
|
||||
|
||||
OS.File.remove(f.path);
|
||||
},
|
||||
|
||||
showCancelConfirmPrompt: function dl_showCancelConfirmPrompt(aDownload) {
|
||||
if (this._showingPrompt)
|
||||
return;
|
||||
this._showingPrompt = true;
|
||||
// Open a prompt that offers a choice to cancel the download
|
||||
let title = Strings.browser.GetStringFromName("downloadCancelPromptTitle");
|
||||
let message = Strings.browser.GetStringFromName("downloadCancelPromptMessage");
|
||||
let flags = Services.prompt.BUTTON_POS_0 * Services.prompt.BUTTON_TITLE_YES +
|
||||
Services.prompt.BUTTON_POS_1 * Services.prompt.BUTTON_TITLE_NO;
|
||||
let choice = Services.prompt.confirmEx(null, title, message, flags,
|
||||
null, null, null, null, {});
|
||||
if (choice == 0)
|
||||
this.cancelDownload(aDownload);
|
||||
this._showingPrompt = false;
|
||||
},
|
||||
|
||||
handleClickEvent: function dl_handleClickEvent(aDownload) {
|
||||
// Only open the downloaded file if the download is complete
|
||||
if (aDownload.state == Ci.nsIDownloadManager.DOWNLOAD_FINISHED)
|
||||
this.openDownload(aDownload);
|
||||
else if (aDownload.state == Ci.nsIDownloadManager.DOWNLOAD_DOWNLOADING ||
|
||||
aDownload.state == Ci.nsIDownloadManager.DOWNLOAD_PAUSED)
|
||||
this.showCancelConfirmPrompt(aDownload);
|
||||
},
|
||||
|
||||
clickCallback: function dl_clickCallback(aDownloadId) {
|
||||
this._dlmgr.getDownloadByGUID(aDownloadId, (function(status, download) {
|
||||
if (Components.isSuccessCode(status))
|
||||
this.handleClickEvent(download);
|
||||
}).bind(this));
|
||||
},
|
||||
|
||||
pauseClickCallback: function dl_buttonPauseCallback(aDownloadId) {
|
||||
this._dlmgr.getDownloadByGUID(aDownloadId, (function(status, download) {
|
||||
if (Components.isSuccessCode(status))
|
||||
download.pause();
|
||||
}).bind(this));
|
||||
},
|
||||
|
||||
resumeClickCallback: function dl_buttonPauseCallback(aDownloadId) {
|
||||
this._dlmgr.getDownloadByGUID(aDownloadId, (function(status, download) {
|
||||
if (Components.isSuccessCode(status))
|
||||
download.resume();
|
||||
}).bind(this));
|
||||
},
|
||||
|
||||
cancelClickCallback: function dl_buttonPauseCallback(aDownloadId) {
|
||||
this._dlmgr.getDownloadByGUID(aDownloadId, (function(status, download) {
|
||||
if (Components.isSuccessCode(status))
|
||||
this.cancelDownload(download);
|
||||
}).bind(this));
|
||||
},
|
||||
|
||||
notificationCanceledCallback: function dl_notifCancelCallback(aId, aDownloadId) {
|
||||
let notificationId = this._downloadsIdMap[aDownloadId];
|
||||
if (notificationId && notificationId == aId)
|
||||
delete this._downloadsIdMap[aDownloadId];
|
||||
},
|
||||
|
||||
createNotification: function dl_createNotif(aDownload, aOptions) {
|
||||
let notificationId = Notifications.create(aOptions);
|
||||
this._downloadsIdMap[aDownload.guid] = notificationId;
|
||||
},
|
||||
|
||||
updateNotification: function dl_updateNotif(aDownload, aOptions) {
|
||||
let notificationId = this._downloadsIdMap[aDownload.guid];
|
||||
if (notificationId)
|
||||
Notifications.update(notificationId, aOptions);
|
||||
},
|
||||
|
||||
cancelNotification: function dl_cleanNotif(aDownload) {
|
||||
Notifications.cancel(this._downloadsIdMap[aDownload.guid]);
|
||||
delete this._downloadsIdMap[aDownload.guid];
|
||||
},
|
||||
|
||||
// observer for last-pb-context-exited
|
||||
observe: function dl_observe(aSubject, aTopic, aData) {
|
||||
let download;
|
||||
while ((download = this._privateDownloads.pop())) {
|
||||
try {
|
||||
let notificationId = aDownload.guid;
|
||||
Notifications.clear(notificationId);
|
||||
Downloads.removeNotification(download);
|
||||
} catch (e) {
|
||||
dump("Error removing private download: " + e);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
QueryInterface: function (aIID) {
|
||||
if (!aIID.equals(Ci.nsISupports) &&
|
||||
!aIID.equals(Ci.nsIObserver) &&
|
||||
!aIID.equals(Ci.nsISupportsWeakReference))
|
||||
throw Components.results.NS_ERROR_NO_INTERFACE;
|
||||
return this;
|
||||
}
|
||||
};
|
||||
|
||||
const PAUSE_BUTTON = {
|
||||
buttonId: "pause",
|
||||
title : Strings.browser.GetStringFromName("alertDownloadsPause"),
|
||||
icon : URI_PAUSE_ICON,
|
||||
onClicked: function (aId, aCookie) {
|
||||
Downloads.pauseClickCallback(aCookie);
|
||||
}
|
||||
};
|
||||
|
||||
const CANCEL_BUTTON = {
|
||||
buttonId: "cancel",
|
||||
title : Strings.browser.GetStringFromName("alertDownloadsCancel"),
|
||||
icon : URI_CANCEL_ICON,
|
||||
onClicked: function (aId, aCookie) {
|
||||
Downloads.cancelClickCallback(aCookie);
|
||||
}
|
||||
};
|
||||
|
||||
const RESUME_BUTTON = {
|
||||
buttonId: "resume",
|
||||
title : Strings.browser.GetStringFromName("alertDownloadsResume"),
|
||||
icon: URI_RESUME_ICON,
|
||||
onClicked: function (aId, aCookie) {
|
||||
Downloads.resumeClickCallback(aCookie);
|
||||
}
|
||||
};
|
||||
|
||||
function DownloadNotifOptions (aDownload, aTitle, aMessage) {
|
||||
this.icon = URI_GENERIC_ICON_DOWNLOAD;
|
||||
this.onCancel = function (aId, aCookie) {
|
||||
Downloads.notificationCanceledCallback(aId, aCookie);
|
||||
}
|
||||
this.onClick = function (aId, aCookie) {
|
||||
Downloads.clickCallback(aCookie);
|
||||
}
|
||||
this.title = aTitle;
|
||||
this.message = aMessage;
|
||||
this.buttons = null;
|
||||
this.cookie = aDownload.guid;
|
||||
this.persistent = true;
|
||||
}
|
||||
|
||||
function DownloadProgressNotifOptions (aDownload, aButtons) {
|
||||
DownloadNotifOptions.apply(this, [aDownload, aDownload.displayName, aDownload.percentComplete + "%"]);
|
||||
this.ongoing = true;
|
||||
this.progress = aDownload.percentComplete;
|
||||
this.buttons = aButtons;
|
||||
}
|
||||
|
||||
// AlertDownloadProgressListener is used to display progress in the alert notifications.
|
||||
function AlertDownloadProgressListener() { }
|
||||
|
||||
AlertDownloadProgressListener.prototype = {
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
//// nsIDownloadProgressListener
|
||||
onProgressChange: function(aWebProgress, aRequest, aCurSelfProgress, aMaxSelfProgress, aCurTotalProgress, aMaxTotalProgress, aDownload) {
|
||||
let strings = Strings.browser;
|
||||
let availableSpace = -1;
|
||||
try {
|
||||
// diskSpaceAvailable is not implemented on all systems
|
||||
let availableSpace = aDownload.targetFile.diskSpaceAvailable;
|
||||
} catch(ex) { }
|
||||
let contentLength = aDownload.size;
|
||||
if (availableSpace > 0 && contentLength > 0 && contentLength > availableSpace) {
|
||||
Downloads.updateNotification(aDownload, new DownloadNotifOptions(aDownload,
|
||||
strings.GetStringFromName("alertDownloadsNoSpace"),
|
||||
strings.GetStringFromName("alertDownloadsSize")));
|
||||
aDownload.cancel();
|
||||
}
|
||||
|
||||
if (aDownload.percentComplete == -1) {
|
||||
// Undetermined progress is not supported yet
|
||||
return;
|
||||
}
|
||||
|
||||
Downloads.updateNotification(aDownload, new DownloadProgressNotifOptions(aDownload, [PAUSE_BUTTON, CANCEL_BUTTON]));
|
||||
},
|
||||
|
||||
onDownloadStateChange: function(aState, aDownload) {
|
||||
let state = aDownload.state;
|
||||
switch (state) {
|
||||
case Ci.nsIDownloadManager.DOWNLOAD_QUEUED: {
|
||||
NativeWindow.toast.show(Strings.browser.GetStringFromName("alertDownloadsToast"), "long");
|
||||
Downloads.createNotification(aDownload, new DownloadNotifOptions(aDownload,
|
||||
Strings.browser.GetStringFromName("alertDownloadsStart2"),
|
||||
aDownload.displayName));
|
||||
break;
|
||||
}
|
||||
case Ci.nsIDownloadManager.DOWNLOAD_PAUSED: {
|
||||
Downloads.updateNotification(aDownload, new DownloadProgressNotifOptions(aDownload, [RESUME_BUTTON, CANCEL_BUTTON]));
|
||||
break;
|
||||
}
|
||||
case Ci.nsIDownloadManager.DOWNLOAD_FAILED:
|
||||
case Ci.nsIDownloadManager.DOWNLOAD_CANCELED:
|
||||
case Ci.nsIDownloadManager.DOWNLOAD_BLOCKED_PARENTAL:
|
||||
case Ci.nsIDownloadManager.DOWNLOAD_DIRTY:
|
||||
case Ci.nsIDownloadManager.DOWNLOAD_FINISHED: {
|
||||
Downloads.cancelNotification(aDownload);
|
||||
if (aDownload.isPrivate) {
|
||||
let index = Downloads._privateDownloads.indexOf(aDownload);
|
||||
if (index != -1) {
|
||||
Downloads._privateDownloads.splice(index, 1);
|
||||
}
|
||||
}
|
||||
|
||||
if (state == Ci.nsIDownloadManager.DOWNLOAD_FINISHED) {
|
||||
Downloads.createNotification(aDownload, new DownloadNotifOptions(aDownload,
|
||||
Strings.browser.GetStringFromName("alertDownloadsDone2"),
|
||||
aDownload.displayName));
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
onStateChange: function(aWebProgress, aRequest, aState, aStatus, aDownload) { },
|
||||
onSecurityChange: function(aWebProgress, aRequest, aState, aDownload) { },
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
//// nsISupports
|
||||
QueryInterface: function (aIID) {
|
||||
if (!aIID.equals(Ci.nsIDownloadProgressListener) &&
|
||||
!aIID.equals(Ci.nsISupports))
|
||||
throw Components.results.NS_ERROR_NO_INTERFACE;
|
||||
return this;
|
||||
}
|
||||
};
|
@ -34,6 +34,7 @@ chrome.jar:
|
||||
* content/browser.js (content/browser.js)
|
||||
content/bindings/checkbox.xml (content/bindings/checkbox.xml)
|
||||
content/bindings/settings.xml (content/bindings/settings.xml)
|
||||
content/downloads.js (content/downloads.js)
|
||||
content/netError.xhtml (content/netError.xhtml)
|
||||
content/SelectHelper.js (content/SelectHelper.js)
|
||||
content/SelectionHandler.js (content/SelectionHandler.js)
|
||||
|
@ -442,9 +442,6 @@
|
||||
@BINPATH@/components/TestInterfaceJS.manifest
|
||||
#endif
|
||||
|
||||
@BINPATH@/components/Downloads.manifest
|
||||
@BINPATH@/components/DownloadLegacy.js
|
||||
|
||||
; Modules
|
||||
@BINPATH@/modules/*
|
||||
|
||||
|
@ -1,232 +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.EXPORTED_SYMBOLS = ["DownloadNotifications"];
|
||||
|
||||
const Cu = Components.utils;
|
||||
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "Downloads", "resource://gre/modules/Downloads.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "Notifications", "resource://gre/modules/Notifications.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "OS", "resource://gre/modules/osfile.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "Services", "resource://gre/modules/Services.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyGetter(this, "strings",
|
||||
() => Services.strings.createBundle("chrome://browser/locale/browser.properties"));
|
||||
Object.defineProperty(this, "nativeWindow",
|
||||
{ get: () => Services.wm.getMostRecentWindow("navigator:browser").NativeWindow });
|
||||
|
||||
const kButtons = {
|
||||
PAUSE: new DownloadNotificationButton("pause",
|
||||
"drawable://pause",
|
||||
"alertDownloadsPause",
|
||||
notification => notification.pauseDownload()),
|
||||
RESUME: new DownloadNotificationButton("resume",
|
||||
"drawable://play",
|
||||
"alertDownloadsResume",
|
||||
notification => notification.resumeDownload()),
|
||||
CANCEL: new DownloadNotificationButton("cancel",
|
||||
"drawable://close",
|
||||
"alertDownloadsCancel",
|
||||
notification => notification.cancelDownload())
|
||||
};
|
||||
|
||||
let notifications = new Map();
|
||||
|
||||
var DownloadNotifications = {
|
||||
init: function () {
|
||||
if (!this._viewAdded) {
|
||||
Downloads.getList(Downloads.ALL)
|
||||
.then(list => list.addView(this))
|
||||
.then(null, Cu.reportError);
|
||||
|
||||
this._viewAdded = true;
|
||||
}
|
||||
},
|
||||
|
||||
uninit: function () {
|
||||
if (this._viewAdded) {
|
||||
Downloads.getList(Downloads.ALL)
|
||||
.then(list => list.removeView(this))
|
||||
.then(null, Cu.reportError);
|
||||
|
||||
for (let notification of notifications.values()) {
|
||||
notification.hide();
|
||||
}
|
||||
|
||||
this._viewAdded = false;
|
||||
}
|
||||
},
|
||||
|
||||
onDownloadAdded: function (download) {
|
||||
let notification = new DownloadNotification(download);
|
||||
notifications.set(download, notification);
|
||||
|
||||
notification.showOrUpdate();
|
||||
if (download.currentBytes == 0) {
|
||||
nativeWindow.toast.show(strings.GetStringFromName("alertDownloadsToast"), "long");
|
||||
}
|
||||
},
|
||||
|
||||
onDownloadChanged: function (download) {
|
||||
let notification = notifications.get(download);
|
||||
if (!notification) {
|
||||
Cu.reportError("Download doesn't have a notification.");
|
||||
return;
|
||||
}
|
||||
|
||||
notification.showOrUpdate();
|
||||
},
|
||||
|
||||
onDownloadRemoved: function (download) {
|
||||
let notification = notifications.get(download);
|
||||
if (!notification) {
|
||||
Cu.reportError("Download doesn't have a notification.");
|
||||
return;
|
||||
}
|
||||
|
||||
notification.hide();
|
||||
notifications.delete(download);
|
||||
}
|
||||
};
|
||||
|
||||
function DownloadNotification(download) {
|
||||
this.download = download;
|
||||
this._fileName = OS.Path.basename(download.target.path);
|
||||
|
||||
this.id = null;
|
||||
}
|
||||
|
||||
DownloadNotification.prototype = {
|
||||
_updateFromDownload: function () {
|
||||
this._downloading = !this.download.stopped;
|
||||
this._paused = this.download.canceled && this.download.hasPartialData;
|
||||
this._succeeded = this.download.succeeded;
|
||||
|
||||
this._show = this._downloading || this._paused || this._succeeded;
|
||||
},
|
||||
|
||||
get options() {
|
||||
if (!this._show) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let options = {
|
||||
icon : "drawable://alert_download",
|
||||
onClick : (id, cookie) => this.onClick(),
|
||||
onCancel : (id, cookie) => this._notificationId = null,
|
||||
cookie : this.download
|
||||
};
|
||||
|
||||
if (this._downloading) {
|
||||
if (this.download.currentBytes == 0) {
|
||||
this._updateOptionsForStatic(options, "alertDownloadsStart2");
|
||||
} else {
|
||||
this._updateOptionsForOngoing(options, [kButtons.PAUSE, kButtons.CANCEL]);
|
||||
}
|
||||
} else if (this._paused) {
|
||||
this._updateOptionsForOngoing(options, [kButtons.RESUME, kButtons.CANCEL]);
|
||||
} else if (this._succeeded) {
|
||||
options.persistent = false;
|
||||
this._updateOptionsForStatic(options, "alertDownloadsDone2");
|
||||
}
|
||||
|
||||
return options;
|
||||
},
|
||||
|
||||
_updateOptionsForStatic : function (options, titleName) {
|
||||
options.title = strings.GetStringFromName(titleName);
|
||||
options.message = this._fileName;
|
||||
},
|
||||
|
||||
_updateOptionsForOngoing: function (options, buttons) {
|
||||
options.title = this._fileName;
|
||||
options.message = this.download.progress + "%";
|
||||
options.buttons = buttons;
|
||||
options.ongoing = true;
|
||||
options.progress = this.download.progress;
|
||||
options.persistent = true;
|
||||
},
|
||||
|
||||
showOrUpdate: function () {
|
||||
this._updateFromDownload();
|
||||
|
||||
if (this._show) {
|
||||
if (!this.id) {
|
||||
this.id = Notifications.create(this.options);
|
||||
} else {
|
||||
Notifications.update(this.id, this.options);
|
||||
}
|
||||
} else {
|
||||
this.hide();
|
||||
}
|
||||
},
|
||||
|
||||
hide: function () {
|
||||
if (this.id) {
|
||||
Notifications.cancel(this.id);
|
||||
this.id = null;
|
||||
}
|
||||
},
|
||||
|
||||
onClick: function () {
|
||||
if (this.download.succeeded) {
|
||||
this.download.launch().then(null, Cu.reportError);
|
||||
} else {
|
||||
ConfirmCancelPrompt.show(this);
|
||||
}
|
||||
},
|
||||
|
||||
pauseDownload: function () {
|
||||
this.download.cancel().then(null, Cu.reportError);
|
||||
},
|
||||
|
||||
resumeDownload: function () {
|
||||
this.download.start().then(null, Cu.reportError);
|
||||
},
|
||||
|
||||
cancelDownload: function () {
|
||||
this.hide();
|
||||
|
||||
this.download.cancel().then(null, Cu.reportError);
|
||||
this.download.removePartialData().then(null, Cu.reportError);
|
||||
}
|
||||
};
|
||||
|
||||
var ConfirmCancelPrompt = {
|
||||
showing: false,
|
||||
show: function (downloadNotification) {
|
||||
if (this.showing) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.showing = true;
|
||||
// Open a prompt that offers a choice to cancel the download
|
||||
let title = strings.GetStringFromName("downloadCancelPromptTitle");
|
||||
let message = strings.GetStringFromName("downloadCancelPromptMessage");
|
||||
|
||||
if (Services.prompt.confirm(null, title, message)) {
|
||||
downloadNotification.cancelDownload();
|
||||
}
|
||||
this.showing = false;
|
||||
}
|
||||
};
|
||||
|
||||
function DownloadNotificationButton(buttonId, iconUrl, titleStringName, onClicked) {
|
||||
this.buttonId = buttonId;
|
||||
this.title = strings.GetStringFromName(titleStringName);
|
||||
this.icon = iconUrl;
|
||||
this.onClicked = (id, download) => {
|
||||
let notification = notifications.get(download);
|
||||
if (!notification) {
|
||||
Cu.reportError("No DownloadNotification for button");
|
||||
return;
|
||||
}
|
||||
|
||||
onClicked(notification);
|
||||
}
|
||||
}
|
@ -8,7 +8,6 @@ EXTRA_JS_MODULES += [
|
||||
'Accounts.jsm',
|
||||
'AndroidLog.jsm',
|
||||
'ContactService.jsm',
|
||||
'DownloadNotifications.jsm',
|
||||
'HelperApps.jsm',
|
||||
'Home.jsm',
|
||||
'HomeProvider.jsm',
|
||||
|
@ -51,7 +51,7 @@ li:active div.details,
|
||||
display: inline;
|
||||
}
|
||||
|
||||
.state {
|
||||
.displayState {
|
||||
color: gray;
|
||||
margin-bottom: -3px; /* Prevent overflow that hides bottom border */
|
||||
}
|
||||
@ -65,7 +65,7 @@ li:active div.details,
|
||||
display: none;
|
||||
}
|
||||
|
||||
#private-downloads-list:empty + #public-downloads-list:empty + #no-downloads-indicator {
|
||||
#private-downloads-list:empty + #normal-downloads-list:empty + #no-downloads-indicator {
|
||||
display: block;
|
||||
text-align: center;
|
||||
padding-top: 3.9em;
|
||||
|
@ -190,7 +190,7 @@ class TestCommandline(unittest.TestCase):
|
||||
def test_setup_logging(self):
|
||||
parser = argparse.ArgumentParser()
|
||||
commandline.add_logging_group(parser)
|
||||
args = parser.parse_args(["--log-raw=/tmp/foo"])
|
||||
args = parser.parse_args(["--log-raw=-"])
|
||||
logger = commandline.setup_logging("test", args, {})
|
||||
self.assertEqual(len(logger.handlers), 1)
|
||||
|
||||
|
@ -3,6 +3,7 @@
|
||||
Unit-Tests for moznetwork
|
||||
"""
|
||||
|
||||
import os
|
||||
import mock
|
||||
import mozinfo
|
||||
import moznetwork
|
||||
@ -29,7 +30,13 @@ def verify_ip_in_list(ip):
|
||||
"(25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)")
|
||||
|
||||
if mozinfo.isLinux or mozinfo.isMac or mozinfo.isBsd:
|
||||
args = ["ifconfig"]
|
||||
# if "/sbin/ifconfig" exist, use it because it may not be in the
|
||||
# PATH (at least on some linux platforms)
|
||||
if os.path.isfile('/sbin/ifconfig') and os.access('/sbin/ifconfig',
|
||||
os.X_OK):
|
||||
args = ['/sbin/ifconfig']
|
||||
else:
|
||||
args = ["ifconfig"]
|
||||
|
||||
if mozinfo.isWin:
|
||||
args = ["ipconfig"]
|
||||
|
@ -56,6 +56,11 @@
|
||||
"n_values": 21,
|
||||
"description": "Maximum number of concurrent threads reached during a given download session"
|
||||
},
|
||||
"BLOCKLIST_SYNC_FILE_LOAD": {
|
||||
"expires_in_version": "35",
|
||||
"kind": "boolean",
|
||||
"description": "blocklist.xml has been loaded synchronously"
|
||||
},
|
||||
"COMPARTMENT_DONATED_NODE": {
|
||||
"expires_in_version": "never",
|
||||
"kind": "boolean",
|
||||
|
@ -674,7 +674,7 @@ var NodeListActor = exports.NodeListActor = protocol.ActorClass({
|
||||
form: function() {
|
||||
return {
|
||||
actor: this.actorID,
|
||||
length: this.nodeList.length
|
||||
length: this.nodeList ? this.nodeList.length : 0
|
||||
}
|
||||
},
|
||||
|
||||
@ -1395,7 +1395,7 @@ var WalkerActor = protocol.ActorClass({
|
||||
sugs.classes.delete(HIDDEN_CLASS);
|
||||
for (let [className, count] of sugs.classes) {
|
||||
if (className.startsWith(completing)) {
|
||||
result.push(["." + className, count]);
|
||||
result.push(["." + className, count, selectorState]);
|
||||
}
|
||||
}
|
||||
break;
|
||||
@ -1409,7 +1409,7 @@ var WalkerActor = protocol.ActorClass({
|
||||
}
|
||||
for (let node of nodes) {
|
||||
if (node.id.startsWith(completing)) {
|
||||
result.push(["#" + node.id, 1]);
|
||||
result.push(["#" + node.id, 1, selectorState]);
|
||||
}
|
||||
}
|
||||
break;
|
||||
@ -1427,9 +1427,20 @@ var WalkerActor = protocol.ActorClass({
|
||||
}
|
||||
for (let [tag, count] of sugs.tags) {
|
||||
if ((new RegExp("^" + completing + ".*", "i")).test(tag)) {
|
||||
result.push([tag, count]);
|
||||
result.push([tag, count, selectorState]);
|
||||
}
|
||||
}
|
||||
|
||||
// For state 'tag' (no preceding # or .) and when there's no query (i.e.
|
||||
// only one word) then search for the matching classes and ids
|
||||
if (!query) {
|
||||
result = [
|
||||
...result,
|
||||
...this.getSuggestionsForQuery(null, completing, "class").suggestions,
|
||||
...this.getSuggestionsForQuery(null, completing, "id").suggestions
|
||||
];
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case "null":
|
||||
@ -1456,11 +1467,38 @@ var WalkerActor = protocol.ActorClass({
|
||||
}
|
||||
}
|
||||
|
||||
// Sort alphabetically in increaseing order.
|
||||
result = result.sort();
|
||||
// Sort based on count in decreasing order.
|
||||
result = result.sort(function(a, b) {
|
||||
return b[1] - a[1];
|
||||
// Sort by count (desc) and name (asc)
|
||||
result = result.sort((a, b) => {
|
||||
// Computed a sortable string with first the inverted count, then the name
|
||||
let sortA = (10000-a[1]) + a[0];
|
||||
let sortB = (10000-b[1]) + b[0];
|
||||
|
||||
// Prefixing ids, classes and tags, to group results
|
||||
let firstA = a[0].substring(0, 1);
|
||||
let firstB = b[0].substring(0, 1);
|
||||
|
||||
if (firstA === "#") {
|
||||
sortA = "2" + sortA;
|
||||
}
|
||||
else if (firstA === ".") {
|
||||
sortA = "1" + sortA;
|
||||
}
|
||||
else {
|
||||
sortA = "0" + sortA;
|
||||
}
|
||||
|
||||
if (firstB === "#") {
|
||||
sortB = "2" + sortB;
|
||||
}
|
||||
else if (firstB === ".") {
|
||||
sortB = "1" + sortB;
|
||||
}
|
||||
else {
|
||||
sortB = "0" + sortB;
|
||||
}
|
||||
|
||||
// String compare
|
||||
return sortA.localeCompare(sortB);
|
||||
});
|
||||
|
||||
result.slice(0, 25);
|
||||
|
@ -974,7 +974,8 @@ function getMatchedProps_impl(aObj, aMatch, {chainIterator, getProperties})
|
||||
let iter = chainIterator(aObj);
|
||||
for (let obj of iter) {
|
||||
let props = getProperties(obj);
|
||||
for (let prop of props) {
|
||||
for (let i = 0; i < props.length; i++) {
|
||||
let prop = props[i];
|
||||
if (prop.indexOf(aMatch) != 0) {
|
||||
continue;
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
component {66354bc9-7ed1-4692-ae1d-8da97d6b205e} nsBlocklistService.js
|
||||
contract @mozilla.org/extensions/blocklist;1 {66354bc9-7ed1-4692-ae1d-8da97d6b205e}
|
||||
category profile-after-change nsBlocklistService @mozilla.org/extensions/blocklist;1
|
||||
#ifndef MOZ_WIDGET_GONK
|
||||
category update-timer nsBlocklistService @mozilla.org/extensions/blocklist;1,getService,blocklist-background-update-timer,extensions.blocklist.interval,86400
|
||||
component {4399533d-08d1-458c-a87a-235f74451cfa} addonManager.js
|
||||
|
@ -127,10 +127,6 @@ XPCOMUtils.defineLazyGetter(this, "gCertUtils", function bls_gCertUtils() {
|
||||
return temp;
|
||||
});
|
||||
|
||||
function getObserverService() {
|
||||
return Cc["@mozilla.org/observer-service;1"].getService(Ci.nsIObserverService);
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs a string to the error console.
|
||||
* @param string
|
||||
@ -270,14 +266,15 @@ function parseRegExp(aStr) {
|
||||
*/
|
||||
|
||||
function Blocklist() {
|
||||
let os = getObserverService();
|
||||
os.addObserver(this, "xpcom-shutdown", false);
|
||||
Services.obs.addObserver(this, "xpcom-shutdown", false);
|
||||
Services.obs.addObserver(this, "sessionstore-windows-restored", false);
|
||||
gLoggingEnabled = getPref("getBoolPref", PREF_EM_LOGGING_ENABLED, false);
|
||||
gBlocklistEnabled = getPref("getBoolPref", PREF_BLOCKLIST_ENABLED, true);
|
||||
gBlocklistLevel = Math.min(getPref("getIntPref", PREF_BLOCKLIST_LEVEL, DEFAULT_LEVEL),
|
||||
MAX_BLOCK_LEVEL);
|
||||
gPref.addObserver("extensions.blocklist.", this, false);
|
||||
gPref.addObserver(PREF_EM_LOGGING_ENABLED, this, false);
|
||||
this.wrappedJSObject = this;
|
||||
}
|
||||
|
||||
Blocklist.prototype = {
|
||||
@ -302,8 +299,7 @@ Blocklist.prototype = {
|
||||
observe: function Blocklist_observe(aSubject, aTopic, aData) {
|
||||
switch (aTopic) {
|
||||
case "xpcom-shutdown":
|
||||
let os = getObserverService();
|
||||
os.removeObserver(this, "xpcom-shutdown");
|
||||
Services.obs.removeObserver(this, "xpcom-shutdown");
|
||||
gPref.removeObserver("extensions.blocklist.", this);
|
||||
gPref.removeObserver(PREF_EM_LOGGING_ENABLED, this);
|
||||
break;
|
||||
@ -324,6 +320,10 @@ Blocklist.prototype = {
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case "sessionstore-windows-restored":
|
||||
Services.obs.removeObserver(this, "sessionstore-windows-restored");
|
||||
this._preloadBlocklist();
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
@ -335,7 +335,7 @@ Blocklist.prototype = {
|
||||
|
||||
/* See nsIBlocklistService */
|
||||
getAddonBlocklistState: function Blocklist_getAddonBlocklistState(addon, appVersion, toolkitVersion) {
|
||||
if (!this._addonEntries)
|
||||
if (!this._isBlocklistLoaded())
|
||||
this._loadBlocklist();
|
||||
return this._getAddonBlocklistState(addon, this._addonEntries,
|
||||
appVersion, toolkitVersion);
|
||||
@ -438,7 +438,7 @@ Blocklist.prototype = {
|
||||
if (!gBlocklistEnabled)
|
||||
return "";
|
||||
|
||||
if (!this._addonEntries)
|
||||
if (!this._isBlocklistLoaded())
|
||||
this._loadBlocklist();
|
||||
|
||||
let blItem = this._findMatchingAddonEntry(this._addonEntries, addon);
|
||||
@ -567,7 +567,7 @@ Blocklist.prototype = {
|
||||
|
||||
// When the blocklist loads we need to compare it to the current copy so
|
||||
// make sure we have loaded it.
|
||||
if (!this._addonEntries)
|
||||
if (!this._isBlocklistLoaded())
|
||||
this._loadBlocklist();
|
||||
},
|
||||
|
||||
@ -707,11 +707,22 @@ Blocklist.prototype = {
|
||||
return;
|
||||
}
|
||||
|
||||
let telemetry = Services.telemetry;
|
||||
|
||||
if (this._isBlocklistPreloaded()) {
|
||||
telemetry.getHistogramById("BLOCKLIST_SYNC_FILE_LOAD").add(false);
|
||||
this._loadBlocklistFromString(this._preloadedBlocklistContent);
|
||||
delete this._preloadedBlocklistContent;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!file.exists()) {
|
||||
LOG("Blocklist::_loadBlocklistFromFile: XML File does not exist " + file.path);
|
||||
return;
|
||||
}
|
||||
|
||||
telemetry.getHistogramById("BLOCKLIST_SYNC_FILE_LOAD").add(true);
|
||||
|
||||
let text = "";
|
||||
let fstream = null;
|
||||
let cstream = null;
|
||||
@ -743,6 +754,60 @@ Blocklist.prototype = {
|
||||
text && this._loadBlocklistFromString(text);
|
||||
},
|
||||
|
||||
_isBlocklistLoaded: function() {
|
||||
return this._addonEntries != null && this._pluginEntries != null;
|
||||
},
|
||||
|
||||
_isBlocklistPreloaded: function() {
|
||||
return this._preloadedBlocklistContent != null;
|
||||
},
|
||||
|
||||
/* Used for testing */
|
||||
_clear: function() {
|
||||
this._addonEntries = null;
|
||||
this._pluginEntries = null;
|
||||
this._preloadedBlocklistContent = null;
|
||||
},
|
||||
|
||||
_preloadBlocklist: Task.async(function*() {
|
||||
let profPath = OS.Path.join(OS.Constants.Path.profileDir, FILE_BLOCKLIST);
|
||||
try {
|
||||
yield this._preloadBlocklistFile(profPath);
|
||||
return;
|
||||
} catch (e) {
|
||||
LOG("Blocklist::_preloadBlocklist: Failed to load XML file " + e)
|
||||
}
|
||||
|
||||
var appFile = FileUtils.getFile(KEY_APPDIR, [FILE_BLOCKLIST]);
|
||||
try{
|
||||
yield this._preloadBlocklistFile(appFile.path);
|
||||
return;
|
||||
} catch (e) {
|
||||
LOG("Blocklist::_preloadBlocklist: Failed to load XML file " + e)
|
||||
}
|
||||
|
||||
LOG("Blocklist::_preloadBlocklist: no XML File found");
|
||||
}),
|
||||
|
||||
_preloadBlocklistFile: Task.async(function* (path){
|
||||
if (this._addonEntries) {
|
||||
// The file has been already loaded.
|
||||
return;
|
||||
}
|
||||
|
||||
if (!gBlocklistEnabled) {
|
||||
LOG("Blocklist::_preloadBlocklistFile: blocklist is disabled");
|
||||
return;
|
||||
}
|
||||
|
||||
let text = yield OS.File.read(path, { encoding: "utf-8" });
|
||||
|
||||
if (!this._addonEntries) {
|
||||
// Store the content only if a sync load has not been performed in the meantime.
|
||||
this._preloadedBlocklistContent = text;
|
||||
}
|
||||
}),
|
||||
|
||||
_loadBlocklistFromString : function Blocklist_loadBlocklistFromString(text) {
|
||||
try {
|
||||
var parser = Cc["@mozilla.org/xmlextras/domparser;1"].
|
||||
@ -891,7 +956,7 @@ Blocklist.prototype = {
|
||||
/* See nsIBlocklistService */
|
||||
getPluginBlocklistState: function Blocklist_getPluginBlocklistState(plugin,
|
||||
appVersion, toolkitVersion) {
|
||||
if (!this._pluginEntries)
|
||||
if (!this._isBlocklistLoaded())
|
||||
this._loadBlocklist();
|
||||
return this._getPluginBlocklistState(plugin, this._pluginEntries,
|
||||
appVersion, toolkitVersion);
|
||||
@ -964,7 +1029,7 @@ Blocklist.prototype = {
|
||||
if (!gBlocklistEnabled)
|
||||
return "";
|
||||
|
||||
if (!this._pluginEntries)
|
||||
if (!this._isBlocklistLoaded())
|
||||
this._loadBlocklist();
|
||||
|
||||
for each (let blockEntry in this._pluginEntries) {
|
||||
|
@ -0,0 +1,44 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/
|
||||
*/
|
||||
|
||||
function run_test() {
|
||||
run_next_test();
|
||||
}
|
||||
|
||||
add_task(function () {
|
||||
let blocklist = AM_Cc["@mozilla.org/extensions/blocklist;1"].
|
||||
getService().wrappedJSObject;
|
||||
let scope = Components.utils.import("resource://gre/modules/osfile.jsm");
|
||||
|
||||
// sync -> async
|
||||
blocklist._loadBlocklist();
|
||||
do_check_true(blocklist._isBlocklistLoaded());
|
||||
yield blocklist._preloadBlocklist();
|
||||
do_check_false(blocklist._isBlocklistPreloaded());
|
||||
blocklist._clear();
|
||||
|
||||
// async -> sync
|
||||
yield blocklist._preloadBlocklist();
|
||||
do_check_false(blocklist._isBlocklistLoaded());
|
||||
do_check_true(blocklist._isBlocklistPreloaded());
|
||||
blocklist._loadBlocklist();
|
||||
do_check_true(blocklist._isBlocklistLoaded());
|
||||
do_check_false(blocklist._isBlocklistPreloaded());
|
||||
blocklist._clear();
|
||||
|
||||
// async -> sync -> async
|
||||
let read = scope.OS.File.read;
|
||||
scope.OS.File.read = function(...args) {
|
||||
return new Promise((resolve, reject) => {
|
||||
do_execute_soon(() => {
|
||||
blocklist._loadBlocklist();
|
||||
resolve(read(...args));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
yield blocklist._preloadBlocklist();
|
||||
do_check_true(blocklist._isBlocklistLoaded());
|
||||
do_check_false(blocklist._isBlocklistPreloaded());
|
||||
});
|
@ -8,3 +8,5 @@ support-files =
|
||||
xpcshell-shared.ini
|
||||
|
||||
[include:xpcshell-shared.ini]
|
||||
|
||||
[test_asyncBlocklistLoad.js]
|
||||
|
Loading…
Reference in New Issue
Block a user