зеркало из https://github.com/mozilla/gecko-dev.git
Merge fx-team to m-c
This commit is contained in:
Коммит
b921abacaf
|
@ -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 |
Двоичные данные
mobile/android/base/resources/drawable-xhdpi/icon_home_empty_firefox.png
Normal file
Двоичные данные
mobile/android/base/resources/drawable-xhdpi/icon_home_empty_firefox.png
Normal file
Двоичный файл не отображается.
После Ширина: | Высота: | Размер: 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({
|
||||
|
|
Загрузка…
Ссылка в новой задаче