This commit is contained in:
Ryan VanderMeulen 2013-08-02 19:55:10 -04:00
Родитель 77bd411859 f26052e447
Коммит 9cc0ca802c
50 изменённых файлов: 917 добавлений и 121 удалений

Просмотреть файл

@ -20,6 +20,10 @@ let promise = require("sdk/core/promise");
Cu.import("resource:///modules/devtools/LayoutHelpers.jsm");
Cu.import("resource://gre/modules/devtools/Templater.jsm");
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
XPCOMUtils.defineLazyGetter(this, "AutocompletePopup", function() {
return Cu.import("resource:///modules/devtools/AutocompletePopup.jsm", {}).AutocompletePopup;
});
/**
* Vocabulary for the purposes of this file:
@ -53,6 +57,14 @@ function MarkupView(aInspector, aFrame, aControllerWindow)
this.maxChildren = DEFAULT_MAX_CHILDREN;
}
// Creating the popup to be used to show CSS suggestions.
let options = {
fixedWidth: true,
autoSelect: true,
theme: "auto"
};
this.popup = new AutocompletePopup(this.doc.defaultView.parent.document, options);
this.undo = new UndoStack();
this.undo.installController(aControllerWindow);
@ -676,6 +688,9 @@ MarkupView.prototype = {
this.undo.destroy();
delete this.undo;
this.popup.destroy();
delete this.popup;
this._frame.removeEventListener("focus", this._boundFocus, false);
delete this._boundFocus;
@ -1130,6 +1145,8 @@ function ElementEditor(aContainer, aNode)
element: this.newAttr,
trigger: "dblclick",
stopOnReturn: true,
contentType: InplaceEditor.CONTENT_TYPES.CSS_MIXED,
popup: this.markup.popup,
done: (aVal, aCommit) => {
if (!aCommit) {
return;
@ -1222,6 +1239,8 @@ ElementEditor.prototype = {
trigger: "dblclick",
stopOnReturn: true,
selectAll: false,
contentType: InplaceEditor.CONTENT_TYPES.CSS_MIXED,
popup: this.markup.popup,
start: (aEditor, aEvent) => {
// If the editing was started inside the name or value areas,
// select accordingly.

Просмотреть файл

@ -19,6 +19,7 @@ MOCHITEST_BROWSER_FILES := \
browser_inspector_markup_edit.js \
browser_inspector_markup_subset.html \
browser_inspector_markup_subset.js \
browser_bug896181_css_mixed_completion_new_attribute.js \
head.js \
$(NULL)

Просмотреть файл

@ -0,0 +1,167 @@
/* vim: set ts=2 et sw=2 tw=80: */
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
// Test CSS state is correctly determined and the corresponding suggestions are
// displayed. i.e. CSS property suggestions are shown when cursor is like:
// ```style="di|"``` where | is teh cursor; And CSS value suggestion is
// displayed when the cursor is like: ```style="display:n|"``` properly. No
// suggestions should ever appear when the attribute is not a style attribute.
// The correctness and cycling of the suggestions is covered in the ruleview
// tests.
function test() {
let inspector;
let {
getInplaceEditorForSpan: inplaceEditor
} = devtools.require("devtools/shared/inplace-editor");
waitForExplicitFinish();
// Will hold the doc we're viewing
let doc;
// Holds the MarkupTool object we're testing.
let markup;
let editor;
let state;
// format :
// [
// what key to press,
// expected input box value after keypress,
// expected input.selectionStart,
// expected input.selectionEnd,
// is popup expected to be open ?
// ]
let testData = [
['s', 's', 1, 1, false],
['t', 'st', 2, 2, false],
['y', 'sty', 3, 3, false],
['l', 'styl', 4, 4, false],
['e', 'style', 5, 5, false],
['=', 'style=', 6, 6, false],
['"', 'style="', 7, 7, false],
['d', 'style="direction', 8, 16, true],
['VK_DOWN', 'style="display', 8, 14, true],
['VK_RIGHT', 'style="display', 14, 14, false],
[':', 'style="display:', 15, 15, false],
['n', 'style="display:none', 16, 19, false],
['VK_BACK_SPACE', 'style="display:n', 16, 16, false],
['VK_BACK_SPACE', 'style="display:', 15, 15, false],
[' ', 'style="display: ', 16, 16, false],
[' ', 'style="display: ', 17, 17, false],
['i', 'style="display: inherit', 18, 24, true],
['VK_RIGHT', 'style="display: inherit', 24, 24, false],
[';', 'style="display: inherit;', 25, 25, false],
[' ', 'style="display: inherit; ', 26, 26, false],
[' ', 'style="display: inherit; ', 27, 27, false],
['VK_LEFT', 'style="display: inherit; ', 26, 26, false],
['c', 'style="display: inherit; caption-side ', 27, 38, true],
['o', 'style="display: inherit; color ', 28, 31, true],
['VK_RIGHT', 'style="display: inherit; color ', 31, 31, false],
[' ', 'style="display: inherit; color ', 32, 32, false],
['c', 'style="display: inherit; color c ', 33, 33, false],
['VK_BACK_SPACE', 'style="display: inherit; color ', 32, 32, false],
[':', 'style="display: inherit; color : ', 33, 33, false],
['c', 'style="display: inherit; color :cadetblue ', 34, 42, true],
['VK_DOWN', 'style="display: inherit; color :chartreuse ', 34, 43, true],
['VK_RETURN', 'style="display: inherit; color :chartreuse"', -1, -1, false]
];
function startTests() {
markup = inspector.markup;
markup.expandAll().then(() => {
let node = getContainerForRawNode(markup, doc.querySelector("#node14")).editor;
let attr = node.newAttr;
attr.focus();
EventUtils.sendKey("return", inspector.panelWin);
editor = inplaceEditor(attr);
checkStateAndMoveOn(0);
});
}
function checkStateAndMoveOn(index) {
if (index == testData.length) {
finishUp();
return;
}
let [key] = testData[index];
state = index;
info("pressing key " + key + " to get result: [" + testData[index].slice(1) +
"] for state " + state);
if (/(down|left|right|back_space|return)/ig.test(key)) {
info("added event listener for down|left|right|back_space|return keys");
editor.input.addEventListener("keypress", function onKeypress() {
if (editor.input) {
editor.input.removeEventListener("keypress", onKeypress);
}
info("inside event listener");
checkState();
})
}
else {
editor.once("after-suggest", checkState);
}
EventUtils.synthesizeKey(key, {}, inspector.panelWin);
}
function checkState() {
executeSoon(() => {
info("After keypress for state " + state);
let [key, completion, selStart, selEnd, popupOpen] = testData[state];
if (selEnd != -1) {
is(editor.input.value, completion,
"Correct value is autocompleted for state " + state);
is(editor.input.selectionStart, selStart,
"Selection is starting at the right location for state " + state);
is(editor.input.selectionEnd, selEnd,
"Selection is ending at the right location for state " + state);
if (popupOpen) {
ok(editor.popup._panel.state == "open" ||
editor.popup._panel.state == "showing",
"Popup is open for state " + state);
}
else {
ok(editor.popup._panel.state != "open" &&
editor.popup._panel.state != "showing",
"Popup is closed for state " + state);
}
}
else {
let editor = getContainerForRawNode(markup, doc.querySelector("#node14")).editor;
let attr = editor.attrs["style"].querySelector(".editable");
is(attr.textContent, completion,
"Correct value is persisted after pressing Enter for state " + state);
}
checkStateAndMoveOn(state + 1);
});
}
// Create the helper tab for parsing...
gBrowser.selectedTab = gBrowser.addTab();
gBrowser.selectedBrowser.addEventListener("load", function onload() {
gBrowser.selectedBrowser.removeEventListener("load", onload, true);
doc = content.document;
waitForFocus(setupTest, content);
}, true);
content.location = "http://mochi.test:8888/browser/browser/devtools/markupview/test/browser_inspector_markup_edit.html";
function setupTest() {
var target = TargetFactory.forTab(gBrowser.selectedTab);
gDevTools.showToolbox(target, "inspector").then(function(toolbox) {
inspector = toolbox.getCurrentPanel();
startTests();
});
}
function finishUp() {
while (markup.undo.canUndo()) {
markup.undo.undo();
}
doc = inspector = null;
gBrowser.removeCurrentTab();
finish();
}
}

