This commit is contained in:
Wes Kocher 2014-02-13 17:28:56 -08:00
Родитель 7782444919 53ceaac97e
Коммит b921abacaf
62 изменённых файлов: 1106 добавлений и 449 удалений

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

@ -246,6 +246,7 @@ skip-if = os == "mac" # Intermittent failures, bug 925225
[browser_clearplugindata.js]
[browser_contentAreaClick.js]
[browser_contextSearchTabPosition.js]
skip-if = os == "mac" # bug 967013, bug 926729
[browser_ctrlTab.js]
[browser_customize_popupNotification.js]
[browser_datareporting_notification.js]
@ -305,6 +306,7 @@ skip-if = true # disabled until the tree view is added
# it ever is (bug 480169)
[browser_save_link-perwindowpb.js]
[browser_save_private_link_perwindowpb.js]
skip-if = os == "linux" # bug 857427
[browser_save_video.js]
[browser_scope.js]
[browser_selectTabAtIndex.js]

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

@ -112,13 +112,13 @@
<toolbarseparator/>
<toolbarbutton id="panelMenu_bookmarksToolbar"
label="&personalbarCmd.label;"
class="subviewbutton"
class="subviewbutton cui-withicon"
oncommand="PlacesCommandHook.showPlacesOrganizer('BookmarksToolbar'); PanelUI.hide();"/>
<toolbarbutton id="panelMenu_unsortedBookmarks"
label="&unsortedBookmarksCmd.label;"
class="subviewbutton"
class="subviewbutton cui-withicon"
oncommand="PlacesCommandHook.showPlacesOrganizer('UnfiledBookmarks'); PanelUI.hide();"/>
<toolbarseparator/>
<toolbarseparator class="small-separator"/>
<toolbaritem id="panelMenu_bookmarksMenu"
orient="vertical"
smoothscroll="false"

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