Просмотреть файл

@ -759,7 +759,13 @@ InplaceEditor.prototype = {
} else {
this.popup.selectNextItem();
}
this.input.value = this.popup.selectedItem.label;
let input = this.input;
let pre = input.value.slice(0, input.selectionStart);
let post = input.value.slice(input.selectionEnd, input.value.length);
let item = this.popup.selectedItem;
let toComplete = item.label.slice(item.preLabel.length);
input.value = pre + toComplete + post;
input.setSelectionRange(pre.length, pre.length + toComplete.length);
this._updateSize();
// This emit is mainly for the purpose of making the test flow simpler.
this.emit("after-suggest");
@ -898,6 +904,7 @@ InplaceEditor.prototype = {
return;
}
let query = input.value.slice(0, input.selectionStart);
let startCheckQuery = query;
if (!query) {
return;
}
@ -906,28 +913,52 @@ InplaceEditor.prototype = {
if (this.contentType == CONTENT_TYPES.CSS_PROPERTY) {
list = CSSPropertyList;
} else if (this.contentType == CONTENT_TYPES.CSS_VALUE) {
list = domUtils.getCSSValuesForProperty(this.property.name).sort();
list = domUtils.getCSSValuesForProperty(this.property.name);
} else if (this.contentType == CONTENT_TYPES.CSS_MIXED &&
/^\s*style\s*=/.test(query)) {
// Detecting if cursor is at property or value;
let match = query.match(/([:;"'=]?)\s*([^"';:= ]+)$/);
if (match && match.length == 3) {
if (match[1] == ":") { // We are in CSS value completion
let propertyName =
query.match(/[;"'=]\s*([^"';:= ]+)\s*:\s*[^"';:= ]+$/)[1];
list = domUtils.getCSSValuesForProperty(propertyName);
startCheckQuery = match[2];
} else if (match[1]) { // We are in CSS property name completion
list = CSSPropertyList;
startCheckQuery = match[2];
}
if (!startCheckQuery) {
// This emit is mainly to make the test flow simpler.
this.emit("after-suggest", "nothing to autocomplete");
return;
}
}
}
list.some(item => {
if (item.startsWith(query)) {
input.value = item;
input.setSelectionRange(query.length, item.length);
if (item.startsWith(startCheckQuery)) {
input.value = query + item.slice(startCheckQuery.length) +
input.value.slice(query.length);
input.setSelectionRange(query.length, query.length + item.length -
startCheckQuery.length);
this._updateSize();
return true;
}
});
if (!this.popup) {
// This emit is mainly to make the test flow simpler.
this.emit("after-suggest", "no popup");
return;
}
let finalList = [];
let length = list.length;
for (let i = 0, count = 0; i < length && count < MAX_POPUP_ENTRIES; i++) {
if (list[i].startsWith(query)) {
if (list[i].startsWith(startCheckQuery)) {
count++;
finalList.push({
preLabel: query,
preLabel: startCheckQuery,
label: list[i]
});
}
@ -936,7 +967,7 @@ InplaceEditor.prototype = {
// which would have started with query, assuming that list is sorted.
break;
}
else if (list[i][0] > query[0]) {
else if (list[i][0] > startCheckQuery[0]) {
// We have crossed all possible matches alphabetically.
break;
}

Просмотреть файл

@ -39,11 +39,6 @@ let testData = [
["VK_UP", "direction", 0, 3],
["VK_UP", "dominant-baseline", 2, 3],
["VK_UP", "display", 1, 3],
["VK_BACK_SPACE", "displa", -1, 0],
["VK_BACK_SPACE", "displ", -1, 0],
["VK_BACK_SPACE", "disp", -1, 0],
["VK_BACK_SPACE", "dis", -1, 0],
["VK_BACK_SPACE", "di", -1, 0],
["VK_BACK_SPACE", "d", -1, 0],
["i", "direction", 0, 2],
["s", "display", -1, 0],

Просмотреть файл

@ -27,11 +27,6 @@ let testData = [
["VK_UP", "direction", 0, 3],
["VK_UP", "dominant-baseline", 2, 3],
["VK_UP", "display", 1, 3],
["VK_BACK_SPACE", "displa", -1, 0],
["VK_BACK_SPACE", "displ", -1, 0],
["VK_BACK_SPACE", "disp", -1, 0],
["VK_BACK_SPACE", "dis", -1, 0],
["VK_BACK_SPACE", "di", -1, 0],
["VK_BACK_SPACE", "d", -1, 0],
["i", "direction", 0, 2],
["s", "display", -1, 0],

Просмотреть файл

@ -144,6 +144,7 @@ MOCHITEST_BROWSER_FILES = \
browser_console_iframe_messages.js \
browser_console_variables_view_while_debugging_and_inspecting.js \
browser_webconsole_bug_686937_autocomplete_JSTerm_helpers.js \
browser_webconsole_cached_autocomplete.js \
head.js \
$(NULL)

Просмотреть файл

@ -0,0 +1,125 @@
/* vim:set ts=2 sw=2 sts=2 et: */
/* 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/. */
// Tests that the cached autocomplete results are used when the new
// user input is a subset of the existing completion results.
const TEST_URI = "data:text/html;charset=utf8,<p>test cached autocompletion results";
let testDriver;
function test() {
requestLongerTimeout(2);
addTab(TEST_URI);
browser.addEventListener("load", function onLoad() {
browser.removeEventListener("load", onLoad, true);
openConsole(null, function(hud) {
testDriver = testCompletion(hud);
testDriver.next();
});
}, true);
}
function testNext() {
executeSoon(function() {
testDriver.next();
});
}
function testCompletion(hud) {
let jsterm = hud.jsterm;
let input = jsterm.inputNode;
let popup = jsterm.autocompletePopup;
// Test if 'doc' gives 'document'
input.value = "doc";
input.setSelectionRange(3, 3);
jsterm.complete(jsterm.COMPLETE_HINT_ONLY, testNext);
yield undefined;
is(input.value, "doc", "'docu' completion (input.value)");
is(jsterm.completeNode.value, " ument", "'docu' completion (completeNode)");
// Test typing 'window.'.
input.value = "window.";
input.setSelectionRange(7, 7);
jsterm.complete(jsterm.COMPLETE_HINT_ONLY, testNext);
yield undefined;
ok(popup.getItems().length > 0, "'window.' gave a list of suggestions")
content.wrappedJSObject.docfoobar = true;
// Test typing 'window.doc'.
input.value = "window.doc";
input.setSelectionRange(10, 10);
jsterm.complete(jsterm.COMPLETE_HINT_ONLY, testNext);
yield undefined;
let newItems = popup.getItems();
ok(newItems.every(function(item) {
return item.label != "docfoobar";
}), "autocomplete cached results do not contain docfoobar. list has not been updated");
// Test that backspace does not cause a request to the server
input.value = "window.do";
input.setSelectionRange(9, 9);
jsterm.complete(jsterm.COMPLETE_HINT_ONLY, testNext);
yield undefined;
newItems = popup.getItems();
ok(newItems.every(function(item) {
return item.label != "docfoobar";
}), "autocomplete cached results do not contain docfoobar. list has not been updated");
delete content.wrappedJSObject.docfoobar;
// Test if 'window.getC' gives 'getComputedStyle'
input.value = "window."
input.setSelectionRange(7, 7);
jsterm.complete(jsterm.COMPLETE_HINT_ONLY, testNext);
yield undefined;
input.value = "window.getC";
input.setSelectionRange(11, 11);
jsterm.complete(jsterm.COMPLETE_HINT_ONLY, testNext);
yield undefined;
newItems = popup.getItems();
ok(!newItems.every(function(item) {
return item.label != "getComputedStyle";
}), "autocomplete results do contain getComputedStyle");
// Test if 'dump(d' gives non-zero results
input.value = "dump(d";
input.setSelectionRange(6, 6);
jsterm.complete(jsterm.COMPLETE_HINT_ONLY, testNext);
yield undefined;
ok(popup.getItems().length > 0, "'dump(d' gives non-zero results");
// Test that 'dump(window.)' works.
input.value = "dump(window.)";
input.setSelectionRange(12, 12);
jsterm.complete(jsterm.COMPLETE_HINT_ONLY, testNext);
yield undefined;
content.wrappedJSObject.docfoobar = true;
// Make sure 'dump(window.doc)' does not contain 'docfoobar'.
input.value = "dump(window.doc)";
input.setSelectionRange(15, 15);
jsterm.complete(jsterm.COMPLETE_HINT_ONLY, testNext);
yield undefined;
newItems = popup.getItems();
ok(newItems.every(function(item) {
return item.label != "docfoobar";
}), "autocomplete cached results do not contain docfoobar. list has not been updated");
delete content.wrappedJSObject.docfoobar;
testDriver = null;
executeSoon(finishTest);
yield undefined;
}

Просмотреть файл

@ -2844,6 +2844,21 @@ JSTerm.prototype = {
*/
lastCompletion: null,
/**
* Array that caches the user input suggestions received from the server.
* @private
* @type array
*/
_autocompleteCache: null,
/**
* The input that caused the last request to the server, whose response is
* cached in the _autocompleteCache array.
* @private
* @type string
*/
_autocompleteQuery: null,
/**
* The Web Console sidebar.
* @see this._createSidebar()
@ -4125,11 +4140,46 @@ JSTerm.prototype = {
}
let requestId = gSequenceId();
let input = this.inputNode.value;
let cursor = this.inputNode.selectionStart;
let input = this.inputNode.value.substring(0, cursor);
let cache = this._autocompleteCache;
// If the current input starts with the previous input, then we already
// have a list of suggestions and we just need to filter the cached
// suggestions. When the current input ends with a non-alphanumeric
// character we ask the server again for suggestions.
// Check if last character is non-alphanumeric
if (!/[a-zA-Z0-9]$/.test(input)) {
this._autocompleteQuery = null;
this._autocompleteCache = null;
}
if (this._autocompleteQuery && input.startsWith(this._autocompleteQuery)) {
let filterBy = input;
// Find the last non-alphanumeric if exists.
let lastNonAlpha = input.match(/[^a-zA-Z0-9][a-zA-Z0-9]*$/);
// If input contains non-alphanumerics, use the part after the last one
// to filter the cache
if (lastNonAlpha) {
filterBy = input.substring(input.lastIndexOf(lastNonAlpha) + 1);
}
let newList = cache.sort().filter(function(l) {
return l.startsWith(filterBy);
});
this.lastCompletion = {
requestId: null,
completionType: aType,
value: null,
};
let response = { matches: newList, matchProp: filterBy };
this._receiveAutocompleteProperties(null, aCallback, response);
return;
}
// TODO: Bug 787986 - throttle/disable updates, deal with slow/high latency
// network connections.
this.lastCompletion = {
requestId: requestId,
completionType: aType,
@ -4164,6 +4214,15 @@ JSTerm.prototype = {
return;
}
// Cache whatever came from the server if the last char is alphanumeric or '.'
let cursor = inputNode.selectionStart;
let inputUntilCursor = inputValue.substring(0, cursor);
if (aRequestId != null && /[a-zA-Z0-9.]$/.test(inputUntilCursor)) {
this._autocompleteCache = aMessage.matches;
this._autocompleteQuery = inputUntilCursor;
}
let matches = aMessage.matches;
let lastPart = aMessage.matchProp;
if (!matches.length) {

Просмотреть файл

@ -68,6 +68,8 @@ var ContextCommands = {
SelectionHelperUI.closeEditSession(true);
}
} else if (ContextMenuUI.popupState.string) {
this.clipboard.copyString(ContextMenuUI.popupState.string, this.docRef);
} else {
// chrome
target.editor.copy();

Просмотреть файл

@ -73,10 +73,5 @@
</body>
</method>
</implementation>
<handlers>
<!-- Work around for bug 835175 -->
<handler event="click">false;</handler>
</handlers>
</binding>
</bindings>

Просмотреть файл

@ -0,0 +1,78 @@
<?xml version="1.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/. -->
<bindings xmlns="http://www.mozilla.org/xbl"
xmlns:xbl="http://www.mozilla.org/xbl"
xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
xmlns:html="http://www.w3.org/1999/xhtml">
<binding id="circular-progress-indicator">
<content>
<xul:stack>
<xul:toolbarbutton anonid="progressButton" class="circularprogressindicator-progressButton appbar-secondary"/>
<html:canvas anonid="progressRing" class="circularprogressindicator-progressRing" width="46" height="46"></html:canvas>
</xul:stack>
</content>
<implementation>
<field name="_progressCanvas">
document.getAnonymousElementByAttribute(this, "anonid", "progressRing");
</field>
<field name="_progressCircleCtx">null</field>
<field name="_img">null</field>
<constructor>
<![CDATA[
this._progressCircleCtx = this._progressCanvas.getContext('2d');
this._img = new Image();
]]>
</constructor>
<method name="updateProgress">
<parameter name="percentComplete"/>
<body>
<![CDATA[
const PROGRESS_RING_IMG = "chrome://browser/skin/images/progresscircle.png";
let startAngle = 1.5 * Math.PI;
let endAngle = startAngle + (2 * Math.PI * (percentComplete / 100));
let ctx = this._progressCircleCtx;
ctx.clearRect(0, 0,
this._progressCanvas.width, this._progressCanvas.height);
// Save the state, so we can undo the clipping
ctx.save();
ctx.beginPath();
let center = this._progressCanvas.width / 2;
ctx.arc(center, center, center, startAngle, endAngle, false);
ctx.lineTo(center, center);
ctx.closePath();
ctx.clip();
// Draw circle image.
if (this._img && this._img.src) {
ctx.drawImage(this._img, 0, 0);
} else {
this._img.onload = function() {
ctx.drawImage(this._img, 0, 0);
}.bind(this);
this._img.src = PROGRESS_RING_IMG;
}
ctx.restore();
return [startAngle, endAngle];
]]>
</body>
</method>
<method name="reset">
<body>
<![CDATA[
this._progressCircleCtx.clearRect(0, 0,
this._progressCanvas.width, this._progressCanvas.height);
]]>
</body>
</method>
</implementation>
</binding>
</bindings>

Просмотреть файл

@ -99,10 +99,5 @@
document.getAnonymousElementByAttribute(this, "class", "flyoutpanel-contents");
]]></field>
</implementation>
<handlers>
<!-- Work around for bug 835175 -->
<handler event="click">false;</handler>
</handlers>
</binding>
</bindings>

Просмотреть файл

@ -42,6 +42,10 @@ autoscroller {
-moz-binding: url('chrome://browser/content/bindings/popup.xml#element-popup');
}
circularprogressindicator {
-moz-binding: url('chrome://browser/content/bindings/circularprogress.xml#circular-progress-indicator');
}
setting[type="bool"] {
display: -moz-box;
-moz-binding: url("chrome://browser/content/bindings/toggleswitch.xml#setting-fulltoggle-bool");

Просмотреть файл

@ -181,8 +181,7 @@
<vbox id="page">
<vbox id="tray" class="tray-toolbar" observes="bcast_windowState" >
<!-- Tabs -->
<!-- onclick handler to work around bug 837242 -->
<hbox id="tabs-container" observes="bcast_windowState" onclick="void(0);">
<hbox id="tabs-container" observes="bcast_windowState">
<box id="tabs" flex="1"
observes="bcast_preciseInput"
onselect="BrowserUI.selectTabAndDismiss(this);"
@ -243,8 +242,7 @@
<box id="horizontal-scroller" class="scroller" orient="horizontal" left="0" bottom="0"/>
<!-- Content touch selection overlay -->
<!-- onclick addresses dom bug 835175 -->
<box onclick="false" class="selection-overlay-hidden" id="content-selection-overlay"/>
<box class="selection-overlay-hidden" id="content-selection-overlay"/>
</stack>
</vbox>
@ -293,6 +291,8 @@
command="cmd_stop"/>
</hbox>
<circularprogressindicator id="download-progress"
oncommand="Appbar.onDownloadButton()"/>
<stack id="toolbar-contextual">
<observes element="bcast_windowState" attribute="*"/>
<observes element="bcast_urlbarState" attribute="*"/>
@ -736,11 +736,9 @@
</flyoutpanel>
<!-- Chrome touch selection overlay -->
<!-- onclick addresses dom bug 835175 -->
<box onclick="false" class="selection-overlay-hidden" id="chrome-selection-overlay"/>
<box class="selection-overlay-hidden" id="chrome-selection-overlay"/>
<box onclick="event.stopPropagation();" id="context-container" class="menu-container" hidden="true">
<!-- onclick is dom bug 835175 -->
<vbox id="context-popup" class="menu-popup">
<richlistbox id="context-commands" bindingType="contextmenu" flex="1">
<!-- priority="low" items are hidden by default when a context is being displayed

Просмотреть файл

@ -243,7 +243,9 @@ let ConsolePanelView = {
target: row,
json: {
types: ["copy"],
string: text
string: text,
xPos: aEvent.clientX,
yPos: aEvent.clientY
}
});
},

Просмотреть файл

@ -12,6 +12,7 @@ var Downloads = {
* downloads if it starts before other downloads have completed.
*/
_downloadCount: 0,
_downloadsInProgress: 0,
_inited: false,
_progressAlert: null,
_lastSec: Infinity,
@ -57,6 +58,8 @@ var Downloads = {
this._progress = new DownloadProgressListener(this);
this.manager.addListener(this._progress);
this._downloadProgressIndicator = document.getElementById("download-progress");
},
uninit: function dh_uninit() {
@ -97,25 +100,26 @@ var Downloads = {
},
cancelDownload: function dh_cancelDownload(aDownload) {
this._progressNotificationInfo.delete(aDownload.guid);
this._runDownloadBooleanMap.delete(aDownload.targetFile.path);
this._downloadCount--;
if (this._progressNotificationInfo.size == 0) {
this._notificationBox.removeNotification(this._progressNotification);
this._progressNotification = null;
}
let fileURI = aDownload.target;
if (!(fileURI && fileURI.spec)) {
Util.dumpLn("Cant remove download file for: "+aDownload.id+", fileURI is invalid");
return;
}
let file = this._getLocalFile(fileURI);
try {
this.manager.cancelDownload(aDownload.id);
let file = this._getLocalFile(fileURI);
if (file && file.exists())
file.remove(false);
this.manager.cancelDownload(aDownload.id);
// If cancelling was successful, stop tracking the download.
this._progressNotificationInfo.delete(aDownload.guid);
this._runDownloadBooleanMap.delete(aDownload.targetFile.path);
this._downloadCount--;
this._downloadsInProgress--;
if (this._downloadsInProgress <= 0) {
this._notificationBox.removeNotification(this._progressNotification);
this._progressNotification = null;
}
} catch (ex) {
Util.dumpLn("Failed to cancel download, with id: "+aDownload.id+", download target URI spec: " + fileURI.spec);
Util.dumpLn("Failed download source:"+(aDownload.source && aDownload.source.spec));
@ -196,6 +200,7 @@ var Downloads = {
accessKey: "",
callback: function() {
Downloads.manager.retryDownload(aDownload.id);
Downloads._downloadProgressIndicator.reset();
}
},
{
@ -203,6 +208,7 @@ var Downloads = {
accessKey: "",
callback: function() {
Downloads.cancelDownload(aDownload);
Downloads._downloadProgressIndicator.reset();
}
}
];
@ -222,6 +228,7 @@ var Downloads = {
let fileURI = aDownload.target;
let file = Downloads._getLocalFile(fileURI);
file.reveal();
Downloads._downloadProgressIndicator.reset();
}
}
];
@ -242,6 +249,7 @@ var Downloads = {
accessKey: "",
callback: function() {
Downloads.openDownload(aDownload);
Downloads._downloadProgressIndicator.reset();
}
});
}
@ -249,6 +257,22 @@ var Downloads = {
this._notificationBox.PRIORITY_WARNING_MEDIUM);
},
_updateCircularProgressMeter: function dv_updateCircularProgressMeter() {
if (!this._progressNotificationInfo) {
return;
}
let totPercent = 0;
for (let info of this._progressNotificationInfo) {
// info[0] => download guid
// info[1].download => nsIDownload
totPercent += info[1].download.percentComplete;
}
let percentComplete = totPercent / this._progressNotificationInfo.size;
this._downloadProgressIndicator.updateProgress(percentComplete);
},
_computeDownloadProgressString: function dv_computeDownloadProgressString(aDownload) {
let totTransferred = 0, totSize = 0, totSecondsLeft = 0;
for (let info of this._progressNotificationInfo) {
@ -260,6 +284,7 @@ var Downloads = {
totSize += size;
totSecondsLeft += ((size - amountTransferred) / speed);
}
// Compute progress in bytes.
let amountTransferred = Util.getDownloadSize(totTransferred);
let size = Util.getDownloadSize(totSize);
@ -296,6 +321,7 @@ var Downloads = {
updateInfobar: function dv_updateInfobar(aDownload) {
this._saveDownloadData(aDownload);
let message = this._computeDownloadProgressString(aDownload);
this._updateCircularProgressMeter();
if (this._progressNotification == null ||
!this._notificationBox.getNotificationWithValue("download-progress")) {
@ -310,6 +336,7 @@ var Downloads = {
accessKey: "",
callback: function() {
Downloads.cancelDownloads();
Downloads._downloadProgressIndicator.reset();
}
}
];
@ -327,6 +354,7 @@ var Downloads = {
this._saveDownloadData(aDownload);
this._progressNotification.label =
this._computeDownloadProgressString(aDownload);
this._updateCircularProgressMeter();
}
},
@ -341,6 +369,7 @@ var Downloads = {
break;
case "dl-start":
this._downloadCount++;
this._downloadsInProgress++;
let download = aSubject.QueryInterface(Ci.nsIDownload);
if (!this._progressNotificationInfo.get(download.guid)) {
this._progressNotificationInfo.set(download.guid, {});
@ -352,18 +381,19 @@ var Downloads = {
this.updateInfobar(download);
break;
case "dl-done":
this._downloadsInProgress--;
download = aSubject.QueryInterface(Ci.nsIDownload);
let runAfterDownload = this._runDownloadBooleanMap.get(download.targetFile.path);
if (runAfterDownload) {
this.openDownload(download);
}
this._progressNotificationInfo.delete(download.guid);
this._runDownloadBooleanMap.delete(download.targetFile.path);
if (this._progressNotificationInfo.size == 0) {
if (this._downloadsInProgress == 0) {
if (this._downloadCount > 1 || !runAfterDownload) {
this._showDownloadCompleteNotification(download);
}
this._progressNotificationInfo.clear();
this._downloadCount = 0;
this._notificationBox.removeNotification(this._progressNotification);
this._progressNotification = null;
@ -371,6 +401,7 @@ var Downloads = {
break;
case "dl-failed":
download = aSubject.QueryInterface(Ci.nsIDownload);
this._showDownloadFailedNotification(download);
break;
}
},

Просмотреть файл

@ -26,6 +26,7 @@ chrome.jar:
content/bindings/selectionoverlay.xml (content/bindings/selectionoverlay.xml)
content/bindings/cssthrobber.xml (content/bindings/cssthrobber.xml)
content/bindings/popup.xml (content/bindings/popup.xml)
content/bindings/circularprogress.xml (content/bindings/circularprogress.xml)
* content/flyoutpanels/FlyoutPanelsUI.js (content/flyoutpanels/FlyoutPanelsUI.js)
* content/flyoutpanels/AboutFlyoutPanel.js (content/flyoutpanels/AboutFlyoutPanel.js)

Просмотреть файл

@ -10,25 +10,29 @@ relativesrcdir = @relativesrcdir@
include $(DEPTH)/config/autoconf.mk
# Disabled for intermittent failures (bug 880739)
# Disabled for intermittent failures
# Bug 880739
# browser_context_menu_tests.js \
# browser_context_menu_tests_01.html \
# browser_context_menu_tests_02.html \
# browser_context_menu_tests_03.html \
# Bug 897175
# browser_findbar.js \
# browser_findbar.html \
MOCHITEST_METRO_FILES = \
head.js \
browser_urlbar.js \
browser_bookmarks.js \
browser_canonizeURL.js \
browser_context_menu_tests_01.html \
browser_context_menu_tests_02.html \
browser_context_menu_tests_03.html \
browser_circular_progress_indicator.js \
browser_context_ui.js \
browser_downloads.js \
browser_findbar.js \
browser_findbar.html \
browser_history.js \
browser_onscreen_keyboard.js \
browser_onscreen_keyboard.html \
browser_prefs_ui.js \
browser_progress_indicator.xul \
browser_remotetabs.js \
browser_tabs.js \
browser_test.js \

Просмотреть файл

@ -0,0 +1,59 @@
/* 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/. */
let doc;
function test() {
waitForExplicitFinish();
Task.spawn(function(){
netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
info(chromeRoot + "browser_progress_indicator.xul");
yield addTab(chromeRoot + "browser_progress_indicator.xul");
doc = Browser.selectedTab.browser.contentWindow.document;
}).then(runTests);
}
gTests.push({
desc: "circular progress indicator binding is applied.",
run: function() {
ok(doc, "doc got defined");
let progressIndicator = doc.querySelector("#progress-indicator");
ok(progressIndicator, "progress-indicator is found");
is(typeof progressIndicator.reset, "function", "#progress-indicator has a reset() function");
is(typeof progressIndicator.updateProgress, "function", "#progress-indicator has a updateProgress() function");
}
});
gTests.push({
desc: "start and end angles are correct for various percents complete",
run: function() {
let progressIndicator = doc.querySelector("#progress-indicator");
ok(progressIndicator, "progress-indicator is found");
is(typeof progressIndicator.updateProgress, "function", "#progress-indicator has a updateProgress() function");
let expectedStartAngle = 1.5 * Math.PI;
let percentComplete = 0;
let [startAngle, endAngle] = progressIndicator.updateProgress(percentComplete);
is(startAngle, expectedStartAngle, "start angle is correct");
is(endAngle, startAngle + (2 * Math.PI * (percentComplete / 100)), "end angle is correct");
percentComplete = 0.05;
[startAngle, endAngle] = progressIndicator.updateProgress(percentComplete);
is(startAngle, expectedStartAngle, "start angle is correct");
is(endAngle, startAngle + (2 * Math.PI * (percentComplete / 100)), "end angle is correct");
percentComplete = 0.5;
[startAngle, endAngle] = progressIndicator.updateProgress(percentComplete);
is(startAngle, expectedStartAngle, "start angle is correct");
is(endAngle, startAngle + (2 * Math.PI * (percentComplete / 100)), "end angle is correct");
percentComplete = 1;
[startAngle, endAngle] = progressIndicator.updateProgress(percentComplete);
is(startAngle, expectedStartAngle, "start angle is correct");
is(endAngle, startAngle + (2 * Math.PI * (percentComplete / 100)), "end angle is correct");
}
});

Просмотреть файл

@ -331,4 +331,14 @@ gTests.push({
}
});
/**
* Make sure the cancelled/aborted downloads are handled correctly.
*/
gTests.push({
desc: "Cancel/Abort Downloads",
run: function(){
todo(false, "Ensure that a cancelled/aborted download is in the correct state \
including correct values for state variables (e.g. _downloadCount, _downloadsInProgress) \
and the existence of the downloaded file.");
}
});

Просмотреть файл

@ -0,0 +1,15 @@
<?xml version="1.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/. -->
<?xml-stylesheet href="chrome://browser/skin/platform.css" type="text/css"?>
<?xml-stylesheet href="chrome://browser/skin/browser.css" type="text/css"?>
<?xml-stylesheet href="chrome://browser/content/browser.css" type="text/css"?>
<!DOCTYPE window []>
<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
<circularprogressindicator id="progress-indicator" oncommand=""/>
</window>

Просмотреть файл

@ -444,6 +444,12 @@ documenttab[selected] .documenttab-selection {
/* Navigation bar ========================================================== */
.circularprogressindicator-progressRing {
margin: 0 @toolbar_horizontal_spacing@;
pointer-events:none;
position: absolute;
}
/* Progress meter ---------------------------------------------------------- */
#progress-container {
@ -689,13 +695,14 @@ documenttab[selected] .documenttab-selection {
pointer-events: none;
}
#download-button {
.circularprogressindicator-progressButton {
margin: 0 @toolbar_horizontal_spacing@;
-moz-image-region: rect(0px, 40px, 40px, 0px) !important;
}
#download-button:hover:not(:active) {
.circularprogressindicator-progressButton:hover {
-moz-image-region: rect(40px, 40px, 80px, 0px) !important;
}
#download-button:active {
.circularprogressindicator-progressButton:active {
-moz-image-region: rect(80px, 40px, 120px, 0px) !important;
}

Двоичные данные
browser/metro/theme/images/progresscircle.png Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 363 B

Просмотреть файл

@ -99,6 +99,7 @@ chrome.jar:
skin/images/tile-selected-check-hdpi.png (images/tile-selected-check-hdpi.png)
skin/images/plus-34.png (images/plus-34.png)
skin/images/plus-24.png (images/plus-24.png)
skin/images/progresscircle.png (images/progresscircle.png)
skin/images/overlay-back.png (images/overlay-back.png)
skin/images/overlay-plus.png (images/overlay-plus.png)

Просмотреть файл

@ -11,7 +11,7 @@
}
.theme-body {
color: hsl(210,100%,85%);
color: hsl(210,100%,85%) !important;
-moz-box-sizing: border-box;
}

Просмотреть файл

@ -11,7 +11,7 @@
}
.theme-body {
color: hsl(210,100%,85%);
color: hsl(210,100%,85%) !important;
-moz-box-sizing: border-box;
}

Просмотреть файл

@ -32,6 +32,10 @@
-moz-image-region: rect(0px, 16px, 16px, 0px);
}
#developer-toolbar-toolbox-button > label {
display: none;
}
#developer-toolbar-toolbox-button:hover {
-moz-image-region: rect(0px, 32px, 16px, 16px);
}

Просмотреть файл

@ -46,7 +46,7 @@
width: 24px;
height: 7px;
cursor: ns-resize;
transform: translate(12px, 12px);
transform: translate(-12px, 12px);
background-image: url("chrome://browser/skin/devtools/responsive-horizontal-resizer.png");
}

Просмотреть файл

@ -229,6 +229,7 @@
.jsterm-input-node {
background: -moz-image-rect(url("chrome://browser/skin/devtools/commandline-icon.png"), 0, 32, 16, 16) no-repeat;
background-position: 0%;
}
:-moz-any(.jsterm-input-node,

Просмотреть файл

@ -11,7 +11,7 @@
}
.theme-body {
color: hsl(210,100%,85%);
color: hsl(210,100%,85%) !important;
-moz-box-sizing: border-box;
}

Просмотреть файл

@ -1733,6 +1733,7 @@ abstract public class GeckoApp
protected int getSessionRestoreState(Bundle savedInstanceState) {
final SharedPreferences prefs = GeckoApp.getAppSharedPreferences();
int restoreMode = RESTORE_NONE;
boolean allowCrashRestore = true;
// If the version has changed, the user has done an upgrade, so restore
// previous tabs.
@ -1748,9 +1749,19 @@ abstract public class GeckoApp
});
restoreMode = RESTORE_NORMAL;
} else if (savedInstanceState != null ||
PreferenceManager.getDefaultSharedPreferences(this).getBoolean(GeckoPreferences.PREFS_RESTORE_SESSION, false)) {
} else if (savedInstanceState != null) {
restoreMode = RESTORE_NORMAL;
} else {
String restorePref = PreferenceManager.getDefaultSharedPreferences(this)
.getString(GeckoPreferences.PREFS_RESTORE_SESSION, "crash");
if ("always".equals(restorePref)) {
restoreMode = RESTORE_NORMAL;
} else {
restoreMode = RESTORE_NONE;
if ("never".equals(restorePref)) {
allowCrashRestore = false;
}
}
}
// We record crashes in the crash reporter. If sessionstore.js
@ -1767,7 +1778,9 @@ abstract public class GeckoApp
}
});
restoreMode = RESTORE_CRASH;
if (allowCrashRestore) {
restoreMode = RESTORE_CRASH;
}
}
return restoreMode;

Просмотреть файл

@ -77,7 +77,7 @@ public class GeckoPreferences
private static String PREFS_HEALTHREPORT_LINK = NON_PREF_PREFIX + "healthreport.link";
private static String PREFS_DEVTOOLS_REMOTE_ENABLED = "devtools.debugger.remote-enabled";
public static String PREFS_RESTORE_SESSION = NON_PREF_PREFIX + "restoreSession";
public static String PREFS_RESTORE_SESSION = NON_PREF_PREFIX + "restoreSession2";
// These values are chosen to be distinct from other Activity constants.
private static int REQUEST_CODE_PREF_SCREEN = 5;
@ -321,6 +321,15 @@ public class GeckoPreferences
return true;
}
});
} else if (PREFS_RESTORE_SESSION.equals(key)) {
// Set the summary string to the current entry. The summary
// for other list prefs will be set in the PrefsHelper
// callback, but since this pref doesn't live in Gecko, we
// need to handle it separately.
ListPreference listPref = (ListPreference) pref;
CharSequence selectedEntry = listPref.getEntry();
listPref.setSummary(selectedEntry);
continue;
}
// Some Preference UI elements are not actually preferences,
@ -452,6 +461,9 @@ public class GeckoPreferences
String prefName = preference.getKey();
if (PREFS_MP_ENABLED.equals(prefName)) {
showDialog((Boolean) newValue ? DIALOG_CREATE_MASTER_PASSWORD : DIALOG_REMOVE_MASTER_PASSWORD);
// We don't want the "use master password" pref to change until the
// user has gone through the dialog.
return false;
} else if (PREFS_MENU_CHAR_ENCODING.equals(prefName)) {
setCharEncodingState(((String) newValue).equals("true"));
@ -467,20 +479,16 @@ public class GeckoPreferences
// background uploader service, which will start or stop the
// repeated background upload attempts.
broadcastHealthReportUploadPref(GeckoAppShell.getContext(), ((Boolean) newValue).booleanValue());
return true;
} else if (PREFS_GEO_REPORTING.equals(prefName)) {
// Translate boolean value to int for geo reporting pref.
PrefsHelper.setPref(prefName, (Boolean) newValue ? 1 : 0);
return true;
} else if (PREFS_RESTORE_SESSION.equals(prefName)) {
// Do nothing else; the pref will be persisted in the shared prefs,
// and it will be read at startup in Java before a session restore.
return true;
newValue = ((Boolean) newValue) ? 1 : 0;
}
if (!TextUtils.isEmpty(prefName)) {
// Send Gecko-side pref changes to Gecko
if (!TextUtils.isEmpty(prefName) && !prefName.startsWith(NON_PREF_PREFIX)) {
PrefsHelper.setPref(prefName, newValue);
}
if (preference instanceof ListPreference) {
// We need to find the entry for the new value
int newIndex = ((ListPreference) preference).findIndexOfValue((String) newValue);
@ -493,6 +501,7 @@ public class GeckoPreferences
final FontSizePreference fontSizePref = (FontSizePreference) preference;
fontSizePref.setSummary(fontSizePref.getSavedFontSizeName());
}
return true;
}

Просмотреть файл

@ -217,6 +217,10 @@ public class ProfileInformationCache implements ProfileInformationProvider {
addons.put(id, new JSONObject(json));
}
public void removeAddon(String id) {
addons.remove(id);
}
/**
* Will throw if you haven't done a full update at least once.
*/

Просмотреть файл

@ -63,6 +63,7 @@ public class BrowserHealthRecorder implements GeckoEventListener {
private static final String PREF_BLOCKLIST_ENABLED = "extensions.blocklist.enabled";
private static final String EVENT_ADDONS_ALL = "Addons:All";
private static final String EVENT_ADDONS_CHANGE = "Addons:Change";
private static final String EVENT_ADDONS_UNINSTALLING = "Addons:Uninstalling";
private static final String EVENT_PREF_CHANGE = "Pref:Change";
// This is raised from Gecko. It avoids browser.js having to know about the
@ -138,6 +139,20 @@ public class BrowserHealthRecorder implements GeckoEventListener {
return new SessionInformation(wallStartTime, realStartTime, wasOOM, wasStopped);
}
/**
* Initialize a new SessionInformation instance to 'split' the current
* session.
*/
public static SessionInformation forRuntimeTransition() {
final boolean wasOOM = false;
final boolean wasStopped = true;
final long wallStartTime = System.currentTimeMillis();
final long realStartTime = android.os.SystemClock.elapsedRealtime();
Log.v(LOG_TAG, "Recording runtime session transition: " +
wallStartTime + ", " + realStartTime);
return new SessionInformation(wallStartTime, realStartTime, wasOOM, wasStopped);
}
public boolean wasKilled() {
return wasOOM || !wasStopped;
}
@ -285,6 +300,7 @@ public class BrowserHealthRecorder implements GeckoEventListener {
private void unregisterEventListeners() {
this.dispatcher.unregisterEventListener(EVENT_ADDONS_ALL, this);
this.dispatcher.unregisterEventListener(EVENT_ADDONS_CHANGE, this);
this.dispatcher.unregisterEventListener(EVENT_ADDONS_UNINSTALLING, this);
this.dispatcher.unregisterEventListener(EVENT_PREF_CHANGE, this);
this.dispatcher.unregisterEventListener(EVENT_KEYWORD_SEARCH, this);
this.dispatcher.unregisterEventListener(EVENT_SEARCH, this);
@ -309,17 +325,29 @@ public class BrowserHealthRecorder implements GeckoEventListener {
}
}
public void onAddonUninstalling(String id) {
this.profileCache.beginInitialization();
try {
this.profileCache.removeAddon(id);
} catch (IllegalStateException e) {
Log.w(LOG_TAG, "Attempted to update add-on cache prior to full init.", e);
}
}
/**
* Call this when a material change has occurred in the running environment,
* such that a new environment should be computed and prepared for use in
* future events.
* Call this when a material change might have occurred in the running
* environment, such that a new environment should be computed and prepared
* for use in future events.
*
* Invoke this method after calls that mutate the environment, such as
* {@link #onBlocklistPrefChanged(boolean)}.
*
* TODO: record a session transition with the new environment.
* If this change resulted in a transition between two environments, {@link
* #onEnvironmentTransition(int, int)} will be invoked on the background
* thread.
*/
public synchronized void onEnvironmentChanged() {
final int previousEnv = this.env;
this.env = -1;
try {
profileCache.completeInitialization();
@ -328,7 +356,24 @@ public class BrowserHealthRecorder implements GeckoEventListener {
this.state = State.INITIALIZATION_FAILED;
return;
}
ensureEnvironment();
final int updatedEnv = ensureEnvironment();
if (updatedEnv == -1 ||
updatedEnv == previousEnv) {
Log.v(LOG_TAG, "Environment didn't change.");
return;
}
ThreadUtils.postToBackgroundThread(new Runnable() {
@Override
public void run() {
try {
onEnvironmentTransition(previousEnv, updatedEnv);
} catch (Exception e) {
Log.w(LOG_TAG, "Could not record environment transition.", e);
}
}
});
}
protected synchronized int ensureEnvironment() {
@ -493,6 +538,7 @@ public class BrowserHealthRecorder implements GeckoEventListener {
try {
// Listen for add-ons and prefs changes.
dispatcher.registerEventListener(EVENT_ADDONS_UNINSTALLING, self);
dispatcher.registerEventListener(EVENT_ADDONS_CHANGE, self);
dispatcher.registerEventListener(EVENT_PREF_CHANGE, self);
@ -567,6 +613,27 @@ public class BrowserHealthRecorder implements GeckoEventListener {
Log.d(LOG_TAG, "Requested prefs.");
}
/**
* Invoked in the background whenever the environment transitions between
* two valid values.
*/
protected void onEnvironmentTransition(int prev, int env) {
if (this.state != State.INITIALIZED) {
Log.d(LOG_TAG, "Not initialized: not recording env transition (" + prev + " => " + env + ").");
return;
}
final SharedPreferences prefs = GeckoApp.getAppSharedPreferences();
final SharedPreferences.Editor editor = prefs.edit();
recordSessionEnd("E", editor, prev);
final SessionInformation newSession = SessionInformation.forRuntimeTransition();
setCurrentSession(newSession);
newSession.recordBegin(editor);
editor.commit();
}
@Override
public void handleMessage(String event, JSONObject message) {
try {
@ -591,12 +658,19 @@ public class BrowserHealthRecorder implements GeckoEventListener {
return;
}
if (EVENT_ADDONS_UNINSTALLING.equals(event)) {
this.onAddonUninstalling(message.getString("id"));
this.onEnvironmentChanged();
return;
}
if (EVENT_ADDONS_CHANGE.equals(event)) {
Log.d(LOG_TAG, "Add-on changed: " + message.getString("id"));
this.onAddonChanged(message.getString("id"), message.getJSONObject("json"));
this.onEnvironmentChanged();
return;
}
if (EVENT_PREF_CHANGE.equals(event)) {
final String pref = message.getString("pref");
Log.d(LOG_TAG, "Pref changed: " + pref);
@ -871,14 +945,21 @@ public class BrowserHealthRecorder implements GeckoEventListener {
/**
* Logic shared between crashed and normal sessions.
*/
private void recordSessionEntry(String field, SessionInformation session, JSONObject value) {
private void recordSessionEntry(String field, SessionInformation session, final int environment, JSONObject value) {
final HealthReportDatabaseStorage storage = this.storage;
if (storage == null) {
Log.d(LOG_TAG, "No storage: not recording session entry. Shutting down?");
return;
}
try {
final int sessionField = storage.getField(MEASUREMENT_NAME_SESSIONS,
MEASUREMENT_VERSION_SESSIONS,
field)
.getID();
final int day = storage.getDay(session.wallStartTime);
storage.recordDailyDiscrete(env, day, sessionField, value);
storage.recordDailyDiscrete(environment, day, sessionField, value);
Log.v(LOG_TAG, "Recorded session entry for env " + environment + ", current is " + env);
} catch (Exception e) {
Log.w(LOG_TAG, "Unable to record session completion.", e);
}
@ -905,7 +986,8 @@ public class BrowserHealthRecorder implements GeckoEventListener {
}
try {
recordSessionEntry("abnormal", this.previousSession, this.previousSession.getCrashedJSON());
recordSessionEntry("abnormal", this.previousSession, this.env,
this.previousSession.getCrashedJSON());
} catch (Exception e) {
Log.w(LOG_TAG, "Unable to generate session JSON.", e);
@ -913,10 +995,17 @@ public class BrowserHealthRecorder implements GeckoEventListener {
}
}
public void recordSessionEnd(String reason, SharedPreferences.Editor editor) {
recordSessionEnd(reason, editor, env);
}
/**
* Record that the current session ended. Does not commit the provided editor.
*
* @param environment An environment ID. This allows callers to record the
* end of a session due to an observed environment change.
*/
public void recordSessionEnd(String reason, SharedPreferences.Editor editor) {
public void recordSessionEnd(String reason, SharedPreferences.Editor editor, final int environment) {
Log.d(LOG_TAG, "Recording session end: " + reason);
if (state != State.INITIALIZED) {
// Something has gone awry.
@ -940,7 +1029,7 @@ public class BrowserHealthRecorder implements GeckoEventListener {
long realEndTime = android.os.SystemClock.elapsedRealtime();
try {
JSONObject json = session.getCompletionJSON(reason, realEndTime);
recordSessionEntry("normal", session, json);
recordSessionEntry("normal", session, environment, json);
} catch (JSONException e) {
Log.w(LOG_TAG, "Unable to generate session JSON.", e);

Просмотреть файл

@ -105,7 +105,10 @@
<!ENTITY pref_plugins_disabled "Disabled">
<!ENTITY pref_text_size "Text size">
<!ENTITY pref_reflow_on_zoom4 "Text reflow">
<!ENTITY pref_restore_session "Always restore tabs">
<!ENTITY pref_restore "Tabs">
<!ENTITY pref_restore_always "Always restore">
<!ENTITY pref_restore_crash "Restore after a crash">
<!ENTITY pref_restore_never "Never restore">
<!ENTITY pref_font_size_tiny "Tiny">
<!ENTITY pref_font_size_small "Small">
<!ENTITY pref_font_size_medium "Medium">

Просмотреть файл

@ -99,6 +99,16 @@
<item>private.data.offlineApps</item>
<item>private.data.siteSettings</item>
</string-array>
<string-array name="pref_restore_entries">
<item>@string/pref_restore_always</item>
<item>@string/pref_restore_crash</item>
<item>@string/pref_restore_never</item>
</string-array>
<string-array name="pref_restore_values">
<item>always</item>
<item>crash</item>
<item>never</item>
</string-array>
<string-array name="pref_update_autodownload_entries">
<item>@string/pref_update_autodownload_enabled</item>
<item>@string/pref_update_autodownload_wifi</item>

Просмотреть файл

@ -25,10 +25,12 @@
android:negativeButtonText="@string/button_cancel"
android:persistent="false" />
<CheckBoxPreference android:key="android.not_a_preference.restoreSession"
android:title="@string/pref_restore_session"
android:defaultValue="false"
android:persistent="true" />
<ListPreference android:key="android.not_a_preference.restoreSession2"
android:title="@string/pref_restore"
android:defaultValue="crash"
android:entries="@array/pref_restore_entries"
android:entryValues="@array/pref_restore_values"
android:persistent="true" />
<ListPreference android:key="app.update.autodownload"
android:title="@string/pref_update_autodownload"

Просмотреть файл

@ -31,10 +31,12 @@
android:negativeButtonText="@string/button_cancel"
android:persistent="false" />
<CheckBoxPreference android:key="android.not_a_preference.restoreSession"
android:title="@string/pref_restore_session"
android:defaultValue="false"
android:persistent="true" />
<ListPreference android:key="android.not_a_preference.restoreSession2"
android:title="@string/pref_restore"
android:defaultValue="crash"
android:entries="@array/pref_restore_entries"
android:entryValues="@array/pref_restore_values"
android:persistent="true" />
<ListPreference android:key="app.update.autodownload"
android:title="@string/pref_update_autodownload"

Просмотреть файл

@ -27,10 +27,12 @@
android:negativeButtonText="@string/button_cancel"
android:persistent="false" />
<CheckBoxPreference android:key="android.not_a_preference.restoreSession"
android:title="@string/pref_restore_session"
android:defaultValue="false"
android:persistent="true" />
<ListPreference android:key="android.not_a_preference.restoreSession2"
android:title="@string/pref_restore"
android:defaultValue="crash"
android:entries="@array/pref_restore_entries"
android:entryValues="@array/pref_restore_values"
android:persistent="true" />
<ListPreference android:key="app.update.autodownload"
android:title="@string/pref_update_autodownload"

Просмотреть файл

@ -125,7 +125,10 @@
<string name="pref_font_size_adjust_char">&pref_font_size_adjust_char;</string>
<string name="pref_font_size_preview_text">&pref_font_size_preview_text;</string>
<string name="pref_reflow_on_zoom">&pref_reflow_on_zoom4;</string>
<string name="pref_restore_session">&pref_restore_session;</string>
<string name="pref_restore">&pref_restore;</string>
<string name="pref_restore_always">&pref_restore_always;</string>
<string name="pref_restore_crash">&pref_restore_crash;</string>
<string name="pref_restore_never">&pref_restore_never;</string>
<string name="pref_show_product_announcements">&pref_show_product_announcements;</string>
<string name="pref_sync">&pref_sync;</string>
<string name="pref_search_suggestions">&pref_search_suggestions;</string>

Просмотреть файл

@ -26,7 +26,7 @@ public class testSettingsMenuItems extends PixelTest {
String[][] OPTIONS_CUSTOMIZE = {
{ "Search settings", "", "Show search suggestions", "Installed search engines" },
{ "Import from Android", "", "Bookmarks", "History", "Import" },
{ "Always restore tabs" },
{ "Tabs", "Restore after a crash", "Always restore", "Restore after a crash", "Never restore" },
{ "Automatic updates", "Only over Wi-Fi", "Enabled", "Only over Wi-Fi", "Disabled" },
};

Просмотреть файл

@ -5355,12 +5355,12 @@ let HealthReportStatusListener = {
return o;
},
notifyJava: function (aAddon, aNeedsRestart) {
notifyJava: function (aAddon, aNeedsRestart, aAction="Addons:Change") {
let json = this.jsonForAddon(aAddon);
if (this._shouldIgnore(aAddon)) {
json.ignore = true;
}
sendMessageToJava({ type: "Addons:Change", id: aAddon.id, json: json });
sendMessageToJava({ type: aAction, id: aAddon.id, json: json });
},
// Add-on listeners.
@ -5374,11 +5374,14 @@ let HealthReportStatusListener = {
this.notifyJava(aAddon, aNeedsRestart);
},
onUninstalling: function (aAddon, aNeedsRestart) {
this.notifyJava(aAddon, aNeedsRestart);
this.notifyJava(aAddon, aNeedsRestart, "Addons:Uninstalling");
},
onPropertyChanged: function (aAddon, aProperties) {
this.notifyJava(aAddon);
},
onOperationCancelled: function (aAddon) {
this.notifyJava(aAddon);
},
sendAllAddonsToJava: function () {
AddonManager.getAllAddons(function (aAddons) {

Просмотреть файл

@ -1200,12 +1200,14 @@ DownloadCopySaver.prototype = {
// If we have data that we can use to resume the download from where
// it stopped, try to use it.
let resumeAttempted = false;
let resumeFromBytes = 0;
if (channel instanceof Ci.nsIResumableChannel && this.entityID &&
partFilePath && keepPartialData) {
try {
let stat = yield OS.File.stat(partFilePath);
channel.resumeAt(stat.size, this.entityID);
resumeAttempted = true;
resumeFromBytes = stat.size;
} catch (ex if ex instanceof OS.File.Error &&
ex.becauseNoSuchFile) { }
}
@ -1216,7 +1218,10 @@ DownloadCopySaver.prototype = {
onProgress: function DCSE_onProgress(aRequest, aContext, aProgress,
aProgressMax)
{
aSetProgressBytesFn(aProgress, aProgressMax, aProgress > 0 &&
let currentBytes = resumeFromBytes + aProgress;
let totalBytes = aProgressMax == -1 ? -1 : (resumeFromBytes +
aProgressMax);
aSetProgressBytesFn(currentBytes, totalBytes, aProgress > 0 &&
partFilePath && keepPartialData);
},
onStatus: function () { },

Просмотреть файл

@ -222,6 +222,8 @@ DownloadLegacyTransfer.prototype = {
}.bind(this)).then(null, Cu.reportError);
},
setSha256Hash: function () { },
//////////////////////////////////////////////////////////////////////////////
//// Private methods and properties

Просмотреть файл

@ -644,9 +644,30 @@ add_task(function test_cancel_midway_restart_tryToKeepPartialData()
// Verify that the server sent the response from the start.
do_check_eq(gMostRecentFirstBytePos, 0);
// The second time, we'll request and obtain the second part of the response.
// The second time, we'll request and obtain the second part of the response,
// but we still stop when half of the remaining progress is reached.
let deferMidway = Promise.defer();
download.onchange = function () {
if (!download.stopped && !download.canceled &&
download.currentBytes == Math.floor(TEST_DATA_SHORT.length * 3 / 2)) {
download.onchange = null;
deferMidway.resolve();
}
};
mustInterruptResponses();
let promiseAttempt = download.start();
// Continue when the number of bytes we received is correct, then check that
// progress is at about 75 percent. The exact figure may vary because of
// rounding issues, since the total number of bytes in the response might not
// be a multiple of four.
yield deferMidway.promise;
do_check_true(download.progress > 72 && download.progress < 78);
// Now we allow the download to finish.
continueResponses();
yield download.start();
yield promiseAttempt;
// Check that the server now sent the second part only.
do_check_eq(gMostRecentFirstBytePos, TEST_DATA_SHORT.length);

Просмотреть файл

@ -141,16 +141,16 @@ FrameWorker.prototype = {
'clearInterval', 'clearTimeout', 'dump',
'setInterval', 'setTimeout', 'XMLHttpRequest',
'FileReader', 'Blob', 'EventSource', 'indexedDB',
'location'];
'location', 'Worker'];
// Only expose localStorage if the caller opted-in
if (this.exposeLocalStorage) {
workerAPI.push('localStorage');
}
// Bug 798660 - XHR and WebSocket have issues in a sandbox and need
// Bug 798660 - XHR, WebSocket and Worker have issues in a sandbox and need
// to be unwrapped to work
let needsWaive = ['XMLHttpRequest', 'WebSocket'];
let needsWaive = ['XMLHttpRequest', 'WebSocket', 'Worker'];
// Methods need to be bound with the proper |this|.
let needsBind = ['atob', 'btoa', 'dump', 'setInterval', 'clearInterval',
'setTimeout', 'clearTimeout'];

Просмотреть файл

@ -5,7 +5,7 @@
function makeWorkerUrl(runner) {
let prefix = "http://example.com/browser/toolkit/components/social/test/browser/echo.sjs?";
if (typeof runner == "function") {
runner = "let run=" + runner.toSource() + ";run();";
runner = "var run=" + runner.toSource() + ";run();";
}
return prefix + encodeURI(runner);
}
@ -694,4 +694,35 @@ let tests = {
}
worker.port.postMessage({topic: "test-indexeddb-create"})
},
testSubworker: function(cbnext) {
// the main "frameworker"...
let mainworker = function() {
onconnect = function(e) {
let port = e.ports[0];
port.onmessage = function(e) {
if (e.data.topic == "go") {
let suburl = e.data.data;
let worker = new Worker(suburl);
worker.onmessage = function(sube) {
port.postMessage({topic: "sub-message", data: sube.data});
}
}
}
}
}
// The "subworker" that is actually a real, bona-fide worker.
let subworker = function() {
postMessage("hello");
}
let worker = getFrameWorkerHandle(makeWorkerUrl(mainworker), undefined, "testSubWorker");
worker.port.onmessage = function(e) {
if (e.data.topic == "sub-message" && e.data.data == "hello") {
worker.terminate();
cbnext();
}
}
worker.port.postMessage({topic: "go", data: makeWorkerUrl(subworker)});
}
}

Просмотреть файл

@ -614,7 +614,7 @@ const OPEN_CLOSE_BODY = {
"(": ")",
};
const MAX_COMPLETIONS = 256;
const MAX_COMPLETIONS = 1500;
/**
* Analyses a given string to find the last statement that is interesting for

Просмотреть файл

@ -23,7 +23,7 @@ NS_IMETHODIMP nsMacWebAppUtils::PathForAppWithIdentifier(const nsAString& bundle
outPath.Truncate();
NSAutoreleasePool* ap = [[NSAutoreleasePool alloc] init];
nsAutoreleasePool localPool;
//note that the result of this expression might be nil, meaning no matching app was found.
NSString* temp = [[NSWorkspace sharedWorkspace] absolutePathForAppBundleWithIdentifier:
@ -34,7 +34,6 @@ NS_IMETHODIMP nsMacWebAppUtils::PathForAppWithIdentifier(const nsAString& bundle
nsCocoaUtils::GetStringForNSString(temp, outPath);
}
[ap release];
return NS_OK;
NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
@ -43,7 +42,7 @@ NS_IMETHODIMP nsMacWebAppUtils::PathForAppWithIdentifier(const nsAString& bundle
NS_IMETHODIMP nsMacWebAppUtils::LaunchAppWithIdentifier(const nsAString& bundleIdentifier) {
NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
NSAutoreleasePool* ap = [[NSAutoreleasePool alloc] init];
nsAutoreleasePool localPool;
// Note this might return false, meaning the app wasnt launched for some reason.
BOOL success = [[NSWorkspace sharedWorkspace] launchAppWithBundleIdentifier:
@ -52,9 +51,7 @@ NS_IMETHODIMP nsMacWebAppUtils::LaunchAppWithIdentifier(const nsAString& bundleI
additionalEventParamDescriptor: nil
launchIdentifier: NULL];
[ap release];
return NS_OK;
return success ? NS_OK : NS_ERROR_FAILURE;
NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
}