@ -662,6 +662,7 @@ const CustomizableWidgets = [{
for (let item of list) {
let elem = aDocument.createElementNS(kNSXUL, "toolbarbutton");
elem.setAttribute("label", item.label);
elem.setAttribute("type", "checkbox");
elem.section = aSection == "detectors" ? "detectors" : "charsets";
elem.value = item.id;
elem.setAttribute("class", "subviewbutton");
@ -706,9 +707,9 @@ const CustomizableWidgets = [{
elem.removeAttribute("disabled");
}
if (elem.value.toLowerCase() == aCurrentItem.toLowerCase()) {
elem.setAttribute("current", "true");
elem.setAttribute("checked", "true");
} else {
elem.removeAttribute("current");
elem.removeAttribute("checked");
}
}
},

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

@ -1122,8 +1122,10 @@ CustomizeMode.prototype = {
DragPositionManager.start(this.window);
if (item.nextSibling) {
this._setDragActive(item.nextSibling, "before", draggedItem.id, isInToolbar);
this._dragOverItem = item.nextSibling;
} else if (isInToolbar && item.previousSibling) {
this._setDragActive(item.previousSibling, "after", draggedItem.id, isInToolbar);
this._dragOverItem = item.previousSibling;
}
}
this._initializeDragAfterMove = null;

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

@ -293,6 +293,7 @@ PlacesViewBase.prototype = {
let type = aPlacesNode.type;
if (type == Ci.nsINavHistoryResultNode.RESULT_TYPE_SEPARATOR) {
element = document.createElement("menuseparator");
element.setAttribute("class", "small-separator");
}
else {
let itemId = aPlacesNode.itemId;
@ -1808,6 +1809,7 @@ PlacesPanelMenuView.prototype = {
let button;
if (type == Ci.nsINavHistoryResultNode.RESULT_TYPE_SEPARATOR) {
button = document.createElement("toolbarseparator");
button.setAttribute("class", "small-separator");
}
else {
button = document.createElement("toolbarbutton");

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

@ -489,7 +489,7 @@
<!-- Most of this is copied from the arrowpanel binding in popup.xml -->
<binding id="places-popup-arrow"
extends="chrome://browser/content/places/menu.xml#places-popup-base">
<content flip="both" side="top" position="bottomcenter topleft">
<content flip="both" side="top" position="bottomcenter topright">
<xul:vbox anonid="container" class="panel-arrowcontainer" flex="1"
xbl:inherits="side,panelopen">
<xul:box anonid="arrowbox" class="panel-arrowbox">

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

@ -57,6 +57,9 @@ const EVENTS = {
// When the response body is displayed in the UI.
RESPONSE_BODY_DISPLAYED: "NetMonitor:ResponseBodyAvailable",
// When the html response preview is displayed in the UI.
RESPONSE_HTML_PREVIEW_DISPLAYED: "NetMonitor:ResponseHtmlPreviewAvailable",
// When `onTabSelect` is fired and subsequently rendered.
TAB_UPDATED: "NetMonitor:TabUpdated",
@ -412,8 +415,7 @@ TargetEventsHandler.prototype = {
case "will-navigate": {
// Reset UI.
NetMonitorView.RequestsMenu.reset();
NetMonitorView.Sidebar.reset();
NetMonitorView.NetworkDetails.reset();
NetMonitorView.Sidebar.toggle(false);
// Switch to the default network traffic inspector view.
if (NetMonitorController.getCurrentActivity() == ACTIVITY_TYPE.NONE) {

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

@ -55,8 +55,7 @@ const GENERIC_VARIABLES_VIEW_SETTINGS = {
editableNameTooltip: "",
preventDisableOnChange: true,
preventDescriptorModifiers: true,
eval: () => {},
switch: () => {}
eval: () => {}
};
const NETWORK_ANALYSIS_PIE_CHART_DIAMETER = 200; // px
@ -1051,6 +1050,9 @@ RequestsMenuView.prototype = Heritage.extend(WidgetMethods, {
this.filterContents();
this.refreshSummary();
this.refreshZebra();
// Rescale all the waterfalls so that everything is visible at once.
this._flushWaterfallViews();
},
/**
@ -1185,14 +1187,6 @@ RequestsMenuView.prototype = Heritage.extend(WidgetMethods, {
timingsNode.insertBefore(timingBox, timingsTotal);
}
}
// Don't paint things while the waterfall view isn't even visible.
if (NetMonitorView.currentFrontendMode != "network-inspector-view") {
return;
}
// Rescale all the waterfalls so that everything is visible at once.
this._flushWaterfallViews();
},
/**
@ -1202,6 +1196,12 @@ RequestsMenuView.prototype = Heritage.extend(WidgetMethods, {
* True if this container's width was changed.
*/
_flushWaterfallViews: function(aReset) {
// Don't paint things while the waterfall view isn't even visible,
// or there are no items added to this container.
if (NetMonitorView.currentFrontendMode != "network-inspector-view" || !this.itemCount) {
return;
}
// To avoid expensive operations like getBoundingClientRect() and
// rebuilding the waterfall background each time a new request comes in,
// stuff is cached. However, in certain scenarios like when the window
@ -1429,11 +1429,6 @@ RequestsMenuView.prototype = Heritage.extend(WidgetMethods, {
* The resize listener for this container's window.
*/
_onResize: function(e) {
// Don't paint things while the waterfall view isn't even visible.
if (NetMonitorView.currentFrontendMode != "network-inspector-view") {
return;
}
// Allow requests to settle down first.
setNamedTimeout(
"resize-events", RESIZE_REFRESH_RATE, () => this._flushWaterfallViews(true));
@ -1634,13 +1629,6 @@ SidebarView.prototype = {
$("#details-pane").selectedIndex = isCustom ? 0 : 1
window.emit(EVENTS.SIDEBAR_POPULATED);
});
},
/**
* Hides this container.
*/
reset: function() {
this.toggle(false);
}
}
@ -1837,13 +1825,6 @@ NetworkDetailsView.prototype = {
dumpn("Destroying the NetworkDetailsView");
},
/**
* Resets this container (removes all the networking information).
*/
reset: function() {
this._dataSrc = null;
},
/**
* Populates this view with the specified data.
*
@ -1861,6 +1842,18 @@ NetworkDetailsView.prototype = {
$("#response-content-textarea-box").hidden = true;
$("#response-content-image-box").hidden = true;
let isHtml = RequestsMenuView.prototype.isHtml({ attachment: aData });
// Show the "Preview" tabpanel only for plain HTML responses.
$("#preview-tab").hidden = !isHtml;
$("#preview-tabpanel").hidden = !isHtml;
// Switch to the "Headers" tabpanel if the "Preview" previously selected
// and this is not an HTML response.
if (!isHtml && this.widget.selectedIndex == 5) {
this.widget.selectedIndex = 0;
}
this._headers.empty();
this._cookies.empty();
this._params.empty();
@ -1907,6 +1900,9 @@ NetworkDetailsView.prototype = {
case 4: // "Timings"
yield view._setTimingsInformation(src.eventTimings);
break;
case 5: // "Preview"
yield view._setHtmlPreview(src.responseContent);
break;
}
populated[tab] = true;
window.emit(EVENTS.TAB_UPDATED);
@ -2104,32 +2100,45 @@ NetworkDetailsView.prototype = {
if (!aHeadersResponse || !aPostDataResponse) {
return promise.resolve();
}
return gNetwork.getString(aPostDataResponse.postData.text).then(aString => {
// Handle query strings (poor man's forms, e.g. "?foo=bar&baz=42").
let cType = aHeadersResponse.headers.filter(({ name }) => name == "Content-Type")[0];
let cString = cType ? cType.value : "";
if (cString.contains("x-www-form-urlencoded") ||
aString.contains("x-www-form-urlencoded")) {
let formDataGroups = aString.split(/\r\n|\n|\r/);
for (let group of formDataGroups) {
this._addParams(this._paramsFormData, group);
}
}
// Handle actual forms ("multipart/form-data" content type).
else {
// This is really awkward, but hey, it works. Let's show an empty
// scope in the params view and place the source editor containing
// the raw post data directly underneath.
$("#request-params-box").removeAttribute("flex");
let paramsScope = this._params.addScope(this._paramsPostPayload);
paramsScope.expanded = true;
paramsScope.locked = true;
return gNetwork.getString(aPostDataResponse.postData.text).then(aPostData => {
let contentTypeHeader = aHeadersResponse.headers.filter(({ name }) => name == "Content-Type")[0];
let contentTypeLongString = contentTypeHeader ? contentTypeHeader.value : "";
$("#request-post-data-textarea-box").hidden = false;
return NetMonitorView.editor("#request-post-data-textarea").then(aEditor => {
aEditor.setText(aString);
});
}
return gNetwork.getString(contentTypeLongString).then(aContentType => {
let urlencoded = "x-www-form-urlencoded";
// Handle query strings (poor man's forms, e.g. "?foo=bar&baz=42").
if (aContentType.contains(urlencoded)) {
let formDataGroups = aPostData.split(/\r\n|\r|\n/);
for (let group of formDataGroups) {
this._addParams(this._paramsFormData, group);
}
}
// Handle actual forms ("multipart/form-data" content type).
else {
// This is really awkward, but hey, it works. Let's show an empty
// scope in the params view and place the source editor containing
// the raw post data directly underneath.
$("#request-params-box").removeAttribute("flex");
let paramsScope = this._params.addScope(this._paramsPostPayload);
paramsScope.expanded = true;
paramsScope.locked = true;
$("#request-post-data-textarea-box").hidden = false;
return NetMonitorView.editor("#request-post-data-textarea").then(aEditor => {
// Most POST bodies are usually JSON, so they can be neatly
// syntax highlighted as JS. Otheriwse, fall back to plain text.
try {
JSON.parse(aPostData);
aEditor.setMode(Editor.modes.js);
} catch (e) {
aEditor.setMode(Editor.modes.text);
} finally {
aEditor.setText(aPostData);
}
});
}
});
}).then(() => window.emit(EVENTS.REQUEST_POST_PARAMS_DISPLAYED));
},
@ -2150,8 +2159,8 @@ NetworkDetailsView.prototype = {
paramsScope.expanded = true;
for (let param of paramsArray) {
let headerVar = paramsScope.addItem(param.name, {}, true);
headerVar.setGrip(param.value);
let paramVar = paramsScope.addItem(param.name, {}, true);
paramVar.setGrip(param.value);
}
},
@ -2345,6 +2354,30 @@ NetworkDetailsView.prototype = {
.style.transform = "translateX(" + (scale * (blocked + dns + connect + send + wait)) + "px)";
},
/**
* Sets the preview for HTML responses shown in this view.
*
* @param object aResponse
* The message received from the server.
* @return object
* A promise that is resolved when the response body is set
*/
_setHtmlPreview: function(aResponse) {
if (!aResponse) {
return promise.resolve();
}
let { text } = aResponse.content;
let iframe = $("#response-preview");
return gNetwork.getString(text).then(aString => {
// Always disable JS when previewing HTML responses.
iframe.contentDocument.docShell.allowJavascript = false;
iframe.contentDocument.documentElement.innerHTML = aString;
window.emit(EVENTS.RESPONSE_HTML_PREVIEW_DISPLAYED);
});
},
_dataSrc: null,
_headers: null,
_cookies: null,
@ -2570,14 +2603,16 @@ nsIURL.store = new Map();
*/
function parseQueryString(aQueryString) {
// Make sure there's at least one param available.
if (!aQueryString || !aQueryString.contains("=")) {
// Be careful here, params don't necessarily need to have values, so
// no need to verify the existence of a "=".
if (!aQueryString) {
return;
}
// Turn the params string into an array containing { name: value } tuples.
let paramsArray = aQueryString.replace(/^[?&]/, "").split("&").map(e =>
let (param = e.split("=")) {
name: NetworkHelper.convertToUnicode(unescape(param[0])),
value: NetworkHelper.convertToUnicode(unescape(param[1]))
name: param[0] ? NetworkHelper.convertToUnicode(unescape(param[0])) : "",
value: param[1] ? NetworkHelper.convertToUnicode(unescape(param[1])) : ""
});
return paramsArray;
}

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

@ -13,7 +13,8 @@
%netmonitorDTD;
]>
<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
xmlns:html="http://www.w3.org/1999/xhtml">
<script type="application/javascript;version=1.8"
src="chrome://browser/content/devtools/theme-switching.js"/>
@ -238,14 +239,21 @@
class="devtools-sidebar-tabs"
handleCtrlTab="false">
<tabs>
<tab label="&netmonitorUI.tab.headers;"/>
<tab label="&netmonitorUI.tab.cookies;"/>
<tab label="&netmonitorUI.tab.params;"/>
<tab label="&netmonitorUI.tab.response;"/>
<tab label="&netmonitorUI.tab.timings;"/>
<tab id="headers-tab"
label="&netmonitorUI.tab.headers;"/>
<tab id="cookies-tab"
label="&netmonitorUI.tab.cookies;"/>
<tab id="params-tab"
label="&netmonitorUI.tab.params;"/>
<tab id="response-tab"
label="&netmonitorUI.tab.response;"/>
<tab id="timings-tab"
label="&netmonitorUI.tab.timings;"/>
<tab id="preview-tab"
label="&netmonitorUI.tab.preview;"/>
</tabs>
<tabpanels flex="1">
<tabpanel id="headers-tabppanel"
<tabpanel id="headers-tabpanel"
class="tabpanel-content">
<vbox flex="1">
<hbox id="headers-summary-url"
@ -413,6 +421,12 @@
</hbox>
</vbox>
</tabpanel>
<tabpanel id="preview-tabpanel"
class="tabpanel-content">
<html:iframe id="response-preview"
frameborder="0"
sandbox=""/>
</tabpanel>
</tabpanels>
</tabbox>
</deck>

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

@ -13,6 +13,7 @@ support-files =
html_json-text-mime-test-page.html
html_jsonp-test-page.html
html_navigate-test-page.html
html_params-test-page.html
html_post-data-test-page.html
html_post-raw-test-page.html
html_simple-test-page.html
@ -35,15 +36,17 @@ support-files =
[browser_net_charts-04.js]
[browser_net_charts-05.js]
[browser_net_clear.js]
[browser_net_complex-params.js]
[browser_net_content-type.js]
[browser_net_copy_url.js]
[browser_net_copy_image_as_data_uri.js]
[browser_net_copy_url.js]
[browser_net_cyrillic-01.js]
[browser_net_cyrillic-02.js]
[browser_net_filter-01.js]
[browser_net_filter-02.js]
[browser_net_filter-03.js]
[browser_net_footer-summary.js]
[browser_net_html-preview.js]
[browser_net_json-long.js]
[browser_net_json-malformed.js]
[browser_net_json_custom_mime.js]

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

@ -0,0 +1,148 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* Tests whether complex request params and payload sent via POST are
* displayed correctly.
*/
function test() {
initNetMonitor(PARAMS_URL).then(([aTab, aDebuggee, aMonitor]) => {
info("Starting test... ");
let { document, L10N, EVENTS, Editor, NetMonitorView } = aMonitor.panelWin;
let { RequestsMenu, NetworkDetails } = NetMonitorView;
RequestsMenu.lazyUpdate = false;
NetworkDetails._params.lazyEmpty = false;
Task.spawn(function () {
yield waitForNetworkEvents(aMonitor, 0, 6);
EventUtils.sendMouseEvent({ type: "mousedown" },
document.getElementById("details-pane-toggle"));
EventUtils.sendMouseEvent({ type: "mousedown" },
document.querySelectorAll("#details-pane tab")[2]);
yield waitFor(aMonitor.panelWin, EVENTS.REQUEST_POST_PARAMS_DISPLAYED);
yield testParamsTab1('a', '""', '{ "foo": "bar" }', '""');
RequestsMenu.selectedIndex = 1;
yield waitFor(aMonitor.panelWin, EVENTS.REQUEST_POST_PARAMS_DISPLAYED);
yield testParamsTab1('a', '"b"', '{ "foo": "bar" }', '""');
RequestsMenu.selectedIndex = 2;
yield waitFor(aMonitor.panelWin, EVENTS.REQUEST_POST_PARAMS_DISPLAYED);
yield testParamsTab1('a', '"b"', 'foo', '"bar"');
RequestsMenu.selectedIndex = 3;
yield waitFor(aMonitor.panelWin, EVENTS.REQUEST_POST_PARAMS_DISPLAYED);
yield testParamsTab2('a', '""', '{ "foo": "bar" }', "js");
RequestsMenu.selectedIndex = 4;
yield waitFor(aMonitor.panelWin, EVENTS.REQUEST_POST_PARAMS_DISPLAYED);
yield testParamsTab2('a', '"b"', '{ "foo": "bar" }', "js");
RequestsMenu.selectedIndex = 5;
yield waitFor(aMonitor.panelWin, EVENTS.REQUEST_POST_PARAMS_DISPLAYED);
yield testParamsTab2('a', '"b"', '?foo=bar', "text");
yield teardown(aMonitor);
finish();
});
function testParamsTab1(
aQueryStringParamName, aQueryStringParamValue, aFormDataParamName, aFormDataParamValue)
{
let tab = document.querySelectorAll("#details-pane tab")[2];
let tabpanel = document.querySelectorAll("#details-pane tabpanel")[2];
is(tabpanel.querySelectorAll(".variables-view-scope").length, 2,
"The number of param scopes displayed in this tabpanel is incorrect.");
is(tabpanel.querySelectorAll(".variable-or-property").length, 2,
"The number of param values displayed in this tabpanel is incorrect.");
is(tabpanel.querySelectorAll(".variables-view-empty-notice").length, 0,
"The empty notice should not be displayed in this tabpanel.");
is(tabpanel.querySelector("#request-params-box")
.hasAttribute("hidden"), false,
"The request params box should not be hidden.");
is(tabpanel.querySelector("#request-post-data-textarea-box")
.hasAttribute("hidden"), true,
"The request post data textarea box should be hidden.");
let paramsScope = tabpanel.querySelectorAll(".variables-view-scope")[0];
let formDataScope = tabpanel.querySelectorAll(".variables-view-scope")[1];
is(paramsScope.querySelector(".name").getAttribute("value"),
L10N.getStr("paramsQueryString"),
"The params scope doesn't have the correct title.");
is(formDataScope.querySelector(".name").getAttribute("value"),
L10N.getStr("paramsFormData"),
"The form data scope doesn't have the correct title.");
is(paramsScope.querySelectorAll(".variables-view-variable .name")[0].getAttribute("value"),
aQueryStringParamName,
"The first query string param name was incorrect.");
is(paramsScope.querySelectorAll(".variables-view-variable .value")[0].getAttribute("value"),
aQueryStringParamValue,
"The first query string param value was incorrect.");
is(formDataScope.querySelectorAll(".variables-view-variable .name")[0].getAttribute("value"),
aFormDataParamName,
"The first form data param name was incorrect.");
is(formDataScope.querySelectorAll(".variables-view-variable .value")[0].getAttribute("value"),
aFormDataParamValue,
"The first form data param value was incorrect.");
}
function testParamsTab2(
aQueryStringParamName, aQueryStringParamValue, aRequestPayload, aEditorMode)
{
let tab = document.querySelectorAll("#details-pane tab")[2];
let tabpanel = document.querySelectorAll("#details-pane tabpanel")[2];
is(tabpanel.querySelectorAll(".variables-view-scope").length, 2,
"The number of param scopes displayed in this tabpanel is incorrect.");
is(tabpanel.querySelectorAll(".variable-or-property").length, 1,
"The number of param values displayed in this tabpanel is incorrect.");
is(tabpanel.querySelectorAll(".variables-view-empty-notice").length, 0,
"The empty notice should not be displayed in this tabpanel.");
is(tabpanel.querySelector("#request-params-box")
.hasAttribute("hidden"), false,
"The request params box should not be hidden.");
is(tabpanel.querySelector("#request-post-data-textarea-box")
.hasAttribute("hidden"), false,
"The request post data textarea box should not be hidden.");
let paramsScope = tabpanel.querySelectorAll(".variables-view-scope")[0];
let payloadScope = tabpanel.querySelectorAll(".variables-view-scope")[1];
is(paramsScope.querySelector(".name").getAttribute("value"),
L10N.getStr("paramsQueryString"),
"The params scope doesn't have the correct title.");
is(payloadScope.querySelector(".name").getAttribute("value"),
L10N.getStr("paramsPostPayload"),
"The request payload scope doesn't have the correct title.");
is(paramsScope.querySelectorAll(".variables-view-variable .name")[0].getAttribute("value"),
aQueryStringParamName,
"The first query string param name was incorrect.");
is(paramsScope.querySelectorAll(".variables-view-variable .value")[0].getAttribute("value"),
aQueryStringParamValue,
"The first query string param value was incorrect.");
return NetMonitorView.editor("#request-post-data-textarea").then((aEditor) => {
is(aEditor.getText(), aRequestPayload,
"The text shown in the source editor is incorrect.");
is(aEditor.getMode(), Editor.modes[aEditorMode],
"The mode active in the source editor is incorrect.");
teardown(aMonitor).then(finish);
});
}
aDebuggee.performRequests();
});
}

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

@ -6,7 +6,7 @@
*/
function test() {
initNetMonitor(CONTENT_TYPE_URL).then(([aTab, aDebuggee, aMonitor]) => {
initNetMonitor(CONTENT_TYPE_WITHOUT_CACHE_URL).then(([aTab, aDebuggee, aMonitor]) => {
info("Starting test... ");
let { document, L10N, Editor, NetMonitorView } = aMonitor.panelWin;
@ -62,6 +62,7 @@ function test() {
});
verifyRequestItemTarget(RequestsMenu.getItemAtIndex(5),
"GET", TEST_IMAGE, {
fuzzyUrl: true,
status: 200,
statusText: "OK",
type: "png",

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

@ -11,7 +11,9 @@ function test() {
let { NetMonitorView } = aMonitor.panelWin;
let { RequestsMenu } = NetMonitorView;
RequestsMenu.lazyUpdate = false;
let imageDataUri = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAHWSURBVHjaYvz//z8DJQAggJiQOe/fv2fv7Oz8rays/N+VkfG/iYnJfyD/1+rVq7ffu3dPFpsBAAHEAHIBCJ85c8bN2Nj4vwsDw/8zQLwKiO8CcRoQu0DxqlWrdsHUwzBAAIGJmTNnPgYa9j8UqhFElwPxf2MIDeIrKSn9FwSJoRkAEEAM0DD4DzMAyPi/G+QKY4hh5WAXGf8PDQ0FGwJ22d27CjADAAIIrLmjo+MXA9R2kAHvGBA2wwx6B8W7od6CeQcggKCmCEL8bgwxYCbUIGTDVkHDBia+CuotgACCueD3TDQN75D4xmAvCoK9ARMHBzAw0AECiBHkAlC0Mdy7x9ABNA3obAZXIAa6iKEcGlMVQHwWyjYuL2d4v2cPg8vZswx7gHyAAAK7AOif7SAbOqCmn4Ha3AHFsIDtgPq/vLz8P4MSkJ2W9h8ggBjevXvHDo4FQUQg/kdypqCg4H8lUIACnQ/SOBMYI8bAsAJFPcj1AAEEjwVQqLpAbXmH5BJjqI0gi9DTAAgDBBCcAVLkgmQ7yKCZxpCQxqUZhAECCJ4XgMl493ug21ZD+aDAXH0WLM4A9MZPXJkJIIAwTAR5pQMalaCABQUULttBGCCAGCnNzgABBgAMJ5THwGvJLAAAAABJRU5ErkJggg==";
waitForNetworkEvents(aMonitor, 6).then(() => {

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

@ -0,0 +1,63 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* Tests if html responses show and properly populate a "Preview" tab.
*/
function test() {
initNetMonitor(CONTENT_TYPE_URL).then(([aTab, aDebuggee, aMonitor]) => {
info("Starting test... ");
let { $, document, NetMonitorView } = aMonitor.panelWin;
let { RequestsMenu } = NetMonitorView;
RequestsMenu.lazyUpdate = false;
waitForNetworkEvents(aMonitor, 6).then(() => {
EventUtils.sendMouseEvent({ type: "mousedown" },
document.getElementById("details-pane-toggle"));
is($("#event-details-pane").selectedIndex, 0,
"The first tab in the details pane should be selected.");
is($("#preview-tab").hidden, true,
"The preview tab should be hidden for non html responses.");
is($("#preview-tabpanel").hidden, true,
"The preview tabpanel should be hidden for non html responses.");
RequestsMenu.selectedIndex = 4;
NetMonitorView.toggleDetailsPane({ visible: true, animated: false }, 5);
is($("#event-details-pane").selectedIndex, 5,
"The fifth tab in the details pane should be selected.");
is($("#preview-tab").hidden, false,
"The preview tab should be visible now.");
is($("#preview-tabpanel").hidden, false,
"The preview tabpanel should be visible now.");
let RESPONSE_HTML_PREVIEW_DISPLAYED = aMonitor.panelWin.EVENTS.RESPONSE_HTML_PREVIEW_DISPLAYED;
waitFor(aMonitor.panelWin, RESPONSE_HTML_PREVIEW_DISPLAYED).then(() => {
let iframe = $("#response-preview");
ok(iframe,
"There should be a response preview iframe available.");
ok(iframe.contentDocument,
"The iframe's content document should be available.");
is(iframe.contentDocument.querySelector("blink").textContent, "Not Found",
"The iframe's content document should be loaded and correct.");
RequestsMenu.selectedIndex = 5;
is($("#event-details-pane").selectedIndex, 0,
"The first tab in the details pane should be selected again.");
is($("#preview-tab").hidden, true,
"The preview tab should be hidden again for non html responses.");
is($("#preview-tabpanel").hidden, true,
"The preview tabpanel should be hidden again for non html responses.");
teardown(aMonitor).then(finish);
});
});
aDebuggee.performRequests();
});
}

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

@ -55,6 +55,7 @@ function test() {
"baz", "The second query param name was incorrect.");
is(postScope.querySelectorAll(".variables-view-variable .value")[1].getAttribute("value"),
"\"123\"", "The second query param value was incorrect.");
teardown(aMonitor).then(finish);
});
});

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

@ -22,6 +22,7 @@ const CYRILLIC_URL = EXAMPLE_URL + "html_cyrillic-test-page.html";
const STATUS_CODES_URL = EXAMPLE_URL + "html_status-codes-test-page.html";
const POST_DATA_URL = EXAMPLE_URL + "html_post-data-test-page.html";
const POST_RAW_URL = EXAMPLE_URL + "html_post-raw-test-page.html";
const PARAMS_URL = EXAMPLE_URL + "html_params-test-page.html";
const JSONP_URL = EXAMPLE_URL + "html_jsonp-test-page.html";
const JSON_LONG_URL = EXAMPLE_URL + "html_json-long-test-page.html";
const JSON_MALFORMED_URL = EXAMPLE_URL + "html_json-malformed-test-page.html";

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

@ -0,0 +1,54 @@
<!-- Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ -->
<!doctype html>
<html>
<head>
<meta charset="utf-8"/>
<title>Network Monitor test page</title>
</head>
<body>
<p>Request params type test</p>
<script type="text/javascript">
function post(aAddress, aQuery, aContentType, aPostBody) {
var xhr = new XMLHttpRequest();
xhr.open("POST", aAddress + aQuery, true);
xhr.setRequestHeader("content-type", aContentType);
xhr.send(aPostBody);
}
function performRequests() {
var urlencoded = "application/x-www-form-urlencoded";
setTimeout(function() {
post("baz", "?a", urlencoded, '{ "foo": "bar" }');
setTimeout(function() {
post("baz", "?a=b", urlencoded, '{ "foo": "bar" }');
setTimeout(function() {
post("baz", "?a=b", urlencoded, '?foo=bar');
setTimeout(function() {
post("baz", "?a", undefined, '{ "foo": "bar" }');
setTimeout(function() {
post("baz", "?a=b", undefined, '{ "foo": "bar" }');
setTimeout(function() {
post("baz", "?a=b", undefined, '?foo=bar');
// Done.
}, 10);
}, 10);
}, 10);
}, 10);
}, 10);
}, 10);
}
</script>
</body>
</html>

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

@ -15,6 +15,7 @@
function post(aAddress, aMessage, aCallback) {
var xhr = new XMLHttpRequest();
xhr.open("POST", aAddress, true);
xhr.setRequestHeader("content-type", "application/x-www-form-urlencoded");
xhr.onreadystatechange = function() {
if (this.readyState == this.DONE) {
@ -25,7 +26,7 @@
}
function performRequests() {
var rawData = "Content-Type: application/x-www-form-urlencoded\r\n\r\nfoo=bar&baz=123";
var rawData = "foo=bar&baz=123";
post("sjs_simple-test-server.sjs", rawData, function() {
// Done.
});

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

@ -153,8 +153,8 @@ function Editor(config) {
// Additional shortcuts.
this.config.extraKeys[Editor.keyFor("jumpToLine")] = () => this.jumpToLine();
this.config.extraKeys[Editor.keyFor("moveLineUp")] = () => this.moveLineUp();
this.config.extraKeys[Editor.keyFor("moveLineDown")] = () => this.moveLineDown();
this.config.extraKeys[Editor.keyFor("moveLineUp", { noaccel: true })] = () => this.moveLineUp();
this.config.extraKeys[Editor.keyFor("moveLineDown", { noaccel: true })] = () => this.moveLineDown();
this.config.extraKeys[Editor.keyFor("toggleComment")] = "toggleComment";
// Disable ctrl-[ and ctrl-] because toolbox uses those shortcuts.
@ -844,12 +844,13 @@ Editor.accel = function (key, modifiers={}) {
/**
* Returns a string representation of a shortcut for a
* specified command 'cmd'. Cmd- for macs, Ctrl- for other
* platforms. Useful when overwriting or disabling default
* shortcuts.
* specified command 'cmd'. Append Cmd- for macs, Ctrl- for other
* platforms unless noaccel is specified in the options. Useful when overwriting
* or disabling default shortcuts.
*/
Editor.keyFor = function (cmd) {
return Editor.accel(L10N.GetStringFromName(cmd + ".commandkey"));
Editor.keyFor = function (cmd, opts={ noaccel: false }) {
let key = L10N.GetStringFromName(cmd + ".commandkey");
return opts.noaccel ? key : Editor.accel(key);
};
// Since Gecko already provide complete and up to date list of CSS property

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

@ -31,6 +31,7 @@ support-files =
[browser_styleeditor_autocomplete.js]
[browser_styleeditor_bug_740541_iframes.js]
skip-if = os == "linux" || "mac" # bug 949355
[browser_styleeditor_bug_851132_middle_click.js]
[browser_styleeditor_bug_870339.js]
[browser_styleeditor_cmd_edit.js]

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

@ -68,6 +68,10 @@
- in the network details pane identifying the timings tab. -->
<!ENTITY netmonitorUI.tab.timings "Timings">
<!-- LOCALIZATION NOTE (debuggerUI.tab.preview): This is the label displayed
- in the network details pane identifying the preview tab. -->
<!ENTITY netmonitorUI.tab.preview "Preview">
<!-- LOCALIZATION NOTE (debuggerUI.footer.filterAll): This is the label displayed
- in the network details footer for the "All" filtering button. -->
<!ENTITY netmonitorUI.footer.filterAll "All">

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

@ -49,17 +49,17 @@ annotation.currentLine=Current line
# user-defined lines.
annotation.debugLocation.title=Current step: %S
# LOCALIZATION NOTE (jumpToLine.commandkey): This the key to use in
# LOCALIZATION NOTE (jumpToLine.commandkey): This is the key to use in
# conjunction with accel (Command on Mac or Ctrl on other platforms) to jump to
# a specific line in the editor.
jumpToLine.commandkey=J
# LOCALIZATION NOTE (toggleComment.commandkey): This the key to use in
# LOCALIZATION NOTE (toggleComment.commandkey): This is the key to use in
# conjunction with accel (Command on Mac or Ctrl on other platforms) to either
# comment or uncomment selected lines in the editor.
toggleComment.commandkey=/
# LOCALIZATION NOTE (toolboxPrevTool.commandkey): This the key to use in
# LOCALIZATION NOTE (indentLess.commandkey): This is the key to use in
# conjunction with accel (Command on Mac or Ctrl on other platforms) to reduce
# indentation level in CodeMirror. However, its default value also used by
# the Toolbox to switch between tools so we disable it.
@ -67,7 +67,7 @@ toggleComment.commandkey=/
# DO NOT translate this key without proper synchronization with toolbox.dtd.
indentLess.commandkey=[
# LOCALIZATION NOTE (toolboxPrevTool.commandkey): This the key to use in
# LOCALIZATION NOTE (indentMore.commandkey): This is the key to use in
# conjunction with accel (Command on Mac or Ctrl on other platforms) to increase
# indentation level in CodeMirror. However, its default value also used by
# the Toolbox to switch between tools
@ -75,12 +75,10 @@ indentLess.commandkey=[
# DO NOT translate this key without proper synchronization with toolbox.dtd.
indentMore.commandkey=]
# LOCALIZATION NOTE (moveLineUp.commandkey): This the key to use in
# conjunction with accel (Command on Mac or Ctrl on other platforms) to move
# LOCALIZATION NOTE (moveLineUp.commandkey): This is the key to use to move
# the selected lines up.
moveLineUp.commandkey=Alt-Up
# LOCALIZATION NOTE (moveLineDown.commandkey): This the key to use in
# conjunction with accel (Command on Mac or Ctrl on other platforms) to move
# LOCALIZATION NOTE (moveLineDown.commandkey): This is the key to use to move
# the selected lines down.
moveLineDown.commandkey=Alt-Down

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

@ -3,7 +3,6 @@
- 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/. -->
<!DOCTYPE bindings [
<!ENTITY % browserDTD SYSTEM "chrome://browser/locale/browser.dtd">
%browserDTD;
@ -13,7 +12,6 @@
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">
<binding id="error" extends="chrome://browser/content/bindings/bindings.xml#richlistitem">
<content orient="vertical">
<xul:hbox class="console-row-internal-box" flex="1">
@ -22,7 +20,6 @@
<xul:label class="label title" xbl:inherits="value=typetext"/>
<xul:description class="console-error-msg title" xbl:inherits="xbl:text=msg" flex="1"/>
</xul:hbox>
<xul:hbox class="console-row-file" xbl:inherits="hidden=hideSource">
<xul:label class="label title" value="&consoleErrFile.label;"/>
<xul:label class="title" xbl:inherits="value=href" crop="right"/>
@ -32,20 +29,13 @@
<xul:label class="label title" xbl:inherits="value=line"/>
</xul:hbox>
</xul:hbox>
<xul:vbox class="console-row-code" xbl:inherits="hidden=hideCode">
<xul:label class="monospace console-code" xbl:inherits="value=code" crop="end"/>
<xul:hbox xbl:inherits="hidden=hideCaret">
<xul:label class="monospace console-dots title" xbl:inherits="value=errorDots"/>
<xul:label class="monospace console-caret title" xbl:inherits="value=errorCaret"/>
<xul:spacer flex="1"/>
</xul:hbox>
</xul:vbox>
</xul:vbox>
</xul:hbox>
</content>
</binding>
<binding id="message" extends="chrome://browser/content/bindings/bindings.xml#richlistitem">
<content>
<xul:hbox class="console-internal-box" flex="1">
@ -57,5 +47,4 @@
</xul:hbox>
</content>
</binding>
</bindings>

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

@ -588,7 +588,9 @@ Desktop browser's sync prefs.
</hbox>
<hbox align="center"
pack="end">
<radiogroup id="console-filter"
<checkbox id="console-follow-checkbox" label="&consoleFollowCheckbox.label;" checked="true"/>
<spacer flex="1"/>
<radiogroup id="console-filter" orient="horizontal"
oncommand="ConsolePanelView.changeMode();">
<radio id="console-filter-all"
value="all"
@ -608,6 +610,10 @@ Desktop browser's sync prefs.
class="show-text"
label="&consoleClear.label;"
oncommand="ConsolePanelView.clearConsole();"/>
<button id="console-copy"
class="show-text"
label="&consoleCopyAll.label;"
oncommand="ConsolePanelView.copyAll();"/>
</hbox>
</vbox>

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

@ -13,6 +13,14 @@ let ConsolePanelView = {
_showChromeErrors: -1,
_enabledPref: "devtools.errorconsole.enabled",
get enabled() {
return Services.prefs.getBoolPref(this._enabledPref);
},
get follow() {
return document.getElementById("console-follow-checkbox").checked;
},
init: function cv_init() {
if (this._list)
return;
@ -23,6 +31,7 @@ let ConsolePanelView = {
this._count = 0;
this.limit = 250;
this.fieldMaxLength = 140;
try {
// update users using the legacy pref
@ -63,10 +72,6 @@ let ConsolePanelView = {
Services.prefs.removeObserver(this._enabledPref, this, false);
},
get enabled() {
return Services.prefs.getBoolPref(this._enabledPref);
},
observe: function(aSubject, aTopic, aData) {
if (aTopic == "nsPref:changed") {
// We may choose to create a new menu in v2
@ -89,6 +94,7 @@ let ConsolePanelView = {
},
appendItem: function cv_appendItem(aObject) {
let index = -1;
try {
// Try to QI it to a script error to get more info
let scriptError = aObject.QueryInterface(Ci.nsIScriptError);
@ -96,7 +102,7 @@ let ConsolePanelView = {
// filter chrome urls
if (!this.showChromeErrors && scriptError.sourceName.substr(0, 9) == "chrome://")
return;
this.appendError(scriptError);
index = this.appendError(scriptError);
}
catch (ex) {
try {
@ -104,15 +110,30 @@ let ConsolePanelView = {
let msg = aObject.QueryInterface(Ci.nsIConsoleMessage);
if (msg.message)
this.appendMessage(msg.message);
index = this.appendMessage(msg.message);
else // observed a null/"clear" message
this.clearConsole();
}
catch (ex2) {
// Give up and append the object itself as a string
this.appendMessage(aObject);
index = this.appendMessage(aObject);
}
}
if (this.follow) {
this._list.ensureIndexIsVisible(index);
}
},
truncateIfNecessary: function (aString) {
if (!aString || aString.length <= this.fieldMaxLength) {
return aString;
}
let truncatedString = aString.substring(0, this.fieldMaxLength);
let Ci = Components.interfaces;
let ellipsis = Services.prefs.getComplexValue("intl.ellipsis",
Ci.nsIPrefLocalizedString).data;
truncatedString = truncatedString + ellipsis;
return truncatedString;
},
appendError: function cv_appendError(aObject) {
@ -134,26 +155,26 @@ let ConsolePanelView = {
else {
row.setAttribute("hideSource", "true");
}
// hide code by default, otherwise initial item display will
// hang the browser.
row.setAttribute("hideCode", "true");
row.setAttribute("hideCaret", "true");
if (aObject.sourceLine) {
row.setAttribute("code", aObject.sourceLine.replace(/\s/g, " "));
row.setAttribute("code", this.truncateIfNecessary(aObject.sourceLine.replace(/\s/g, " ")));
if (aObject.columnNumber) {
row.setAttribute("col", aObject.columnNumber);
row.setAttribute("errorDots", this.repeatChar(" ", aObject.columnNumber));
row.setAttribute("errorCaret", " ");
}
else {
row.setAttribute("hideCaret", "true");
}
}
else {
row.setAttribute("hideCode", "true");
}
let mode = document.getElementById("console-filter").value;
if (mode != "all" && mode != row.getAttribute("type"))
if (mode != "all" && mode != row.getAttribute("type")) {
row.collapsed = true;
}
row.setAttribute("onclick", "ConsolePanelView.onRowClick(this)");
this.appendConsoleRow(row);
return this._list.getIndexOfItem(row);
},
appendMessage: function cv_appendMessage (aMessage) {
@ -166,6 +187,7 @@ let ConsolePanelView = {
row.collapsed = true;
this.appendConsoleRow(row);
return this._list.getIndexOfItem(row);
},
createConsoleRow: function cv_createConsoleRow() {
@ -176,8 +198,9 @@ let ConsolePanelView = {
appendConsoleRow: function cv_appendConsoleRow(aRow) {
this._list.appendChild(aRow);
if (++this._count > this.limit)
if (++this._count > this.limit) {
this.deleteFirst();
}
},
deleteFirst: function cv_deleteFirst() {
@ -187,6 +210,7 @@ let ConsolePanelView = {
},
appendInitialItems: function cv_appendInitialItems() {
this._list.collapsed = true;
let messages = Services.console.getMessageArray();
// In case getMessageArray returns 0-length array as null
@ -198,13 +222,17 @@ let ConsolePanelView = {
limit = 0;
// Checks if console ever been cleared
for (var i = messages.length - 1; i >= limit; --i)
if (!messages[i].message)
for (var i = messages.length - 1; i >= limit; --i) {
if (!messages[i].message) {
break;
}
}
// Populate with messages after latest "clear"
while (++i < messages.length)
while (++i < messages.length) {
this.appendItem(messages[i]);
}
this._list.collapsed = false;
},
clearConsole: function cv_clearConsole() {
@ -218,6 +246,27 @@ let ConsolePanelView = {
this.selectedItem = null;
},
copyAll: function () {
let mode = document.getElementById("console-filter").value;
let rows = this._list.childNodes;
let copyText = "";
for (let i=0; i < rows.length; i++) {
let row = rows[i];
if (mode == "all" || row.getAttribute ("type") == mode) {
let text = "* " + row.getAttribute("msg");
if (row.hasAttribute("href")) {
text += "\r\n " + row.getAttribute("href") + " line:" + row.getAttribute("line");
}
if (row.hasAttribute("code")) {
text += "\r\n " + row.getAttribute("code") + " col:" + row.getAttribute("col");
}
copyText += text + "\r\n";
}
}
let clip = Cc["@mozilla.org/widget/clipboardhelper;1"].getService(Ci.nsIClipboardHelper);
clip.copyString(copyText, document);
},
changeMode: function cv_changeMode() {
let mode = document.getElementById("console-filter").value;
if (this._list.getAttribute("mode") != mode) {
@ -237,7 +286,7 @@ let ConsolePanelView = {
onContextMenu: function cv_onContextMenu(aEvent) {
let row = aEvent.target;
let text = ["msg", "href", "line", "code", "col"].map(function(attr) row.getAttribute(attr))
.filter(function(x) x).join("\n");
.filter(function(x) x).join("\r\n");
ContextMenuUI.showContextMenu({
target: row,
@ -250,6 +299,15 @@ let ConsolePanelView = {
});
},
onRowClick: function (aRow) {
if (aRow.hasAttribute("code")) {
aRow.setAttribute("hideCode", "false");
}
if (aRow.hasAttribute("col")) {
aRow.setAttribute("hideCaret", "false");
}
},
onEvalKeyPress: function cv_onEvalKeyPress(aEvent) {
if (aEvent.keyCode == 13)
this.evaluateTypein();

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

@ -59,10 +59,12 @@
<!ENTITY consoleMessages.label "Messages">
<!ENTITY consoleCodeEval.label "Code:">
<!ENTITY consoleClear.label "Clear">
<!ENTITY consoleCopyAll.label "Copy">
<!ENTITY consoleEvaluate.label "…">
<!ENTITY consoleErrFile.label "Source File:">
<!ENTITY consoleErrLine.label "Line:">
<!ENTITY consoleErrColumn.label "Column:">
<!ENTITY consoleFollowCheckbox.label "Follow">
<!-- TEXT CONTEXT MENU -->
<!ENTITY contextTextCut.label "Cut">

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

@ -922,3 +922,8 @@ appbar toolbar[labelled] toolbarbutton > .toolbarbutton-text {
.flyout-narrow .flyoutpanel-hack {
max-width: calc(346px - 2 * 40px);
}
.console-row-code {
padding-top: 2px;
font-size: small;
}

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

@ -1466,6 +1466,11 @@ richlistitem[type~="action"][actiontype="switchtab"] > .ac-url-box > .ac-action-
margin-bottom: -16px;
}
#nav-bar .toolbarbutton-1 > menupopup[side="top"].cui-widget-panel,
#nav-bar .toolbarbutton-1 > menupopup[side="bottom"].cui-widget-panel {
margin-top: -4px;
}
/* Bookmarking panel */
#editBookmarkPanelStarIcon {
list-style-image: url("chrome://browser/skin/places/starred48.png");

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

@ -16,3 +16,10 @@
.widget-overflow-list .toolbarbutton-1 > .toolbarbutton-menubutton-dropmarker {
-moz-margin-start: 0;
}
.PanelUI-subView toolbarseparator,
.PanelUI-subView menuseparator,
.cui-widget-panelview menuseparator,
#PanelUI-footer-inner > toolbarseparator {
-moz-appearance: none !important;
}

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

@ -1272,6 +1272,10 @@ toolbarbutton[sdk-button="true"][cui-areatype="toolbar"] > .toolbarbutton-icon {
margin-top: 1px;
}
.toolbarbutton-1 > menupopup.cui-widget-panel {
margin-top: -5px;
}
/* Common back and forward button styles */
#back-button,

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

@ -66,3 +66,8 @@
.widget-overflow-list .toolbarbutton-1 > .toolbarbutton-menubutton-dropmarker {
-moz-margin-start: 4px;
}
.PanelUI-subView menuseparator,
.cui-widget-panelview menuseparator {
padding: 0 !important;
}

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

@ -54,6 +54,10 @@
-moz-box-flex: 1;
}
.subviewbutton:not(:-moz-any([image],[targetURI],.cui-withicon)) > .toolbarbutton-text {
-moz-margin-start: 0;
}
.panel-subview-body {
overflow-y: auto;
overflow-x: hidden;
@ -62,6 +66,7 @@
#PanelUI-popup .panel-subview-body {
margin: -4px;
padding: 2px 4px;
}
.panel-subview-header,
@ -154,7 +159,6 @@
panelview:not([mainview]) .toolbarbutton-text,
.cui-widget-panel toolbarbutton > .toolbarbutton-text {
text-align: start;
-moz-padding-start: 8px;
display: -moz-box;
}
@ -335,6 +339,7 @@ toolbarpaletteitem[place="palette"] > toolbaritem > toolbarbutton {
border: 0;
border-left: 1px solid rgba(0,0,0,0.1);
margin: 7px 0 7px;
-moz-appearance: none;
}
#PanelUI-footer-inner:hover > toolbarseparator {
@ -529,12 +534,6 @@ panelview .toolbarbutton-1,
margin-right: 4px;
}
.PanelUI-subView menuseparator,
.PanelUI-subView toolbarseparator {
-moz-margin-start: -5px;
-moz-margin-end: -4px;
}
panelview .toolbarbutton-1,
.widget-overflow-list .toolbarbutton-1 {
margin-top: 6px;
@ -580,12 +579,31 @@ panelview .toolbarbutton-1@buttonStateActive@,
margin-bottom: 0;
}
panelview toolbarseparator,
#BMB_bookmarksPopup > menuseparator {
.PanelUI-subView menuseparator,
.PanelUI-subView toolbarseparator,
.cui-widget-panelview menuseparator {
-moz-appearance: none;
min-height: 0;
border-top: 1px solid ThreeDShadow;
margin: 5px 0;
border-top: 1px solid hsla(210,4%,10%,.15);
margin: 2px 0;
padding: 0;
}
.PanelUI-subView menuseparator,
.PanelUI-subView toolbarseparator {
-moz-margin-start: -5px;
-moz-margin-end: -4px;
}
.PanelUI-subView menuseparator.small-separator,
.PanelUI-subView toolbarseparator.small-separator {
margin-left: 5px;
margin-right: 5px;
}
.cui-widget-panelview menuseparator.small-separator {
margin-left: 10px;
margin-right: 10px;
}
.subviewbutton > .menu-accel-container {
@ -782,36 +800,20 @@ toolbarpaletteitem[place="palette"] > #search-container {
box-shadow: 0 0 0 1px hsla(0,0%,100%,.2);
}
#PanelUI-developerItems > toolbarbutton[checked="true"],
#PanelUI-bookmarks > toolbarbutton[checked="true"],
#PanelUI-history > toolbarbutton[checked="true"],
.PanelUI-characterEncodingView-list > toolbarbutton[current] {
.PanelUI-subView toolbarbutton[checked="true"] {
-moz-padding-start: 4px;
}
#PanelUI-developerItems > toolbarbutton[checked="true"] > .toolbarbutton-text,
#PanelUI-bookmarks > toolbarbutton[checked="true"] > .toolbarbutton-text,
#PanelUI-history > toolbarbutton[checked="true"] > .toolbarbutton-text,
.PanelUI-characterEncodingView-list > toolbarbutton[current] > .toolbarbutton-text,
.cui-widget-panel .PanelUI-characterEncodingView-list > toolbarbutton[current] > .toolbarbutton-text {
.PanelUI-subView toolbarbutton[checked="true"] > .toolbarbutton-text {
-moz-padding-start: 0px;
}
#BMB_bookmarksPopup > menuitem[checked="true"]::before,
#PanelUI-bookmarks > toolbarbutton[checked="true"]::before,
#PanelUI-history > toolbarbutton[checked="true"]::before,
#PanelUI-developerItems > toolbarbutton[checked="true"]::before,
.PanelUI-characterEncodingView-list > toolbarbutton[current]::before {
.PanelUI-subView menuitem[checked="true"]::before,
.PanelUI-subView toolbarbutton[checked="true"]::before {
content: "✓";
display: -moz-box;
width: 12px;
}
#PanelUI-bookmarks > toolbarbutton[checked="true"]::before,
#PanelUI-history > toolbarbutton[checked="true"]::before,
#PanelUI-developerItems > toolbarbutton[checked="true"]::before,
.PanelUI-characterEncodingView-list > toolbarbutton[current]::before {
-moz-margin-end: -2px;
margin: 0 2px;
}
#BMB_bookmarksPopup > menuitem[checked="true"] > .menu-iconic-left {

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

@ -458,6 +458,17 @@ box.requests-menu-status {
margin-bottom: 10px;
}
/* Preview tabpanel */
#preview-tabpanel {
background: #fff;
}
#response-preview {
display: -moz-box;
-moz-box-flex: 1;
}
/* Timings tabpanel */
#timings-tabpanel .tabpanel-summary-label {

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

@ -505,6 +505,10 @@ menuitem.bookmark-item {
margin-top: -3px;
}
#nav-bar .toolbarbutton-1 > menupopup.cui-widget-panel {
margin-top: -8px;
}
#nav-bar .toolbarbutton-1 > .toolbarbutton-menubutton-button {
-moz-padding-end: 0;
}

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

@ -445,7 +445,7 @@ pref("browser.ui.touch.bottom", 16);
pref("browser.ui.touch.weight.visited", 120); // percentage
// The percentage of the screen that needs to be scrolled before margins are exposed.
pref("browser.ui.show-margins-threshold", 20);
pref("browser.ui.show-margins-threshold", 10);
// Maximum distance from the point where the user pressed where we still
// look for text to select

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

@ -469,16 +469,71 @@ public final class HomeConfig {
};
}
public static enum ItemHandler implements Parcelable {
BROWSER("browser"),
INTENT("intent");
private final String mId;
ItemHandler(String id) {
mId = id;
}
public static ItemHandler fromId(String id) {
if (id == null) {
throw new IllegalArgumentException("Could not convert null String to ItemHandler");
}
for (ItemHandler itemHandler : ItemHandler.values()) {
if (TextUtils.equals(itemHandler.mId, id.toLowerCase())) {
return itemHandler;
}
}
throw new IllegalArgumentException("Could not convert String id to ItemHandler");
}
@Override
public String toString() {
return mId;
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(ordinal());
}
public static final Creator<ItemHandler> CREATOR = new Creator<ItemHandler>() {
@Override
public ItemHandler createFromParcel(final Parcel source) {
return ItemHandler.values()[source.readInt()];
}
@Override
public ItemHandler[] newArray(final int size) {
return new ItemHandler[size];
}
};
}
public static class ViewConfig implements Parcelable {
private final ViewType mType;
private final String mDatasetId;
private final ItemHandler mItemHandler;
private static final String JSON_KEY_TYPE = "type";
private static final String JSON_KEY_DATASET = "dataset";
private static final String JSON_KEY_ITEM_HANDLER = "itemHandler";
public ViewConfig(JSONObject json) throws JSONException, IllegalArgumentException {
mType = ViewType.fromId(json.getString(JSON_KEY_TYPE));
mDatasetId = json.getString(JSON_KEY_DATASET);
mItemHandler = ItemHandler.fromId(json.getString(JSON_KEY_ITEM_HANDLER));
validate();
}
@ -487,6 +542,7 @@ public final class HomeConfig {
public ViewConfig(Parcel in) {
mType = (ViewType) in.readParcelable(getClass().getClassLoader());
mDatasetId = in.readString();
mItemHandler = (ItemHandler) in.readParcelable(getClass().getClassLoader());
validate();
}
@ -494,13 +550,15 @@ public final class HomeConfig {
public ViewConfig(ViewConfig viewConfig) {
mType = viewConfig.mType;
mDatasetId = viewConfig.mDatasetId;
mItemHandler = viewConfig.mItemHandler;
validate();
}
public ViewConfig(ViewType type, String datasetId) {
public ViewConfig(ViewType type, String datasetId, ItemHandler itemHandler) {
mType = type;
mDatasetId = datasetId;
mItemHandler = itemHandler;
validate();
}
@ -513,6 +571,10 @@ public final class HomeConfig {
if (TextUtils.isEmpty(mDatasetId)) {
throw new IllegalArgumentException("Can't create ViewConfig with empty dataset ID");
}
if (mItemHandler == null) {
throw new IllegalArgumentException("Can't create ViewConfig with null item handler");
}
}
public ViewType getType() {
@ -523,11 +585,16 @@ public final class HomeConfig {
return mDatasetId;
}
public ItemHandler getItemHandler() {
return mItemHandler;
}
public JSONObject toJSON() throws JSONException {
final JSONObject json = new JSONObject();
json.put(JSON_KEY_TYPE, mType.toString());
json.put(JSON_KEY_DATASET, mDatasetId);
json.put(JSON_KEY_ITEM_HANDLER, mItemHandler.toString());
return json;
}
@ -541,6 +608,7 @@ public final class HomeConfig {
public void writeToParcel(Parcel dest, int flags) {
dest.writeParcelable(mType, 0);
dest.writeString(mDatasetId);
dest.writeParcelable(mItemHandler, 0);
}
public static final Creator<ViewConfig> CREATOR = new Creator<ViewConfig>() {

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

@ -14,6 +14,7 @@ import org.mozilla.gecko.home.HomeConfig.PanelType;
import org.mozilla.gecko.util.HardwareUtils;
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.os.Bundle;
import android.support.v4.app.Fragment;
@ -51,6 +52,9 @@ public class HomePager extends ViewPager {
// Whether or not we need to restart the loader when we show the HomePager.
private boolean mRestartLoader;
// Cached original ViewPager background.
private final Drawable mOriginalBackground;
// This is mostly used by UI tests to easily fetch
// specific list views at runtime.
static final String LIST_TAG_HISTORY = "history";
@ -122,6 +126,8 @@ public class HomePager extends ViewPager {
// ensure there is always a focusable view. This would ordinarily be done via an XML
// attribute, but it is not working properly.
setFocusableInTouchMode(true);
mOriginalBackground = getBackground();
}
@Override
@ -310,9 +316,18 @@ public class HomePager extends ViewPager {
// Update the adapter with the new panel configs
adapter.update(enabledPanels);
// Hide the tab strip if the new configuration contains no panels.
final int count = enabledPanels.size();
mTabStrip.setVisibility(count > 0 ? View.VISIBLE : View.INVISIBLE);
if (count == 0) {
// Set firefox watermark as background.
setBackgroundResource(R.drawable.home_pager_empty_state);
// Hide the tab strip as there are no panels.
mTabStrip.setVisibility(View.INVISIBLE);
} else {
mTabStrip.setVisibility(View.VISIBLE);
// Restore original background.
setBackgroundDrawable(mOriginalBackground);
}
// Re-install the adapter with the final state
// in the pager.
setAdapter(adapter);

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

@ -7,6 +7,7 @@ package org.mozilla.gecko.home;
import org.mozilla.gecko.R;
import org.mozilla.gecko.db.BrowserContract.HomeItems;
import org.mozilla.gecko.home.HomeConfig.ItemHandler;
import org.mozilla.gecko.home.HomeConfig.ViewConfig;
import org.mozilla.gecko.home.HomePager.OnUrlOpenListener;
import org.mozilla.gecko.home.PanelLayout.DatasetBacked;
@ -27,11 +28,13 @@ public class PanelGridView extends GridView
implements DatasetBacked, PanelView {
private static final String LOGTAG = "GeckoPanelGridView";
private final ViewConfig mViewConfig;
private final PanelGridViewAdapter mAdapter;
protected OnUrlOpenListener mUrlOpenListener;
public PanelGridView(Context context, ViewConfig viewConfig) {
super(context, null, R.attr.panelGridViewStyle);
mViewConfig = viewConfig;
mAdapter = new PanelGridViewAdapter(context);
setAdapter(mAdapter);
setOnItemClickListener(new PanelGridItemClickListener());
@ -82,7 +85,12 @@ public class PanelGridView extends GridView
int urlIndex = cursor.getColumnIndexOrThrow(HomeItems.URL);
final String url = cursor.getString(urlIndex);
mUrlOpenListener.onUrlOpen(url, EnumSet.of(OnUrlOpenListener.Flags.OPEN_WITH_INTENT));
EnumSet<OnUrlOpenListener.Flags> flags = EnumSet.noneOf(OnUrlOpenListener.Flags.class);
if (mViewConfig.getItemHandler() == ItemHandler.INTENT) {
flags.add(OnUrlOpenListener.Flags.OPEN_WITH_INTENT);
}
mUrlOpenListener.onUrlOpen(url, flags);
}
}
}

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

@ -6,6 +6,7 @@
package org.mozilla.gecko.home;
import org.mozilla.gecko.R;
import org.mozilla.gecko.home.HomeConfig.ItemHandler;
import org.mozilla.gecko.home.HomeConfig.ViewConfig;
import org.mozilla.gecko.home.HomePager.OnUrlOpenListener;
import org.mozilla.gecko.home.PanelLayout.DatasetBacked;
@ -73,7 +74,12 @@ public class PanelListView extends HomeListView
int urlIndex = cursor.getColumnIndexOrThrow(HomeItems.URL);
final String url = cursor.getString(urlIndex);
mUrlOpenListener.onUrlOpen(url, EnumSet.of(OnUrlOpenListener.Flags.OPEN_WITH_INTENT));
EnumSet<OnUrlOpenListener.Flags> flags = EnumSet.noneOf(OnUrlOpenListener.Flags.class);
if (mViewConfig.getItemHandler() == ItemHandler.INTENT) {
flags.add(OnUrlOpenListener.Flags.OPEN_WITH_INTENT);
}
mUrlOpenListener.onUrlOpen(url, flags);
}
}
}

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

После

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

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

После

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

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

После

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

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

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- 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/. -->
<layer-list xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:gecko="http://schemas.android.com/apk/res-auto">
<item android:maxLevel="0" android:drawable="@android:color/white"/>
<item>
<bitmap android:src="@drawable/icon_home_empty_firefox"
android:gravity="center"/>
</item>
</layer-list>

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

@ -6,7 +6,7 @@
<merge xmlns:android="http://schemas.android.com/apk/res/android">
<ViewStub android:id="@+id/home_empty_view_stub"
android:layout="@layout/home_empty_page"
android:layout="@layout/home_empty_panel"
android:layout_width="fill_parent"
android:layout_height="fill_parent"/>

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

@ -6,7 +6,7 @@
<merge xmlns:android="http://schemas.android.com/apk/res/android">
<ViewStub android:id="@+id/home_empty_view_stub"
android:layout="@layout/home_empty_page"
android:layout="@layout/home_empty_panel"
android:layout_width="fill_parent"
android:layout_height="fill_parent"/>

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

@ -8,7 +8,7 @@
android:layout_height="fill_parent">
<ViewStub android:id="@+id/home_empty_view_stub"
android:layout="@layout/home_empty_page"
android:layout="@layout/home_empty_panel"
android:layout_width="fill_parent"
android:layout_height="fill_parent"/>

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

@ -6,7 +6,7 @@
<merge xmlns:android="http://schemas.android.com/apk/res/android">
<ViewStub android:id="@+id/home_empty_view_stub"
android:layout="@layout/home_empty_page"
android:layout="@layout/home_empty_panel"
android:layout_width="fill_parent"
android:layout_height="fill_parent"/>

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

@ -9,7 +9,7 @@
android:orientation="vertical">
<ViewStub android:id="@+id/home_empty_view_stub"
android:layout="@layout/home_empty_reading_page"
android:layout="@layout/home_empty_reading_panel"
android:layout_width="fill_parent"
android:layout_height="fill_parent"/>

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

@ -169,6 +169,12 @@ let HomePanels = Object.freeze({
REFRESH: "refresh"
}),
// Valid item handlers for a panel view.
ItemHandler: Object.freeze({
BROWSER: "browser",
INTENT: "intent"
}),
// Holds the currrent set of registered panels.
_panels: {},
@ -225,6 +231,13 @@ let HomePanels = Object.freeze({
throw "Home.panels: Invalid view type: panel.id = " + panel.id + ", view.type = " + view.type;
}
if (!view.itemHandler) {
// Use BROWSER item handler by default
view.itemHandler = this.ItemHandler.BROWSER;
} else if (!this._valueExists(this.ItemHandler, view.itemHandler)) {
throw "Home.panels: Invalid item handler: panel.id = " + panel.id + ", view.itemHandler = " + view.itemHandler;
}
if (!view.dataset) {
throw "Home.panels: No dataset provided for view: panel.id = " + panel.id + ", view.type = " + view.type;
}

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

@ -1,6 +1,8 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
Cu.import("resource://gre/modules/Promise.jsm");
Cu.import("resource://services-common/hawk.js");
@ -20,7 +22,6 @@ add_task(function test_now() {
let client = new HawkClient("https://example.com");
do_check_true(client.now() - Date.now() < SECOND_MS);
run_next_test();
});
add_task(function test_updateClockOffset() {
@ -41,8 +42,6 @@ add_task(function test_updateClockOffset() {
// that it agrees with the server. We are one hour ahead of the server, so
// our offset should be -1 hour.
do_check_true(Math.abs(client.localtimeOffsetMsec + HOUR_MS) <= SECOND_MS);
run_next_test();
});
add_task(function test_authenticated_get_request() {
@ -105,6 +104,7 @@ add_task(function test_credentials_optional() {
let client = new HawkClient(server.baseURI);
let result = yield client.request("/foo", method); // credentials undefined
do_check_eq(JSON.parse(result).msg, "you're in the friend zone");
yield deferredStop(server);
});
@ -122,6 +122,7 @@ add_task(function test_server_error() {
try {
yield client.request("/foo", method, TEST_CREDS);
do_throw("Expected an error");
} catch(err) {
do_check_eq(418, err.code);
do_check_eq("I am a Teapot", err.message);
@ -144,6 +145,7 @@ add_task(function test_server_error_json() {
try {
yield client.request("/foo", method, TEST_CREDS);
do_throw("Expected an error");
} catch(err) {
do_check_eq("Cannot get ye flask.", err.error);
}
@ -243,7 +245,6 @@ add_task(function test_2xx_success() {
do_check_eq(response, "");
yield deferredStop(server);
});
add_task(function test_retry_request_on_fail() {
@ -269,7 +270,8 @@ add_task(function test_retry_request_on_fail() {
do_check_true(delta > MINUTE_MS);
let message = "never!!!";
response.setStatusLine(request.httpVersion, 401, "Unauthorized");
return response.bodyOutputStream.write(message, message.length);
response.bodyOutputStream.write(message, message.length);
return;
}
// Second time through, timestamp should be corrected by client
@ -277,6 +279,7 @@ add_task(function test_retry_request_on_fail() {
let message = "i love you!!!";
response.setStatusLine(request.httpVersion, 200, "OK");
response.bodyOutputStream.write(message, message.length);
return;
}
});
@ -350,7 +353,6 @@ add_task(function test_multiple_401_retry_once() {
add_task(function test_500_no_retry() {
// If we get a 500 error, the client should not retry (as it would with a
// 401)
let attempts = 0;
let credentials = {
id: "eyJleHBpcmVzIjogMTM2NTAxMDg5OC4x",
key: "qTZf4ZFpAMpMoeSsX3zVRjiqmNs=",
@ -360,7 +362,6 @@ add_task(function test_500_no_retry() {
let server = httpd_setup({
"/no-shutup": function() {
attempts += 1;
let message = "Cannot get ye flask.";
response.setStatusLine(request.httpVersion, 500, "Internal server error");
response.bodyOutputStream.write(message, message.length);
@ -381,13 +382,12 @@ add_task(function test_500_no_retry() {
// Request will 500; no retries
try {
yield client.request("/no-shutup", method, credentials);
do_throw("Expected an error");
} catch(err) {
do_check_eq(err.code, 500);
}
do_check_eq(attempts, 1);
yield deferredStop(server);
});
add_task(function test_401_then_500() {
@ -417,7 +417,8 @@ add_task(function test_401_then_500() {
do_check_true(delta > MINUTE_MS);
let message = "never!!!";
response.setStatusLine(request.httpVersion, 401, "Unauthorized");
return response.bodyOutputStream.write(message, message.length);
response.bodyOutputStream.write(message, message.length);
return;
}
// Second time through, timestamp should be corrected by client
@ -426,6 +427,7 @@ add_task(function test_401_then_500() {
let message = "Cannot get ye flask.";
response.setStatusLine(request.httpVersion, 500, "Internal server error");
response.bodyOutputStream.write(message, message.length);
return;
}
});
@ -453,13 +455,12 @@ add_task(function test_401_then_500() {
});
add_task(function throw_if_not_json_body() {
do_test_pending();
let client = new HawkClient("https://example.com");
try {
yield client.request("/bogus", "GET", {}, "I am not json");
do_throw("Expected an error");
} catch(err) {
do_check_true(!!err.message);
do_test_finished();
}
});

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

@ -274,6 +274,8 @@
"dom/browser-element/mochitest/test_browserElement_inproc_CloseFromOpener.html":"",
"dom/browser-element/":"",
"dom/downloads/tests/test_downloads_pause_resume.html":"bug 947167",
"dom/events/test/test_bug226361.xhtml":"",
"dom/events/test/test_bug238987.html":"",
"dom/events/test/test_bug409604.html":"",

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

@ -262,6 +262,9 @@
"dom/browser-element/mochitest/test_browserElement_oop_OpenWindowRejected.html":"",
"dom/browser-element/mochitest/test_browserElement_oop_SecurityChange.html":"",
"dom/browser-element/mochitest/test_browserElement_oop_TargetBlank.html":"",
"dom/downloads/tests/test_downloads_pause_resume.html":"bug 947167",
"dom/events/test/test_bug226361.xhtml":"",
"dom/events/test/test_bug238987.html":"",
"dom/events/test/test_bug409604.html":"",

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

@ -0,0 +1,47 @@
/* 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/. */
const Cc = Components.classes;
const Ci = Components.interfaces;
var gProtocols = [];
var gContainer;
window.onload = function () {
gContainer = document.getElementById("abouts");
findAbouts();
}
function findAbouts() {
var ios = Cc["@mozilla.org/network/io-service;1"].getService(Ci.nsIIOService);
for (var cid in Cc) {
var result = cid.match(/@mozilla.org\/network\/protocol\/about;1\?what\=(.*)$/);
if (result) {
var aboutType = result[1];
var contract = "@mozilla.org/network/protocol/about;1?what=" + aboutType;
try {
var am = Cc[contract].getService(Ci.nsIAboutModule);
var uri = ios.newURI("about:"+aboutType, null, null);
var flags = am.getURIFlags(uri);
if (!(flags & Ci.nsIAboutModule.HIDE_FROM_ABOUTABOUT)) {
gProtocols.push(aboutType);
}
} catch (e) {
// getService might have thrown if the component doesn't actually
// implement nsIAboutModule
}
}
}
gProtocols.sort().forEach(createProtocolListing);
}
function createProtocolListing(aProtocol) {
var uri = "about:" + aProtocol;
var li = document.createElement("li");
var link = document.createElement("a");
var text = document.createTextNode(uri);
link.href = uri;
link.appendChild(text);
li.appendChild(link);
gContainer.appendChild(li);
}

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

@ -14,51 +14,7 @@
<head>
<title>&aboutAbout.title;</title>
<link rel="stylesheet" href="chrome://global/skin/about.css" type="text/css"/>
<script type="application/javascript"><![CDATA[
const Cc = Components.classes;
const Ci = Components.interfaces;
var gProtocols = [];
var gContainer;
window.onload = function () {
gContainer = document.getElementById("abouts");
findAbouts();
}
function findAbouts() {
var ios = Cc["@mozilla.org/network/io-service;1"].getService(Ci.nsIIOService);
for (var cid in Cc) {
var result = cid.match(/@mozilla.org\/network\/protocol\/about;1\?what\=(.*)$/);
if (result) {
var aboutType = result[1];
var contract = "@mozilla.org/network/protocol/about;1?what=" + aboutType;
try {
var am = Cc[contract].getService(Ci.nsIAboutModule);
var uri = ios.newURI("about:"+aboutType, null, null);
var flags = am.getURIFlags(uri);
if (!(flags & Ci.nsIAboutModule.HIDE_FROM_ABOUTABOUT)) {
gProtocols.push(aboutType);
}
} catch (e) {
// getService might have thrown if the component doesn't actually
// implement nsIAboutModule
}
}
}
gProtocols.sort().forEach(createProtocolListing);
}
function createProtocolListing(aProtocol) {
var uri = "about:" + aProtocol;
var li = document.createElement("li");
var link = document.createElement("a");
var text = document.createTextNode(uri);
link.href = uri;
link.appendChild(text);
li.appendChild(link);
gContainer.appendChild(li);
}
]]></script>
<script type="application/javascript" src="chrome://global/content/aboutAbout.js"></script>
</head>
<body dir="&locale.dir;">

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

@ -12,6 +12,7 @@ toolkit.jar:
content/global/menulist.css (menulist.css)
content/global/about.js (about.js)
content/global/about.xhtml (about.xhtml)
content/global/aboutAbout.js (aboutAbout.js)
content/global/aboutAbout.xhtml (aboutAbout.xhtml)
content/global/aboutRights.xhtml (aboutRights.xhtml)
content/global/aboutRights-unbranded.xhtml (aboutRights-unbranded.xhtml)

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

@ -463,6 +463,13 @@
return str.replace(/\n/g, "\\n");
}
/**
* Make sure that we put "\'" into the single-quoted output instead of raw single quotes.
*/
function sanitizeSingleQuotes(str) {
return str.replace(/\'/g, "\\'");
}
/**
* Add the given token to the pretty printed results.
*
@ -475,7 +482,7 @@
*/
function addToken(token, write, options) {
if (token.type.type == "string") {
write("'" + sanitizeNewlines(token.value) + "'",
write("'" + sanitizeSingleQuotes(sanitizeNewlines(token.value)) + "'",
token.startLoc.line,
token.startLoc.column);
} else {

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

@ -179,7 +179,7 @@ var testCases = [
{
name: "String with quote",
input: "var foo = \"'\";\n",
output: "var foo = '\'';\n"
output: "var foo = '\\'';\n"
},
{

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

@ -123,15 +123,17 @@ function safeGetState(state) {
let data = JSON.parse(string);
// Simplify the rest of the code by ensuring that we can simply
// concatenate the result to a message.
data.toString = function() {
return string;
};
if (data && typeof data == "object") {
data.toString = function() {
return string;
};
}
return data;
} catch (ex) {
try {
return "Error getting state: " + ex;
return "Error getting state: " + ex + " at " + ex.stack;
} catch (ex2) {
return "Could not display error";
return "Error getting state but could not display error";
}
}
}

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

@ -292,115 +292,237 @@ const ERRORS_TO_REPORT = ["EvalError", "RangeError", "ReferenceError", "TypeErro
//// Promise
/**
* This object provides the public module functions.
* The Promise constructor. Creates a new promise given an executor callback.
* The executor callback is called with the resolve and reject handlers.
*
* @param aExecutor
* The callback that will be called with resolve and reject.
*/
this.Promise = Object.freeze({
/**
* Creates a new pending promise and provides methods to resolve or reject it.
*
* @return A new object, containing the new promise in the "promise" property,
* and the methods to change its state in the "resolve" and "reject"
* properties. See the Deferred documentation for details.
this.Promise = function Promise(aExecutor)
{
if (typeof(aExecutor) != "function") {
throw new TypeError("Promise constructor must be called with an executor.");
}
/*
* Internal status of the promise. This can be equal to STATUS_PENDING,
* STATUS_RESOLVED, or STATUS_REJECTED.
*/
defer: function ()
{
return new Deferred();
},
Object.defineProperty(this, N_STATUS, { value: STATUS_PENDING,
writable: true });
/*
* When the N_STATUS property is STATUS_RESOLVED, this contains the final
* resolution value, that cannot be a promise, because resolving with a
* promise will cause its state to be eventually propagated instead. When the
* N_STATUS property is STATUS_REJECTED, this contains the final rejection
* reason, that could be a promise, even if this is uncommon.
*/
Object.defineProperty(this, N_VALUE, { writable: true });
/*
* Array of Handler objects registered by the "then" method, and not processed
* yet. Handlers are removed when the promise is resolved or rejected.
*/
Object.defineProperty(this, N_HANDLERS, { value: [] });
/**
* Creates a new promise resolved with the specified value, or propagates the
* state of an existing promise.
*
* @param aValue
* If this value is not a promise, including "undefined", it becomes
* the resolution value of the returned promise. If this value is a
* promise, then the returned promise will eventually assume the same
* state as the provided promise.
*
* @return A promise that can be pending, resolved, or rejected.
* When the N_STATUS property is STATUS_REJECTED and until there is
* a rejection callback, this contains an array
* - {string} id An id for use with |PendingErrors|;
* - {FinalizationWitness} witness A witness broadcasting |id| on
* notification "promise-finalization-witness".
*/
resolve: function (aValue)
{
let promise = new PromiseImpl();
PromiseWalker.completePromise(promise, STATUS_RESOLVED, aValue);
return promise;
},
Object.defineProperty(this, N_WITNESS, { writable: true });
/**
* Creates a new promise rejected with the specified reason.
*
* @param aReason
* The rejection reason for the returned promise. Although the reason
* can be "undefined", it is generally an Error object, like in
* exception handling.
*
* @return A rejected promise.
*
* @note The aReason argument should not be a promise. Using a rejected
* promise for the value of aReason would make the rejection reason
* equal to the rejected promise itself, and not its rejection reason.
*/
reject: function (aReason)
{
let promise = new PromiseImpl();
PromiseWalker.completePromise(promise, STATUS_REJECTED, aReason);
return promise;
},
Object.seal(this);
/**
* Returns a promise that is resolved or rejected when all values are
* resolved or any is rejected.
*
* @param aValues
* Array of promises that may be pending, resolved, or rejected. When
* all are resolved or any is rejected, the returned promise will be
* resolved or rejected as well.
*
* @return A new promise that is fulfilled when all values are resolved or
* that is rejected when any of the values are rejected. Its
* resolution value will be an array of all resolved values in the
* given order, or undefined if aValues is an empty array. The reject
* reason will be forwarded from the first promise in the list of
* given promises to be rejected.
*/
all: function (aValues)
{
if (!Array.isArray(aValues)) {
throw new Error("Promise.all() expects an array of promises or values.");
let resolve = PromiseWalker.completePromise
.bind(PromiseWalker, this, STATUS_RESOLVED);
let reject = PromiseWalker.completePromise
.bind(PromiseWalker, this, STATUS_REJECTED);
try {
Function.prototype.call.call(aExecutor, this, resolve, reject);
} catch (ex) {
reject(ex);
}
}
/**
* Calls one of the provided functions as soon as this promise is either
* resolved or rejected. A new promise is returned, whose state evolves
* depending on this promise and the provided callback functions.
*
* The appropriate callback is always invoked after this method returns, even
* if this promise is already resolved or rejected. You can also call the
* "then" method multiple times on the same promise, and the callbacks will be
* invoked in the same order as they were registered.
*
* @param aOnResolve
* If the promise is resolved, this function is invoked with the
* resolution value of the promise as its only argument, and the
* outcome of the function determines the state of the new promise
* returned by the "then" method. In case this parameter is not a
* function (usually "null"), the new promise returned by the "then"
* method is resolved with the same value as the original promise.
*
* @param aOnReject
* If the promise is rejected, this function is invoked with the
* rejection reason of the promise as its only argument, and the
* outcome of the function determines the state of the new promise
* returned by the "then" method. In case this parameter is not a
* function (usually left "undefined"), the new promise returned by the
* "then" method is rejected with the same reason as the original
* promise.
*
* @return A new promise that is initially pending, then assumes a state that
* depends on the outcome of the invoked callback function:
* - If the callback returns a value that is not a promise, including
* "undefined", the new promise is resolved with this resolution
* value, even if the original promise was rejected.
* - If the callback throws an exception, the new promise is rejected
* with the exception as the rejection reason, even if the original
* promise was resolved.
* - If the callback returns a promise, the new promise will
* eventually assume the same state as the returned promise.
*
* @note If the aOnResolve callback throws an exception, the aOnReject
* callback is not invoked. You can register a rejection callback on
* the returned promise instead, to process any exception occurred in
* either of the callbacks registered on this promise.
*/
Promise.prototype.then = function (aOnResolve, aOnReject)
{
let handler = new Handler(this, aOnResolve, aOnReject);
this[N_HANDLERS].push(handler);
// Ensure the handler is scheduled for processing if this promise is already
// resolved or rejected.
if (this[N_STATUS] != STATUS_PENDING) {
// This promise is not the last in the chain anymore. Remove any watchdog.
if (this[N_WITNESS] != null) {
let [id, witness] = this[N_WITNESS];
this[N_WITNESS] = null;
witness.forget();
PendingErrors.unregister(id);
}
if (!aValues.length) {
return Promise.resolve([]);
PromiseWalker.schedulePromise(this);
}
return handler.nextPromise;
};
/**
* Creates a new pending promise and provides methods to resolve or reject it.
*
* @return A new object, containing the new promise in the "promise" property,
* and the methods to change its state in the "resolve" and "reject"
* properties. See the Deferred documentation for details.
*/
Promise.defer = function ()
{
return new Deferred();
};
/**
* Creates a new promise resolved with the specified value, or propagates the
* state of an existing promise.
*
* @param aValue
* If this value is not a promise, including "undefined", it becomes
* the resolution value of the returned promise. If this value is a
* promise, then the returned promise will eventually assume the same
* state as the provided promise.
*
* @return A promise that can be pending, resolved, or rejected.
*/
Promise.resolve = function (aValue)
{
return new Promise((aResolve) => aResolve(aValue));
};
/**
* Creates a new promise rejected with the specified reason.
*
* @param aReason
* The rejection reason for the returned promise. Although the reason
* can be "undefined", it is generally an Error object, like in
* exception handling.
*
* @return A rejected promise.
*
* @note The aReason argument should not be a promise. Using a rejected
* promise for the value of aReason would make the rejection reason
* equal to the rejected promise itself, and not its rejection reason.
*/
Promise.reject = function (aReason)
{
return new Promise((_, aReject) => aReject(aReason));
};
/**
* Returns a promise that is resolved or rejected when all values are
* resolved or any is rejected.
*
* @param aValues
* Iterable of promises that may be pending, resolved, or rejected. When
* all are resolved or any is rejected, the returned promise will be
* resolved or rejected as well.
*
* @return A new promise that is fulfilled when all values are resolved or
* that is rejected when any of the values are rejected. Its
* resolution value will be an array of all resolved values in the
* given order, or undefined if aValues is an empty array. The reject
* reason will be forwarded from the first promise in the list of
* given promises to be rejected.
*/
Promise.all = function (aValues)
{
if (aValues == null || typeof(aValues["@@iterator"]) != "function") {
throw new Error("Promise.all() expects an iterable.");
}
if (!Array.isArray(aValues)) {
aValues = [...aValues];
}
if (!aValues.length) {
return Promise.resolve([]);
}
let countdown = aValues.length;
let deferred = Promise.defer();
let resolutionValues = new Array(countdown);
function checkForCompletion(aValue, aIndex) {
resolutionValues[aIndex] = aValue;
if (--countdown === 0) {
deferred.resolve(resolutionValues);
}
}
let countdown = aValues.length;
let deferred = Promise.defer();
let resolutionValues = new Array(countdown);
for (let i = 0; i < aValues.length; i++) {
let index = i;
let value = aValues[i];
let resolve = val => checkForCompletion(val, index);
function checkForCompletion(aValue, aIndex) {
resolutionValues[aIndex] = aValue;
if (--countdown === 0) {
deferred.resolve(resolutionValues);
}
if (value && typeof(value.then) == "function") {
value.then(resolve, deferred.reject);
} else {
// Given value is not a promise, forward it as a resolution value.
resolve(value);
}
}
for (let i = 0; i < aValues.length; i++) {
let index = i;
let value = aValues[i];
let resolve = val => checkForCompletion(val, index);
return deferred.promise;
};
if (value && typeof(value.then) == "function") {
value.then(resolve, deferred.reject);
} else {
// Given value is not a promise, forward it as a resolution value.
resolve(value);
}
}
return deferred.promise;
},
});
Object.freeze(Promise);
////////////////////////////////////////////////////////////////////////////////
//// PromiseWalker
@ -545,10 +667,10 @@ PromiseWalker.walkerLoop = PromiseWalker.walkerLoop.bind(PromiseWalker);
*/
function Deferred()
{
this.promise = new PromiseImpl();
this.resolve = this.resolve.bind(this);
this.reject = this.reject.bind(this);
this.promise = new Promise((aResolve, aReject) => {
this.resolve = aResolve;
this.reject = aReject;
});
Object.freeze(this);
}
@ -576,9 +698,7 @@ Deferred.prototype = {
* and then calling it again with another value before the promise is
* resolved or rejected, has unspecified behavior and should be avoided.
*/
resolve: function (aValue) {
PromiseWalker.completePromise(this.promise, STATUS_RESOLVED, aValue);
},
resolve: null,
/**
* Rejects the associated promise with the specified reason. If the promise
@ -597,120 +717,7 @@ Deferred.prototype = {
* rejection reason equal to the rejected promise itself, not to the
* rejection reason of the rejected promise.
*/
reject: function (aReason) {
PromiseWalker.completePromise(this.promise, STATUS_REJECTED, aReason);
},
};
////////////////////////////////////////////////////////////////////////////////
//// PromiseImpl
/**
* The promise object implementation. This includes the public "then" method,
* as well as private state properties.
*/
function PromiseImpl()
{
/*
* Internal status of the promise. This can be equal to STATUS_PENDING,
* STATUS_RESOLVED, or STATUS_REJECTED.
*/
Object.defineProperty(this, N_STATUS, { value: STATUS_PENDING,
writable: true });
/*
* When the N_STATUS property is STATUS_RESOLVED, this contains the final
* resolution value, that cannot be a promise, because resolving with a
* promise will cause its state to be eventually propagated instead. When the
* N_STATUS property is STATUS_REJECTED, this contains the final rejection
* reason, that could be a promise, even if this is uncommon.
*/
Object.defineProperty(this, N_VALUE, { writable: true });
/*
* Array of Handler objects registered by the "then" method, and not processed
* yet. Handlers are removed when the promise is resolved or rejected.
*/
Object.defineProperty(this, N_HANDLERS, { value: [] });
/**
* When the N_STATUS property is STATUS_REJECTED and until there is
* a rejection callback, this contains an array
* - {string} id An id for use with |PendingErrors|;
* - {FinalizationWitness} witness A witness broadcasting |id| on
* notification "promise-finalization-witness".
*/
Object.defineProperty(this, N_WITNESS, { writable: true });
Object.seal(this);
}
PromiseImpl.prototype = {
/**
* Calls one of the provided functions as soon as this promise is either
* resolved or rejected. A new promise is returned, whose state evolves
* depending on this promise and the provided callback functions.
*
* The appropriate callback is always invoked after this method returns, even
* if this promise is already resolved or rejected. You can also call the
* "then" method multiple times on the same promise, and the callbacks will be
* invoked in the same order as they were registered.
*
* @param aOnResolve
* If the promise is resolved, this function is invoked with the
* resolution value of the promise as its only argument, and the
* outcome of the function determines the state of the new promise
* returned by the "then" method. In case this parameter is not a
* function (usually "null"), the new promise returned by the "then"
* method is resolved with the same value as the original promise.
*
* @param aOnReject
* If the promise is rejected, this function is invoked with the
* rejection reason of the promise as its only argument, and the
* outcome of the function determines the state of the new promise
* returned by the "then" method. In case this parameter is not a
* function (usually left "undefined"), the new promise returned by the
* "then" method is rejected with the same reason as the original
* promise.
*
* @return A new promise that is initially pending, then assumes a state that
* depends on the outcome of the invoked callback function:
* - If the callback returns a value that is not a promise, including
* "undefined", the new promise is resolved with this resolution
* value, even if the original promise was rejected.
* - If the callback throws an exception, the new promise is rejected
* with the exception as the rejection reason, even if the original
* promise was resolved.
* - If the callback returns a promise, the new promise will
* eventually assume the same state as the returned promise.
*
* @note If the aOnResolve callback throws an exception, the aOnReject
* callback is not invoked. You can register a rejection callback on
* the returned promise instead, to process any exception occurred in
* either of the callbacks registered on this promise.
*/
then: function (aOnResolve, aOnReject)
{
let handler = new Handler(this, aOnResolve, aOnReject);
this[N_HANDLERS].push(handler);
// Ensure the handler is scheduled for processing if this promise is already
// resolved or rejected.
if (this[N_STATUS] != STATUS_PENDING) {
// This promise is not the last in the chain anymore. Remove any watchdog.
if (this[N_WITNESS] != null) {
let [id, witness] = this[N_WITNESS];
this[N_WITNESS] = null;
witness.forget();
PendingErrors.unregister(id);
}
PromiseWalker.schedulePromise(this);
}
return handler.nextPromise;
},
reject: null,
};
////////////////////////////////////////////////////////////////////////////////
@ -724,7 +731,7 @@ function Handler(aThisPromise, aOnResolve, aOnReject)
this.thisPromise = aThisPromise;
this.onResolve = aOnResolve;
this.onReject = aOnReject;
this.nextPromise = new PromiseImpl();
this.nextPromise = new Promise(() => {});
}
Handler.prototype = {

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

@ -662,6 +662,77 @@ tests.push(
return Promise.all([p1, p2]);
}));
// Test behavior of the Promise constructor.
tests.push(
make_promise_test(function test_constructor(test) {
try {
new Promise(null);
do_check_true(false, "Constructor should fail when not passed a function");
} catch (e) {
do_check_true(true, "Constructor fails when not passed a function");
}
let executorRan = false;
let receiver;
let promise = new Promise(
function executor(resolve, reject) {
executorRan = true;
receiver = this;
do_check_eq(typeof resolve, "function",
"resolve function should be passed to the executor");
do_check_eq(typeof reject, "function",
"reject function should be passed to the executor");
}
);
do_check_instanceof(promise, Promise);
do_check_true(executorRan, "Executor should execute synchronously");
do_check_eq(receiver, promise, "The promise is the |this| in the executor");
// resolve a promise from the executor
let resolvePromise = new Promise(
function executor(resolve) {
resolve(1);
}
).then(
function onResolve(value) {
do_check_eq(value, 1, "Executor resolved with correct value");
},
function onReject() {
do_throw("Executor unexpectedly rejected");
}
);
// reject a promise from the executor
let rejectPromise = new Promise(
function executor(_, reject) {
reject(1);
}
).then(
function onResolve() {
do_throw("Executor unexpectedly resolved");
},
function onReject(reason) {
do_check_eq(reason, 1, "Executor rejected with correct value");
}
);
// throw from the executor, causing a rejection
let throwPromise = new Promise(
function executor() {
throw 1;
}
).then(
function onResolve() {
do_throw("Throwing inside an executor should not resolve the promise");
},
function onReject(reason) {
do_check_eq(reason, 1, "Executor rejected with correct value");
}
);
return Promise.all([resolvePromise, rejectPromise, throwPromise]);
}));
// Test deadlock in Promise.jsm with nested event loops
// The scenario being tested is:
// promise_1.then({