зеркало из 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_clearplugindata.js]
|
||||||
[browser_contentAreaClick.js]
|
[browser_contentAreaClick.js]
|
||||||
[browser_contextSearchTabPosition.js]
|
[browser_contextSearchTabPosition.js]
|
||||||
|
skip-if = os == "mac" # bug 967013, bug 926729
|
||||||
[browser_ctrlTab.js]
|
[browser_ctrlTab.js]
|
||||||
[browser_customize_popupNotification.js]
|
[browser_customize_popupNotification.js]
|
||||||
[browser_datareporting_notification.js]
|
[browser_datareporting_notification.js]
|
||||||
|
@ -305,6 +306,7 @@ skip-if = true # disabled until the tree view is added
|
||||||
# it ever is (bug 480169)
|
# it ever is (bug 480169)
|
||||||
[browser_save_link-perwindowpb.js]
|
[browser_save_link-perwindowpb.js]
|
||||||
[browser_save_private_link_perwindowpb.js]
|
[browser_save_private_link_perwindowpb.js]
|
||||||
|
skip-if = os == "linux" # bug 857427
|
||||||
[browser_save_video.js]
|
[browser_save_video.js]
|
||||||
[browser_scope.js]
|
[browser_scope.js]
|
||||||
[browser_selectTabAtIndex.js]
|
[browser_selectTabAtIndex.js]
|
||||||
|
|
|
@ -112,13 +112,13 @@
|
||||||
<toolbarseparator/>
|
<toolbarseparator/>
|
||||||
<toolbarbutton id="panelMenu_bookmarksToolbar"
|
<toolbarbutton id="panelMenu_bookmarksToolbar"
|
||||||
label="&personalbarCmd.label;"
|
label="&personalbarCmd.label;"
|
||||||
class="subviewbutton"
|
class="subviewbutton cui-withicon"
|
||||||
oncommand="PlacesCommandHook.showPlacesOrganizer('BookmarksToolbar'); PanelUI.hide();"/>
|
oncommand="PlacesCommandHook.showPlacesOrganizer('BookmarksToolbar'); PanelUI.hide();"/>
|
||||||
<toolbarbutton id="panelMenu_unsortedBookmarks"
|
<toolbarbutton id="panelMenu_unsortedBookmarks"
|
||||||
label="&unsortedBookmarksCmd.label;"
|
label="&unsortedBookmarksCmd.label;"
|
||||||
class="subviewbutton"
|
class="subviewbutton cui-withicon"
|
||||||
oncommand="PlacesCommandHook.showPlacesOrganizer('UnfiledBookmarks'); PanelUI.hide();"/>
|
oncommand="PlacesCommandHook.showPlacesOrganizer('UnfiledBookmarks'); PanelUI.hide();"/>
|
||||||
<toolbarseparator/>
|
<toolbarseparator class="small-separator"/>
|
||||||
<toolbaritem id="panelMenu_bookmarksMenu"
|
<toolbaritem id="panelMenu_bookmarksMenu"
|
||||||
orient="vertical"
|
orient="vertical"
|
||||||
smoothscroll="false"
|
smoothscroll="false"
|
||||||
|
|
|
@ -662,6 +662,7 @@ const CustomizableWidgets = [{
|
||||||
for (let item of list) {
|
for (let item of list) {
|
||||||
let elem = aDocument.createElementNS(kNSXUL, "toolbarbutton");
|
let elem = aDocument.createElementNS(kNSXUL, "toolbarbutton");
|
||||||
elem.setAttribute("label", item.label);
|
elem.setAttribute("label", item.label);
|
||||||
|
elem.setAttribute("type", "checkbox");
|
||||||
elem.section = aSection == "detectors" ? "detectors" : "charsets";
|
elem.section = aSection == "detectors" ? "detectors" : "charsets";
|
||||||
elem.value = item.id;
|
elem.value = item.id;
|
||||||
elem.setAttribute("class", "subviewbutton");
|
elem.setAttribute("class", "subviewbutton");
|
||||||
|
@ -706,9 +707,9 @@ const CustomizableWidgets = [{
|
||||||
elem.removeAttribute("disabled");
|
elem.removeAttribute("disabled");
|
||||||
}
|
}
|
||||||
if (elem.value.toLowerCase() == aCurrentItem.toLowerCase()) {
|
if (elem.value.toLowerCase() == aCurrentItem.toLowerCase()) {
|
||||||
elem.setAttribute("current", "true");
|
elem.setAttribute("checked", "true");
|
||||||
} else {
|
} else {
|
||||||
elem.removeAttribute("current");
|
elem.removeAttribute("checked");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
@ -1122,8 +1122,10 @@ CustomizeMode.prototype = {
|
||||||
DragPositionManager.start(this.window);
|
DragPositionManager.start(this.window);
|
||||||
if (item.nextSibling) {
|
if (item.nextSibling) {
|
||||||
this._setDragActive(item.nextSibling, "before", draggedItem.id, isInToolbar);
|
this._setDragActive(item.nextSibling, "before", draggedItem.id, isInToolbar);
|
||||||
|
this._dragOverItem = item.nextSibling;
|
||||||
} else if (isInToolbar && item.previousSibling) {
|
} else if (isInToolbar && item.previousSibling) {
|
||||||
this._setDragActive(item.previousSibling, "after", draggedItem.id, isInToolbar);
|
this._setDragActive(item.previousSibling, "after", draggedItem.id, isInToolbar);
|
||||||
|
this._dragOverItem = item.previousSibling;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this._initializeDragAfterMove = null;
|
this._initializeDragAfterMove = null;
|
||||||
|
|
|
@ -293,6 +293,7 @@ PlacesViewBase.prototype = {
|
||||||
let type = aPlacesNode.type;
|
let type = aPlacesNode.type;
|
||||||
if (type == Ci.nsINavHistoryResultNode.RESULT_TYPE_SEPARATOR) {
|
if (type == Ci.nsINavHistoryResultNode.RESULT_TYPE_SEPARATOR) {
|
||||||
element = document.createElement("menuseparator");
|
element = document.createElement("menuseparator");
|
||||||
|
element.setAttribute("class", "small-separator");
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
let itemId = aPlacesNode.itemId;
|
let itemId = aPlacesNode.itemId;
|
||||||
|
@ -1808,6 +1809,7 @@ PlacesPanelMenuView.prototype = {
|
||||||
let button;
|
let button;
|
||||||
if (type == Ci.nsINavHistoryResultNode.RESULT_TYPE_SEPARATOR) {
|
if (type == Ci.nsINavHistoryResultNode.RESULT_TYPE_SEPARATOR) {
|
||||||
button = document.createElement("toolbarseparator");
|
button = document.createElement("toolbarseparator");
|
||||||
|
button.setAttribute("class", "small-separator");
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
button = document.createElement("toolbarbutton");
|
button = document.createElement("toolbarbutton");
|
||||||
|
|
|
@ -489,7 +489,7 @@
|
||||||
<!-- Most of this is copied from the arrowpanel binding in popup.xml -->
|
<!-- Most of this is copied from the arrowpanel binding in popup.xml -->
|
||||||
<binding id="places-popup-arrow"
|
<binding id="places-popup-arrow"
|
||||||
extends="chrome://browser/content/places/menu.xml#places-popup-base">
|
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"
|
<xul:vbox anonid="container" class="panel-arrowcontainer" flex="1"
|
||||||
xbl:inherits="side,panelopen">
|
xbl:inherits="side,panelopen">
|
||||||
<xul:box anonid="arrowbox" class="panel-arrowbox">
|
<xul:box anonid="arrowbox" class="panel-arrowbox">
|
||||||
|
|
|
@ -57,6 +57,9 @@ const EVENTS = {
|
||||||
// When the response body is displayed in the UI.
|
// When the response body is displayed in the UI.
|
||||||
RESPONSE_BODY_DISPLAYED: "NetMonitor:ResponseBodyAvailable",
|
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.
|
// When `onTabSelect` is fired and subsequently rendered.
|
||||||
TAB_UPDATED: "NetMonitor:TabUpdated",
|
TAB_UPDATED: "NetMonitor:TabUpdated",
|
||||||
|
|
||||||
|
@ -412,8 +415,7 @@ TargetEventsHandler.prototype = {
|
||||||
case "will-navigate": {
|
case "will-navigate": {
|
||||||
// Reset UI.
|
// Reset UI.
|
||||||
NetMonitorView.RequestsMenu.reset();
|
NetMonitorView.RequestsMenu.reset();
|
||||||
NetMonitorView.Sidebar.reset();
|
NetMonitorView.Sidebar.toggle(false);
|
||||||
NetMonitorView.NetworkDetails.reset();
|
|
||||||
|
|
||||||
// Switch to the default network traffic inspector view.
|
// Switch to the default network traffic inspector view.
|
||||||
if (NetMonitorController.getCurrentActivity() == ACTIVITY_TYPE.NONE) {
|
if (NetMonitorController.getCurrentActivity() == ACTIVITY_TYPE.NONE) {
|
||||||
|
|
|
@ -55,8 +55,7 @@ const GENERIC_VARIABLES_VIEW_SETTINGS = {
|
||||||
editableNameTooltip: "",
|
editableNameTooltip: "",
|
||||||
preventDisableOnChange: true,
|
preventDisableOnChange: true,
|
||||||
preventDescriptorModifiers: true,
|
preventDescriptorModifiers: true,
|
||||||
eval: () => {},
|
eval: () => {}
|
||||||
switch: () => {}
|
|
||||||
};
|
};
|
||||||
const NETWORK_ANALYSIS_PIE_CHART_DIAMETER = 200; // px
|
const NETWORK_ANALYSIS_PIE_CHART_DIAMETER = 200; // px
|
||||||
|
|
||||||
|
@ -1051,6 +1050,9 @@ RequestsMenuView.prototype = Heritage.extend(WidgetMethods, {
|
||||||
this.filterContents();
|
this.filterContents();
|
||||||
this.refreshSummary();
|
this.refreshSummary();
|
||||||
this.refreshZebra();
|
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);
|
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.
|
* True if this container's width was changed.
|
||||||
*/
|
*/
|
||||||
_flushWaterfallViews: function(aReset) {
|
_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
|
// To avoid expensive operations like getBoundingClientRect() and
|
||||||
// rebuilding the waterfall background each time a new request comes in,
|
// rebuilding the waterfall background each time a new request comes in,
|
||||||
// stuff is cached. However, in certain scenarios like when the window
|
// 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.
|
* The resize listener for this container's window.
|
||||||
*/
|
*/
|
||||||
_onResize: function(e) {
|
_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.
|
// Allow requests to settle down first.
|
||||||
setNamedTimeout(
|
setNamedTimeout(
|
||||||
"resize-events", RESIZE_REFRESH_RATE, () => this._flushWaterfallViews(true));
|
"resize-events", RESIZE_REFRESH_RATE, () => this._flushWaterfallViews(true));
|
||||||
|
@ -1634,13 +1629,6 @@ SidebarView.prototype = {
|
||||||
$("#details-pane").selectedIndex = isCustom ? 0 : 1
|
$("#details-pane").selectedIndex = isCustom ? 0 : 1
|
||||||
window.emit(EVENTS.SIDEBAR_POPULATED);
|
window.emit(EVENTS.SIDEBAR_POPULATED);
|
||||||
});
|
});
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Hides this container.
|
|
||||||
*/
|
|
||||||
reset: function() {
|
|
||||||
this.toggle(false);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1837,13 +1825,6 @@ NetworkDetailsView.prototype = {
|
||||||
dumpn("Destroying the NetworkDetailsView");
|
dumpn("Destroying the NetworkDetailsView");
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
|
||||||
* Resets this container (removes all the networking information).
|
|
||||||
*/
|
|
||||||
reset: function() {
|
|
||||||
this._dataSrc = null;
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Populates this view with the specified data.
|
* Populates this view with the specified data.
|
||||||
*
|
*
|
||||||
|
@ -1861,6 +1842,18 @@ NetworkDetailsView.prototype = {
|
||||||
$("#response-content-textarea-box").hidden = true;
|
$("#response-content-textarea-box").hidden = true;
|
||||||
$("#response-content-image-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._headers.empty();
|
||||||
this._cookies.empty();
|
this._cookies.empty();
|
||||||
this._params.empty();
|
this._params.empty();
|
||||||
|
@ -1907,6 +1900,9 @@ NetworkDetailsView.prototype = {
|
||||||
case 4: // "Timings"
|
case 4: // "Timings"
|
||||||
yield view._setTimingsInformation(src.eventTimings);
|
yield view._setTimingsInformation(src.eventTimings);
|
||||||
break;
|
break;
|
||||||
|
case 5: // "Preview"
|
||||||
|
yield view._setHtmlPreview(src.responseContent);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
populated[tab] = true;
|
populated[tab] = true;
|
||||||
window.emit(EVENTS.TAB_UPDATED);
|
window.emit(EVENTS.TAB_UPDATED);
|
||||||
|
@ -2104,32 +2100,45 @@ NetworkDetailsView.prototype = {
|
||||||
if (!aHeadersResponse || !aPostDataResponse) {
|
if (!aHeadersResponse || !aPostDataResponse) {
|
||||||
return promise.resolve();
|
return promise.resolve();
|
||||||
}
|
}
|
||||||
return gNetwork.getString(aPostDataResponse.postData.text).then(aString => {
|
return gNetwork.getString(aPostDataResponse.postData.text).then(aPostData => {
|
||||||
// Handle query strings (poor man's forms, e.g. "?foo=bar&baz=42").
|
let contentTypeHeader = aHeadersResponse.headers.filter(({ name }) => name == "Content-Type")[0];
|
||||||
let cType = aHeadersResponse.headers.filter(({ name }) => name == "Content-Type")[0];
|
let contentTypeLongString = contentTypeHeader ? contentTypeHeader.value : "";
|
||||||
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;
|
|
||||||
|
|
||||||
$("#request-post-data-textarea-box").hidden = false;
|
return gNetwork.getString(contentTypeLongString).then(aContentType => {
|
||||||
return NetMonitorView.editor("#request-post-data-textarea").then(aEditor => {
|
let urlencoded = "x-www-form-urlencoded";
|
||||||
aEditor.setText(aString);
|
|
||||||
});
|
// 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));
|
}).then(() => window.emit(EVENTS.REQUEST_POST_PARAMS_DISPLAYED));
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -2150,8 +2159,8 @@ NetworkDetailsView.prototype = {
|
||||||
paramsScope.expanded = true;
|
paramsScope.expanded = true;
|
||||||
|
|
||||||
for (let param of paramsArray) {
|
for (let param of paramsArray) {
|
||||||
let headerVar = paramsScope.addItem(param.name, {}, true);
|
let paramVar = paramsScope.addItem(param.name, {}, true);
|
||||||
headerVar.setGrip(param.value);
|
paramVar.setGrip(param.value);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -2345,6 +2354,30 @@ NetworkDetailsView.prototype = {
|
||||||
.style.transform = "translateX(" + (scale * (blocked + dns + connect + send + wait)) + "px)";
|
.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,
|
_dataSrc: null,
|
||||||
_headers: null,
|
_headers: null,
|
||||||
_cookies: null,
|
_cookies: null,
|
||||||
|
@ -2570,14 +2603,16 @@ nsIURL.store = new Map();
|
||||||
*/
|
*/
|
||||||
function parseQueryString(aQueryString) {
|
function parseQueryString(aQueryString) {
|
||||||
// Make sure there's at least one param available.
|
// 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;
|
return;
|
||||||
}
|
}
|
||||||
// Turn the params string into an array containing { name: value } tuples.
|
// Turn the params string into an array containing { name: value } tuples.
|
||||||
let paramsArray = aQueryString.replace(/^[?&]/, "").split("&").map(e =>
|
let paramsArray = aQueryString.replace(/^[?&]/, "").split("&").map(e =>
|
||||||
let (param = e.split("=")) {
|
let (param = e.split("=")) {
|
||||||
name: NetworkHelper.convertToUnicode(unescape(param[0])),
|
name: param[0] ? NetworkHelper.convertToUnicode(unescape(param[0])) : "",
|
||||||
value: NetworkHelper.convertToUnicode(unescape(param[1]))
|
value: param[1] ? NetworkHelper.convertToUnicode(unescape(param[1])) : ""
|
||||||
});
|
});
|
||||||
return paramsArray;
|
return paramsArray;
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,7 +13,8 @@
|
||||||
%netmonitorDTD;
|
%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"
|
<script type="application/javascript;version=1.8"
|
||||||
src="chrome://browser/content/devtools/theme-switching.js"/>
|
src="chrome://browser/content/devtools/theme-switching.js"/>
|
||||||
|
@ -238,14 +239,21 @@
|
||||||
class="devtools-sidebar-tabs"
|
class="devtools-sidebar-tabs"
|
||||||
handleCtrlTab="false">
|
handleCtrlTab="false">
|
||||||
<tabs>
|
<tabs>
|
||||||
<tab label="&netmonitorUI.tab.headers;"/>
|
<tab id="headers-tab"
|
||||||
<tab label="&netmonitorUI.tab.cookies;"/>
|
label="&netmonitorUI.tab.headers;"/>
|
||||||
<tab label="&netmonitorUI.tab.params;"/>
|
<tab id="cookies-tab"
|
||||||
<tab label="&netmonitorUI.tab.response;"/>
|
label="&netmonitorUI.tab.cookies;"/>
|
||||||
<tab label="&netmonitorUI.tab.timings;"/>
|
<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>
|
</tabs>
|
||||||
<tabpanels flex="1">
|
<tabpanels flex="1">
|
||||||
<tabpanel id="headers-tabppanel"
|
<tabpanel id="headers-tabpanel"
|
||||||
class="tabpanel-content">
|
class="tabpanel-content">
|
||||||
<vbox flex="1">
|
<vbox flex="1">
|
||||||
<hbox id="headers-summary-url"
|
<hbox id="headers-summary-url"
|
||||||
|
@ -413,6 +421,12 @@
|
||||||
</hbox>
|
</hbox>
|
||||||
</vbox>
|
</vbox>
|
||||||
</tabpanel>
|
</tabpanel>
|
||||||
|
<tabpanel id="preview-tabpanel"
|
||||||
|
class="tabpanel-content">
|
||||||
|
<html:iframe id="response-preview"
|
||||||
|
frameborder="0"
|
||||||
|
sandbox=""/>
|
||||||
|
</tabpanel>
|
||||||
</tabpanels>
|
</tabpanels>
|
||||||
</tabbox>
|
</tabbox>
|
||||||
</deck>
|
</deck>
|
||||||
|
|
|
@ -13,6 +13,7 @@ support-files =
|
||||||
html_json-text-mime-test-page.html
|
html_json-text-mime-test-page.html
|
||||||
html_jsonp-test-page.html
|
html_jsonp-test-page.html
|
||||||
html_navigate-test-page.html
|
html_navigate-test-page.html
|
||||||
|
html_params-test-page.html
|
||||||
html_post-data-test-page.html
|
html_post-data-test-page.html
|
||||||
html_post-raw-test-page.html
|
html_post-raw-test-page.html
|
||||||
html_simple-test-page.html
|
html_simple-test-page.html
|
||||||
|
@ -35,15 +36,17 @@ support-files =
|
||||||
[browser_net_charts-04.js]
|
[browser_net_charts-04.js]
|
||||||
[browser_net_charts-05.js]
|
[browser_net_charts-05.js]
|
||||||
[browser_net_clear.js]
|
[browser_net_clear.js]
|
||||||
|
[browser_net_complex-params.js]
|
||||||
[browser_net_content-type.js]
|
[browser_net_content-type.js]
|
||||||
[browser_net_copy_url.js]
|
|
||||||
[browser_net_copy_image_as_data_uri.js]
|
[browser_net_copy_image_as_data_uri.js]
|
||||||
|
[browser_net_copy_url.js]
|
||||||
[browser_net_cyrillic-01.js]
|
[browser_net_cyrillic-01.js]
|
||||||
[browser_net_cyrillic-02.js]
|
[browser_net_cyrillic-02.js]
|
||||||
[browser_net_filter-01.js]
|
[browser_net_filter-01.js]
|
||||||
[browser_net_filter-02.js]
|
[browser_net_filter-02.js]
|
||||||
[browser_net_filter-03.js]
|
[browser_net_filter-03.js]
|
||||||
[browser_net_footer-summary.js]
|
[browser_net_footer-summary.js]
|
||||||
|
[browser_net_html-preview.js]
|
||||||
[browser_net_json-long.js]
|
[browser_net_json-long.js]
|
||||||
[browser_net_json-malformed.js]
|
[browser_net_json-malformed.js]
|
||||||
[browser_net_json_custom_mime.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() {
|
function test() {
|
||||||
initNetMonitor(CONTENT_TYPE_URL).then(([aTab, aDebuggee, aMonitor]) => {
|
initNetMonitor(CONTENT_TYPE_WITHOUT_CACHE_URL).then(([aTab, aDebuggee, aMonitor]) => {
|
||||||
info("Starting test... ");
|
info("Starting test... ");
|
||||||
|
|
||||||
let { document, L10N, Editor, NetMonitorView } = aMonitor.panelWin;
|
let { document, L10N, Editor, NetMonitorView } = aMonitor.panelWin;
|
||||||
|
@ -62,6 +62,7 @@ function test() {
|
||||||
});
|
});
|
||||||
verifyRequestItemTarget(RequestsMenu.getItemAtIndex(5),
|
verifyRequestItemTarget(RequestsMenu.getItemAtIndex(5),
|
||||||
"GET", TEST_IMAGE, {
|
"GET", TEST_IMAGE, {
|
||||||
|
fuzzyUrl: true,
|
||||||
status: 200,
|
status: 200,
|
||||||
statusText: "OK",
|
statusText: "OK",
|
||||||
type: "png",
|
type: "png",
|
||||||
|
|
|
@ -11,7 +11,9 @@ function test() {
|
||||||
|
|
||||||
let { NetMonitorView } = aMonitor.panelWin;
|
let { NetMonitorView } = aMonitor.panelWin;
|
||||||
let { RequestsMenu } = NetMonitorView;
|
let { RequestsMenu } = NetMonitorView;
|
||||||
|
|
||||||
RequestsMenu.lazyUpdate = false;
|
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==";
|
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(() => {
|
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.");
|
"baz", "The second query param name was incorrect.");
|
||||||
is(postScope.querySelectorAll(".variables-view-variable .value")[1].getAttribute("value"),
|
is(postScope.querySelectorAll(".variables-view-variable .value")[1].getAttribute("value"),
|
||||||
"\"123\"", "The second query param value was incorrect.");
|
"\"123\"", "The second query param value was incorrect.");
|
||||||
|
|
||||||
teardown(aMonitor).then(finish);
|
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 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_DATA_URL = EXAMPLE_URL + "html_post-data-test-page.html";
|
||||||
const POST_RAW_URL = EXAMPLE_URL + "html_post-raw-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 JSONP_URL = EXAMPLE_URL + "html_jsonp-test-page.html";
|
||||||
const JSON_LONG_URL = EXAMPLE_URL + "html_json-long-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";
|
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) {
|
function post(aAddress, aMessage, aCallback) {
|
||||||
var xhr = new XMLHttpRequest();
|
var xhr = new XMLHttpRequest();
|
||||||
xhr.open("POST", aAddress, true);
|
xhr.open("POST", aAddress, true);
|
||||||
|
xhr.setRequestHeader("content-type", "application/x-www-form-urlencoded");
|
||||||
|
|
||||||
xhr.onreadystatechange = function() {
|
xhr.onreadystatechange = function() {
|
||||||
if (this.readyState == this.DONE) {
|
if (this.readyState == this.DONE) {
|
||||||
|
@ -25,7 +26,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
function performRequests() {
|
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() {
|
post("sjs_simple-test-server.sjs", rawData, function() {
|
||||||
// Done.
|
// Done.
|
||||||
});
|
});
|
||||||
|
|
|
@ -153,8 +153,8 @@ function Editor(config) {
|
||||||
|
|
||||||
// Additional shortcuts.
|
// Additional shortcuts.
|
||||||
this.config.extraKeys[Editor.keyFor("jumpToLine")] = () => this.jumpToLine();
|
this.config.extraKeys[Editor.keyFor("jumpToLine")] = () => this.jumpToLine();
|
||||||
this.config.extraKeys[Editor.keyFor("moveLineUp")] = () => this.moveLineUp();
|
this.config.extraKeys[Editor.keyFor("moveLineUp", { noaccel: true })] = () => this.moveLineUp();
|
||||||
this.config.extraKeys[Editor.keyFor("moveLineDown")] = () => this.moveLineDown();
|
this.config.extraKeys[Editor.keyFor("moveLineDown", { noaccel: true })] = () => this.moveLineDown();
|
||||||
this.config.extraKeys[Editor.keyFor("toggleComment")] = "toggleComment";
|
this.config.extraKeys[Editor.keyFor("toggleComment")] = "toggleComment";
|
||||||
|
|
||||||
// Disable ctrl-[ and ctrl-] because toolbox uses those shortcuts.
|
// 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
|
* Returns a string representation of a shortcut for a
|
||||||
* specified command 'cmd'. Cmd- for macs, Ctrl- for other
|
* specified command 'cmd'. Append Cmd- for macs, Ctrl- for other
|
||||||
* platforms. Useful when overwriting or disabling default
|
* platforms unless noaccel is specified in the options. Useful when overwriting
|
||||||
* shortcuts.
|
* or disabling default shortcuts.
|
||||||
*/
|
*/
|
||||||
Editor.keyFor = function (cmd) {
|
Editor.keyFor = function (cmd, opts={ noaccel: false }) {
|
||||||
return Editor.accel(L10N.GetStringFromName(cmd + ".commandkey"));
|
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
|
// 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_autocomplete.js]
|
||||||
[browser_styleeditor_bug_740541_iframes.js]
|
[browser_styleeditor_bug_740541_iframes.js]
|
||||||
|
skip-if = os == "linux" || "mac" # bug 949355
|
||||||
[browser_styleeditor_bug_851132_middle_click.js]
|
[browser_styleeditor_bug_851132_middle_click.js]
|
||||||
[browser_styleeditor_bug_870339.js]
|
[browser_styleeditor_bug_870339.js]
|
||||||
[browser_styleeditor_cmd_edit.js]
|
[browser_styleeditor_cmd_edit.js]
|
||||||
|
|
|
@ -68,6 +68,10 @@
|
||||||
- in the network details pane identifying the timings tab. -->
|
- in the network details pane identifying the timings tab. -->
|
||||||
<!ENTITY netmonitorUI.tab.timings "Timings">
|
<!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
|
<!-- LOCALIZATION NOTE (debuggerUI.footer.filterAll): This is the label displayed
|
||||||
- in the network details footer for the "All" filtering button. -->
|
- in the network details footer for the "All" filtering button. -->
|
||||||
<!ENTITY netmonitorUI.footer.filterAll "All">
|
<!ENTITY netmonitorUI.footer.filterAll "All">
|
||||||
|
|
|
@ -49,17 +49,17 @@ annotation.currentLine=Current line
|
||||||
# user-defined lines.
|
# user-defined lines.
|
||||||
annotation.debugLocation.title=Current step: %S
|
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
|
# conjunction with accel (Command on Mac or Ctrl on other platforms) to jump to
|
||||||
# a specific line in the editor.
|
# a specific line in the editor.
|
||||||
jumpToLine.commandkey=J
|
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
|
# conjunction with accel (Command on Mac or Ctrl on other platforms) to either
|
||||||
# comment or uncomment selected lines in the editor.
|
# comment or uncomment selected lines in the editor.
|
||||||
toggleComment.commandkey=/
|
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
|
# conjunction with accel (Command on Mac or Ctrl on other platforms) to reduce
|
||||||
# indentation level in CodeMirror. However, its default value also used by
|
# indentation level in CodeMirror. However, its default value also used by
|
||||||
# the Toolbox to switch between tools so we disable it.
|
# 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.
|
# DO NOT translate this key without proper synchronization with toolbox.dtd.
|
||||||
indentLess.commandkey=[
|
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
|
# conjunction with accel (Command on Mac or Ctrl on other platforms) to increase
|
||||||
# indentation level in CodeMirror. However, its default value also used by
|
# indentation level in CodeMirror. However, its default value also used by
|
||||||
# the Toolbox to switch between tools
|
# the Toolbox to switch between tools
|
||||||
|
@ -75,12 +75,10 @@ indentLess.commandkey=[
|
||||||
# DO NOT translate this key without proper synchronization with toolbox.dtd.
|
# DO NOT translate this key without proper synchronization with toolbox.dtd.
|
||||||
indentMore.commandkey=]
|
indentMore.commandkey=]
|
||||||
|
|
||||||
# LOCALIZATION NOTE (moveLineUp.commandkey): This the key to use in
|
# LOCALIZATION NOTE (moveLineUp.commandkey): This is the key to use to move
|
||||||
# conjunction with accel (Command on Mac or Ctrl on other platforms) to move
|
|
||||||
# the selected lines up.
|
# the selected lines up.
|
||||||
moveLineUp.commandkey=Alt-Up
|
moveLineUp.commandkey=Alt-Up
|
||||||
|
|
||||||
# LOCALIZATION NOTE (moveLineDown.commandkey): This the key to use in
|
# LOCALIZATION NOTE (moveLineDown.commandkey): This is the key to use to move
|
||||||
# conjunction with accel (Command on Mac or Ctrl on other platforms) to move
|
|
||||||
# the selected lines down.
|
# the selected lines down.
|
||||||
moveLineDown.commandkey=Alt-Down
|
moveLineDown.commandkey=Alt-Down
|
||||||
|
|
|
@ -3,7 +3,6 @@
|
||||||
- License, v. 2.0. If a copy of the MPL was not distributed with this
|
- 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/. -->
|
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
|
||||||
|
|
||||||
|
|
||||||
<!DOCTYPE bindings [
|
<!DOCTYPE bindings [
|
||||||
<!ENTITY % browserDTD SYSTEM "chrome://browser/locale/browser.dtd">
|
<!ENTITY % browserDTD SYSTEM "chrome://browser/locale/browser.dtd">
|
||||||
%browserDTD;
|
%browserDTD;
|
||||||
|
@ -13,7 +12,6 @@
|
||||||
xmlns="http://www.mozilla.org/xbl"
|
xmlns="http://www.mozilla.org/xbl"
|
||||||
xmlns:xbl="http://www.mozilla.org/xbl"
|
xmlns:xbl="http://www.mozilla.org/xbl"
|
||||||
xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
|
xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
|
||||||
|
|
||||||
<binding id="error" extends="chrome://browser/content/bindings/bindings.xml#richlistitem">
|
<binding id="error" extends="chrome://browser/content/bindings/bindings.xml#richlistitem">
|
||||||
<content orient="vertical">
|
<content orient="vertical">
|
||||||
<xul:hbox class="console-row-internal-box" flex="1">
|
<xul:hbox class="console-row-internal-box" flex="1">
|
||||||
|
@ -22,7 +20,6 @@
|
||||||
<xul:label class="label title" xbl:inherits="value=typetext"/>
|
<xul:label class="label title" xbl:inherits="value=typetext"/>
|
||||||
<xul:description class="console-error-msg title" xbl:inherits="xbl:text=msg" flex="1"/>
|
<xul:description class="console-error-msg title" xbl:inherits="xbl:text=msg" flex="1"/>
|
||||||
</xul:hbox>
|
</xul:hbox>
|
||||||
|
|
||||||
<xul:hbox class="console-row-file" xbl:inherits="hidden=hideSource">
|
<xul:hbox class="console-row-file" xbl:inherits="hidden=hideSource">
|
||||||
<xul:label class="label title" value="&consoleErrFile.label;"/>
|
<xul:label class="label title" value="&consoleErrFile.label;"/>
|
||||||
<xul:label class="title" xbl:inherits="value=href" crop="right"/>
|
<xul:label class="title" xbl:inherits="value=href" crop="right"/>
|
||||||
|
@ -32,20 +29,13 @@
|
||||||
<xul:label class="label title" xbl:inherits="value=line"/>
|
<xul:label class="label title" xbl:inherits="value=line"/>
|
||||||
</xul:hbox>
|
</xul:hbox>
|
||||||
</xul:hbox>
|
</xul:hbox>
|
||||||
|
|
||||||
<xul:vbox class="console-row-code" xbl:inherits="hidden=hideCode">
|
<xul:vbox class="console-row-code" xbl:inherits="hidden=hideCode">
|
||||||
<xul:label class="monospace console-code" xbl:inherits="value=code" crop="end"/>
|
<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:vbox>
|
</xul:vbox>
|
||||||
</xul:hbox>
|
</xul:hbox>
|
||||||
</content>
|
</content>
|
||||||
</binding>
|
</binding>
|
||||||
|
|
||||||
<binding id="message" extends="chrome://browser/content/bindings/bindings.xml#richlistitem">
|
<binding id="message" extends="chrome://browser/content/bindings/bindings.xml#richlistitem">
|
||||||
<content>
|
<content>
|
||||||
<xul:hbox class="console-internal-box" flex="1">
|
<xul:hbox class="console-internal-box" flex="1">
|
||||||
|
@ -57,5 +47,4 @@
|
||||||
</xul:hbox>
|
</xul:hbox>
|
||||||
</content>
|
</content>
|
||||||
</binding>
|
</binding>
|
||||||
|
|
||||||
</bindings>
|
</bindings>
|
||||||
|
|
|
@ -588,7 +588,9 @@ Desktop browser's sync prefs.
|
||||||
</hbox>
|
</hbox>
|
||||||
<hbox align="center"
|
<hbox align="center"
|
||||||
pack="end">
|
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();">
|
oncommand="ConsolePanelView.changeMode();">
|
||||||
<radio id="console-filter-all"
|
<radio id="console-filter-all"
|
||||||
value="all"
|
value="all"
|
||||||
|
@ -608,6 +610,10 @@ Desktop browser's sync prefs.
|
||||||
class="show-text"
|
class="show-text"
|
||||||
label="&consoleClear.label;"
|
label="&consoleClear.label;"
|
||||||
oncommand="ConsolePanelView.clearConsole();"/>
|
oncommand="ConsolePanelView.clearConsole();"/>
|
||||||
|
<button id="console-copy"
|
||||||
|
class="show-text"
|
||||||
|
label="&consoleCopyAll.label;"
|
||||||
|
oncommand="ConsolePanelView.copyAll();"/>
|
||||||
</hbox>
|
</hbox>
|
||||||
</vbox>
|
</vbox>
|
||||||
|
|
||||||
|
|
|
@ -13,6 +13,14 @@ let ConsolePanelView = {
|
||||||
_showChromeErrors: -1,
|
_showChromeErrors: -1,
|
||||||
_enabledPref: "devtools.errorconsole.enabled",
|
_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() {
|
init: function cv_init() {
|
||||||
if (this._list)
|
if (this._list)
|
||||||
return;
|
return;
|
||||||
|
@ -23,6 +31,7 @@ let ConsolePanelView = {
|
||||||
|
|
||||||
this._count = 0;
|
this._count = 0;
|
||||||
this.limit = 250;
|
this.limit = 250;
|
||||||
|
this.fieldMaxLength = 140;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// update users using the legacy pref
|
// update users using the legacy pref
|
||||||
|
@ -63,10 +72,6 @@ let ConsolePanelView = {
|
||||||
Services.prefs.removeObserver(this._enabledPref, this, false);
|
Services.prefs.removeObserver(this._enabledPref, this, false);
|
||||||
},
|
},
|
||||||
|
|
||||||
get enabled() {
|
|
||||||
return Services.prefs.getBoolPref(this._enabledPref);
|
|
||||||
},
|
|
||||||
|
|
||||||
observe: function(aSubject, aTopic, aData) {
|
observe: function(aSubject, aTopic, aData) {
|
||||||
if (aTopic == "nsPref:changed") {
|
if (aTopic == "nsPref:changed") {
|
||||||
// We may choose to create a new menu in v2
|
// We may choose to create a new menu in v2
|
||||||
|
@ -89,6 +94,7 @@ let ConsolePanelView = {
|
||||||
},
|
},
|
||||||
|
|
||||||
appendItem: function cv_appendItem(aObject) {
|
appendItem: function cv_appendItem(aObject) {
|
||||||
|
let index = -1;
|
||||||
try {
|
try {
|
||||||
// Try to QI it to a script error to get more info
|
// Try to QI it to a script error to get more info
|
||||||
let scriptError = aObject.QueryInterface(Ci.nsIScriptError);
|
let scriptError = aObject.QueryInterface(Ci.nsIScriptError);
|
||||||
|
@ -96,7 +102,7 @@ let ConsolePanelView = {
|
||||||
// filter chrome urls
|
// filter chrome urls
|
||||||
if (!this.showChromeErrors && scriptError.sourceName.substr(0, 9) == "chrome://")
|
if (!this.showChromeErrors && scriptError.sourceName.substr(0, 9) == "chrome://")
|
||||||
return;
|
return;
|
||||||
this.appendError(scriptError);
|
index = this.appendError(scriptError);
|
||||||
}
|
}
|
||||||
catch (ex) {
|
catch (ex) {
|
||||||
try {
|
try {
|
||||||
|
@ -104,15 +110,30 @@ let ConsolePanelView = {
|
||||||
let msg = aObject.QueryInterface(Ci.nsIConsoleMessage);
|
let msg = aObject.QueryInterface(Ci.nsIConsoleMessage);
|
||||||
|
|
||||||
if (msg.message)
|
if (msg.message)
|
||||||
this.appendMessage(msg.message);
|
index = this.appendMessage(msg.message);
|
||||||
else // observed a null/"clear" message
|
else // observed a null/"clear" message
|
||||||
this.clearConsole();
|
this.clearConsole();
|
||||||
}
|
}
|
||||||
catch (ex2) {
|
catch (ex2) {
|
||||||
// Give up and append the object itself as a string
|
// 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) {
|
appendError: function cv_appendError(aObject) {
|
||||||
|
@ -134,26 +155,26 @@ let ConsolePanelView = {
|
||||||
else {
|
else {
|
||||||
row.setAttribute("hideSource", "true");
|
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) {
|
if (aObject.sourceLine) {
|
||||||
row.setAttribute("code", aObject.sourceLine.replace(/\s/g, " "));
|
row.setAttribute("code", this.truncateIfNecessary(aObject.sourceLine.replace(/\s/g, " ")));
|
||||||
if (aObject.columnNumber) {
|
if (aObject.columnNumber) {
|
||||||
row.setAttribute("col", 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;
|
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.collapsed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
row.setAttribute("onclick", "ConsolePanelView.onRowClick(this)");
|
||||||
this.appendConsoleRow(row);
|
this.appendConsoleRow(row);
|
||||||
|
return this._list.getIndexOfItem(row);
|
||||||
},
|
},
|
||||||
|
|
||||||
appendMessage: function cv_appendMessage (aMessage) {
|
appendMessage: function cv_appendMessage (aMessage) {
|
||||||
|
@ -166,6 +187,7 @@ let ConsolePanelView = {
|
||||||
row.collapsed = true;
|
row.collapsed = true;
|
||||||
|
|
||||||
this.appendConsoleRow(row);
|
this.appendConsoleRow(row);
|
||||||
|
return this._list.getIndexOfItem(row);
|
||||||
},
|
},
|
||||||
|
|
||||||
createConsoleRow: function cv_createConsoleRow() {
|
createConsoleRow: function cv_createConsoleRow() {
|
||||||
|
@ -176,8 +198,9 @@ let ConsolePanelView = {
|
||||||
|
|
||||||
appendConsoleRow: function cv_appendConsoleRow(aRow) {
|
appendConsoleRow: function cv_appendConsoleRow(aRow) {
|
||||||
this._list.appendChild(aRow);
|
this._list.appendChild(aRow);
|
||||||
if (++this._count > this.limit)
|
if (++this._count > this.limit) {
|
||||||
this.deleteFirst();
|
this.deleteFirst();
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
deleteFirst: function cv_deleteFirst() {
|
deleteFirst: function cv_deleteFirst() {
|
||||||
|
@ -187,6 +210,7 @@ let ConsolePanelView = {
|
||||||
},
|
},
|
||||||
|
|
||||||
appendInitialItems: function cv_appendInitialItems() {
|
appendInitialItems: function cv_appendInitialItems() {
|
||||||
|
this._list.collapsed = true;
|
||||||
let messages = Services.console.getMessageArray();
|
let messages = Services.console.getMessageArray();
|
||||||
|
|
||||||
// In case getMessageArray returns 0-length array as null
|
// In case getMessageArray returns 0-length array as null
|
||||||
|
@ -198,13 +222,17 @@ let ConsolePanelView = {
|
||||||
limit = 0;
|
limit = 0;
|
||||||
|
|
||||||
// Checks if console ever been cleared
|
// Checks if console ever been cleared
|
||||||
for (var i = messages.length - 1; i >= limit; --i)
|
for (var i = messages.length - 1; i >= limit; --i) {
|
||||||
if (!messages[i].message)
|
if (!messages[i].message) {
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Populate with messages after latest "clear"
|
// Populate with messages after latest "clear"
|
||||||
while (++i < messages.length)
|
while (++i < messages.length) {
|
||||||
this.appendItem(messages[i]);
|
this.appendItem(messages[i]);
|
||||||
|
}
|
||||||
|
this._list.collapsed = false;
|
||||||
},
|
},
|
||||||
|
|
||||||
clearConsole: function cv_clearConsole() {
|
clearConsole: function cv_clearConsole() {
|
||||||
|
@ -218,6 +246,27 @@ let ConsolePanelView = {
|
||||||
this.selectedItem = null;
|
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() {
|
changeMode: function cv_changeMode() {
|
||||||
let mode = document.getElementById("console-filter").value;
|
let mode = document.getElementById("console-filter").value;
|
||||||
if (this._list.getAttribute("mode") != mode) {
|
if (this._list.getAttribute("mode") != mode) {
|
||||||
|
@ -237,7 +286,7 @@ let ConsolePanelView = {
|
||||||
onContextMenu: function cv_onContextMenu(aEvent) {
|
onContextMenu: function cv_onContextMenu(aEvent) {
|
||||||
let row = aEvent.target;
|
let row = aEvent.target;
|
||||||
let text = ["msg", "href", "line", "code", "col"].map(function(attr) row.getAttribute(attr))
|
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({
|
ContextMenuUI.showContextMenu({
|
||||||
target: row,
|
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) {
|
onEvalKeyPress: function cv_onEvalKeyPress(aEvent) {
|
||||||
if (aEvent.keyCode == 13)
|
if (aEvent.keyCode == 13)
|
||||||
this.evaluateTypein();
|
this.evaluateTypein();
|
||||||
|
|
|
@ -59,10 +59,12 @@
|
||||||
<!ENTITY consoleMessages.label "Messages">
|
<!ENTITY consoleMessages.label "Messages">
|
||||||
<!ENTITY consoleCodeEval.label "Code:">
|
<!ENTITY consoleCodeEval.label "Code:">
|
||||||
<!ENTITY consoleClear.label "Clear">
|
<!ENTITY consoleClear.label "Clear">
|
||||||
|
<!ENTITY consoleCopyAll.label "Copy">
|
||||||
<!ENTITY consoleEvaluate.label "…">
|
<!ENTITY consoleEvaluate.label "…">
|
||||||
<!ENTITY consoleErrFile.label "Source File:">
|
<!ENTITY consoleErrFile.label "Source File:">
|
||||||
<!ENTITY consoleErrLine.label "Line:">
|
<!ENTITY consoleErrLine.label "Line:">
|
||||||
<!ENTITY consoleErrColumn.label "Column:">
|
<!ENTITY consoleErrColumn.label "Column:">
|
||||||
|
<!ENTITY consoleFollowCheckbox.label "Follow">
|
||||||
|
|
||||||
<!-- TEXT CONTEXT MENU -->
|
<!-- TEXT CONTEXT MENU -->
|
||||||
<!ENTITY contextTextCut.label "Cut">
|
<!ENTITY contextTextCut.label "Cut">
|
||||||
|
|
|
@ -922,3 +922,8 @@ appbar toolbar[labelled] toolbarbutton > .toolbarbutton-text {
|
||||||
.flyout-narrow .flyoutpanel-hack {
|
.flyout-narrow .flyoutpanel-hack {
|
||||||
max-width: calc(346px - 2 * 40px);
|
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;
|
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 */
|
/* Bookmarking panel */
|
||||||
#editBookmarkPanelStarIcon {
|
#editBookmarkPanelStarIcon {
|
||||||
list-style-image: url("chrome://browser/skin/places/starred48.png");
|
list-style-image: url("chrome://browser/skin/places/starred48.png");
|
||||||
|
|
|
@ -16,3 +16,10 @@
|
||||||
.widget-overflow-list .toolbarbutton-1 > .toolbarbutton-menubutton-dropmarker {
|
.widget-overflow-list .toolbarbutton-1 > .toolbarbutton-menubutton-dropmarker {
|
||||||
-moz-margin-start: 0;
|
-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;
|
margin-top: 1px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.toolbarbutton-1 > menupopup.cui-widget-panel {
|
||||||
|
margin-top: -5px;
|
||||||
|
}
|
||||||
|
|
||||||
/* Common back and forward button styles */
|
/* Common back and forward button styles */
|
||||||
|
|
||||||
#back-button,
|
#back-button,
|
||||||
|
|
|
@ -66,3 +66,8 @@
|
||||||
.widget-overflow-list .toolbarbutton-1 > .toolbarbutton-menubutton-dropmarker {
|
.widget-overflow-list .toolbarbutton-1 > .toolbarbutton-menubutton-dropmarker {
|
||||||
-moz-margin-start: 4px;
|
-moz-margin-start: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.PanelUI-subView menuseparator,
|
||||||
|
.cui-widget-panelview menuseparator {
|
||||||
|
padding: 0 !important;
|
||||||
|
}
|
||||||
|
|
|
@ -54,6 +54,10 @@
|
||||||
-moz-box-flex: 1;
|
-moz-box-flex: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.subviewbutton:not(:-moz-any([image],[targetURI],.cui-withicon)) > .toolbarbutton-text {
|
||||||
|
-moz-margin-start: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.panel-subview-body {
|
.panel-subview-body {
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
overflow-x: hidden;
|
overflow-x: hidden;
|
||||||
|
@ -62,6 +66,7 @@
|
||||||
|
|
||||||
#PanelUI-popup .panel-subview-body {
|
#PanelUI-popup .panel-subview-body {
|
||||||
margin: -4px;
|
margin: -4px;
|
||||||
|
padding: 2px 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.panel-subview-header,
|
.panel-subview-header,
|
||||||
|
@ -154,7 +159,6 @@
|
||||||
panelview:not([mainview]) .toolbarbutton-text,
|
panelview:not([mainview]) .toolbarbutton-text,
|
||||||
.cui-widget-panel toolbarbutton > .toolbarbutton-text {
|
.cui-widget-panel toolbarbutton > .toolbarbutton-text {
|
||||||
text-align: start;
|
text-align: start;
|
||||||
-moz-padding-start: 8px;
|
|
||||||
display: -moz-box;
|
display: -moz-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -335,6 +339,7 @@ toolbarpaletteitem[place="palette"] > toolbaritem > toolbarbutton {
|
||||||
border: 0;
|
border: 0;
|
||||||
border-left: 1px solid rgba(0,0,0,0.1);
|
border-left: 1px solid rgba(0,0,0,0.1);
|
||||||
margin: 7px 0 7px;
|
margin: 7px 0 7px;
|
||||||
|
-moz-appearance: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
#PanelUI-footer-inner:hover > toolbarseparator {
|
#PanelUI-footer-inner:hover > toolbarseparator {
|
||||||
|
@ -529,12 +534,6 @@ panelview .toolbarbutton-1,
|
||||||
margin-right: 4px;
|
margin-right: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.PanelUI-subView menuseparator,
|
|
||||||
.PanelUI-subView toolbarseparator {
|
|
||||||
-moz-margin-start: -5px;
|
|
||||||
-moz-margin-end: -4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
panelview .toolbarbutton-1,
|
panelview .toolbarbutton-1,
|
||||||
.widget-overflow-list .toolbarbutton-1 {
|
.widget-overflow-list .toolbarbutton-1 {
|
||||||
margin-top: 6px;
|
margin-top: 6px;
|
||||||
|
@ -580,12 +579,31 @@ panelview .toolbarbutton-1@buttonStateActive@,
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
panelview toolbarseparator,
|
.PanelUI-subView menuseparator,
|
||||||
#BMB_bookmarksPopup > menuseparator {
|
.PanelUI-subView toolbarseparator,
|
||||||
|
.cui-widget-panelview menuseparator {
|
||||||
-moz-appearance: none;
|
-moz-appearance: none;
|
||||||
min-height: 0;
|
min-height: 0;
|
||||||
border-top: 1px solid ThreeDShadow;
|
border-top: 1px solid hsla(210,4%,10%,.15);
|
||||||
margin: 5px 0;
|
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 {
|
.subviewbutton > .menu-accel-container {
|
||||||
|
@ -782,36 +800,20 @@ toolbarpaletteitem[place="palette"] > #search-container {
|
||||||
box-shadow: 0 0 0 1px hsla(0,0%,100%,.2);
|
box-shadow: 0 0 0 1px hsla(0,0%,100%,.2);
|
||||||
}
|
}
|
||||||
|
|
||||||
#PanelUI-developerItems > toolbarbutton[checked="true"],
|
.PanelUI-subView toolbarbutton[checked="true"] {
|
||||||
#PanelUI-bookmarks > toolbarbutton[checked="true"],
|
|
||||||
#PanelUI-history > toolbarbutton[checked="true"],
|
|
||||||
.PanelUI-characterEncodingView-list > toolbarbutton[current] {
|
|
||||||
-moz-padding-start: 4px;
|
-moz-padding-start: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
#PanelUI-developerItems > toolbarbutton[checked="true"] > .toolbarbutton-text,
|
.PanelUI-subView 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 {
|
|
||||||
-moz-padding-start: 0px;
|
-moz-padding-start: 0px;
|
||||||
}
|
}
|
||||||
|
|
||||||
#BMB_bookmarksPopup > menuitem[checked="true"]::before,
|
.PanelUI-subView menuitem[checked="true"]::before,
|
||||||
#PanelUI-bookmarks > toolbarbutton[checked="true"]::before,
|
.PanelUI-subView toolbarbutton[checked="true"]::before {
|
||||||
#PanelUI-history > toolbarbutton[checked="true"]::before,
|
|
||||||
#PanelUI-developerItems > toolbarbutton[checked="true"]::before,
|
|
||||||
.PanelUI-characterEncodingView-list > toolbarbutton[current]::before {
|
|
||||||
content: "✓";
|
content: "✓";
|
||||||
display: -moz-box;
|
display: -moz-box;
|
||||||
width: 12px;
|
width: 12px;
|
||||||
}
|
margin: 0 2px;
|
||||||
|
|
||||||
#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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#BMB_bookmarksPopup > menuitem[checked="true"] > .menu-iconic-left {
|
#BMB_bookmarksPopup > menuitem[checked="true"] > .menu-iconic-left {
|
||||||
|
|
|
@ -458,6 +458,17 @@ box.requests-menu-status {
|
||||||
margin-bottom: 10px;
|
margin-bottom: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Preview tabpanel */
|
||||||
|
|
||||||
|
#preview-tabpanel {
|
||||||
|
background: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
#response-preview {
|
||||||
|
display: -moz-box;
|
||||||
|
-moz-box-flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
/* Timings tabpanel */
|
/* Timings tabpanel */
|
||||||
|
|
||||||
#timings-tabpanel .tabpanel-summary-label {
|
#timings-tabpanel .tabpanel-summary-label {
|
||||||
|
|
|
@ -505,6 +505,10 @@ menuitem.bookmark-item {
|
||||||
margin-top: -3px;
|
margin-top: -3px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#nav-bar .toolbarbutton-1 > menupopup.cui-widget-panel {
|
||||||
|
margin-top: -8px;
|
||||||
|
}
|
||||||
|
|
||||||
#nav-bar .toolbarbutton-1 > .toolbarbutton-menubutton-button {
|
#nav-bar .toolbarbutton-1 > .toolbarbutton-menubutton-button {
|
||||||
-moz-padding-end: 0;
|
-moz-padding-end: 0;
|
||||||
}
|
}
|
||||||
|
|
|
@ -445,7 +445,7 @@ pref("browser.ui.touch.bottom", 16);
|
||||||
pref("browser.ui.touch.weight.visited", 120); // percentage
|
pref("browser.ui.touch.weight.visited", 120); // percentage
|
||||||
|
|
||||||
// The percentage of the screen that needs to be scrolled before margins are exposed.
|
// 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
|
// Maximum distance from the point where the user pressed where we still
|
||||||
// look for text to select
|
// 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 {
|
public static class ViewConfig implements Parcelable {
|
||||||
private final ViewType mType;
|
private final ViewType mType;
|
||||||
private final String mDatasetId;
|
private final String mDatasetId;
|
||||||
|
private final ItemHandler mItemHandler;
|
||||||
|
|
||||||
private static final String JSON_KEY_TYPE = "type";
|
private static final String JSON_KEY_TYPE = "type";
|
||||||
private static final String JSON_KEY_DATASET = "dataset";
|
private static final String JSON_KEY_DATASET = "dataset";
|
||||||
|
private static final String JSON_KEY_ITEM_HANDLER = "itemHandler";
|
||||||
|
|
||||||
public ViewConfig(JSONObject json) throws JSONException, IllegalArgumentException {
|
public ViewConfig(JSONObject json) throws JSONException, IllegalArgumentException {
|
||||||
mType = ViewType.fromId(json.getString(JSON_KEY_TYPE));
|
mType = ViewType.fromId(json.getString(JSON_KEY_TYPE));
|
||||||
mDatasetId = json.getString(JSON_KEY_DATASET);
|
mDatasetId = json.getString(JSON_KEY_DATASET);
|
||||||
|
mItemHandler = ItemHandler.fromId(json.getString(JSON_KEY_ITEM_HANDLER));
|
||||||
|
|
||||||
validate();
|
validate();
|
||||||
}
|
}
|
||||||
|
@ -487,6 +542,7 @@ public final class HomeConfig {
|
||||||
public ViewConfig(Parcel in) {
|
public ViewConfig(Parcel in) {
|
||||||
mType = (ViewType) in.readParcelable(getClass().getClassLoader());
|
mType = (ViewType) in.readParcelable(getClass().getClassLoader());
|
||||||
mDatasetId = in.readString();
|
mDatasetId = in.readString();
|
||||||
|
mItemHandler = (ItemHandler) in.readParcelable(getClass().getClassLoader());
|
||||||
|
|
||||||
validate();
|
validate();
|
||||||
}
|
}
|
||||||
|
@ -494,13 +550,15 @@ public final class HomeConfig {
|
||||||
public ViewConfig(ViewConfig viewConfig) {
|
public ViewConfig(ViewConfig viewConfig) {
|
||||||
mType = viewConfig.mType;
|
mType = viewConfig.mType;
|
||||||
mDatasetId = viewConfig.mDatasetId;
|
mDatasetId = viewConfig.mDatasetId;
|
||||||
|
mItemHandler = viewConfig.mItemHandler;
|
||||||
|
|
||||||
validate();
|
validate();
|
||||||
}
|
}
|
||||||
|
|
||||||
public ViewConfig(ViewType type, String datasetId) {
|
public ViewConfig(ViewType type, String datasetId, ItemHandler itemHandler) {
|
||||||
mType = type;
|
mType = type;
|
||||||
mDatasetId = datasetId;
|
mDatasetId = datasetId;
|
||||||
|
mItemHandler = itemHandler;
|
||||||
|
|
||||||
validate();
|
validate();
|
||||||
}
|
}
|
||||||
|
@ -513,6 +571,10 @@ public final class HomeConfig {
|
||||||
if (TextUtils.isEmpty(mDatasetId)) {
|
if (TextUtils.isEmpty(mDatasetId)) {
|
||||||
throw new IllegalArgumentException("Can't create ViewConfig with empty dataset ID");
|
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() {
|
public ViewType getType() {
|
||||||
|
@ -523,11 +585,16 @@ public final class HomeConfig {
|
||||||
return mDatasetId;
|
return mDatasetId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ItemHandler getItemHandler() {
|
||||||
|
return mItemHandler;
|
||||||
|
}
|
||||||
|
|
||||||
public JSONObject toJSON() throws JSONException {
|
public JSONObject toJSON() throws JSONException {
|
||||||
final JSONObject json = new JSONObject();
|
final JSONObject json = new JSONObject();
|
||||||
|
|
||||||
json.put(JSON_KEY_TYPE, mType.toString());
|
json.put(JSON_KEY_TYPE, mType.toString());
|
||||||
json.put(JSON_KEY_DATASET, mDatasetId);
|
json.put(JSON_KEY_DATASET, mDatasetId);
|
||||||
|
json.put(JSON_KEY_ITEM_HANDLER, mItemHandler.toString());
|
||||||
|
|
||||||
return json;
|
return json;
|
||||||
}
|
}
|
||||||
|
@ -541,6 +608,7 @@ public final class HomeConfig {
|
||||||
public void writeToParcel(Parcel dest, int flags) {
|
public void writeToParcel(Parcel dest, int flags) {
|
||||||
dest.writeParcelable(mType, 0);
|
dest.writeParcelable(mType, 0);
|
||||||
dest.writeString(mDatasetId);
|
dest.writeString(mDatasetId);
|
||||||
|
dest.writeParcelable(mItemHandler, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static final Creator<ViewConfig> CREATOR = new Creator<ViewConfig>() {
|
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 org.mozilla.gecko.util.HardwareUtils;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
import android.graphics.drawable.Drawable;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.support.v4.app.Fragment;
|
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.
|
// Whether or not we need to restart the loader when we show the HomePager.
|
||||||
private boolean mRestartLoader;
|
private boolean mRestartLoader;
|
||||||
|
|
||||||
|
// Cached original ViewPager background.
|
||||||
|
private final Drawable mOriginalBackground;
|
||||||
|
|
||||||
// This is mostly used by UI tests to easily fetch
|
// This is mostly used by UI tests to easily fetch
|
||||||
// specific list views at runtime.
|
// specific list views at runtime.
|
||||||
static final String LIST_TAG_HISTORY = "history";
|
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
|
// ensure there is always a focusable view. This would ordinarily be done via an XML
|
||||||
// attribute, but it is not working properly.
|
// attribute, but it is not working properly.
|
||||||
setFocusableInTouchMode(true);
|
setFocusableInTouchMode(true);
|
||||||
|
|
||||||
|
mOriginalBackground = getBackground();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -310,9 +316,18 @@ public class HomePager extends ViewPager {
|
||||||
// Update the adapter with the new panel configs
|
// Update the adapter with the new panel configs
|
||||||
adapter.update(enabledPanels);
|
adapter.update(enabledPanels);
|
||||||
|
|
||||||
// Hide the tab strip if the new configuration contains no panels.
|
|
||||||
final int count = enabledPanels.size();
|
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
|
// Re-install the adapter with the final state
|
||||||
// in the pager.
|
// in the pager.
|
||||||
setAdapter(adapter);
|
setAdapter(adapter);
|
||||||
|
|
|
@ -7,6 +7,7 @@ package org.mozilla.gecko.home;
|
||||||
|
|
||||||
import org.mozilla.gecko.R;
|
import org.mozilla.gecko.R;
|
||||||
import org.mozilla.gecko.db.BrowserContract.HomeItems;
|
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.HomeConfig.ViewConfig;
|
||||||
import org.mozilla.gecko.home.HomePager.OnUrlOpenListener;
|
import org.mozilla.gecko.home.HomePager.OnUrlOpenListener;
|
||||||
import org.mozilla.gecko.home.PanelLayout.DatasetBacked;
|
import org.mozilla.gecko.home.PanelLayout.DatasetBacked;
|
||||||
|
@ -27,11 +28,13 @@ public class PanelGridView extends GridView
|
||||||
implements DatasetBacked, PanelView {
|
implements DatasetBacked, PanelView {
|
||||||
private static final String LOGTAG = "GeckoPanelGridView";
|
private static final String LOGTAG = "GeckoPanelGridView";
|
||||||
|
|
||||||
|
private final ViewConfig mViewConfig;
|
||||||
private final PanelGridViewAdapter mAdapter;
|
private final PanelGridViewAdapter mAdapter;
|
||||||
protected OnUrlOpenListener mUrlOpenListener;
|
protected OnUrlOpenListener mUrlOpenListener;
|
||||||
|
|
||||||
public PanelGridView(Context context, ViewConfig viewConfig) {
|
public PanelGridView(Context context, ViewConfig viewConfig) {
|
||||||
super(context, null, R.attr.panelGridViewStyle);
|
super(context, null, R.attr.panelGridViewStyle);
|
||||||
|
mViewConfig = viewConfig;
|
||||||
mAdapter = new PanelGridViewAdapter(context);
|
mAdapter = new PanelGridViewAdapter(context);
|
||||||
setAdapter(mAdapter);
|
setAdapter(mAdapter);
|
||||||
setOnItemClickListener(new PanelGridItemClickListener());
|
setOnItemClickListener(new PanelGridItemClickListener());
|
||||||
|
@ -82,7 +85,12 @@ public class PanelGridView extends GridView
|
||||||
int urlIndex = cursor.getColumnIndexOrThrow(HomeItems.URL);
|
int urlIndex = cursor.getColumnIndexOrThrow(HomeItems.URL);
|
||||||
final String url = cursor.getString(urlIndex);
|
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;
|
package org.mozilla.gecko.home;
|
||||||
|
|
||||||
import org.mozilla.gecko.R;
|
import org.mozilla.gecko.R;
|
||||||
|
import org.mozilla.gecko.home.HomeConfig.ItemHandler;
|
||||||
import org.mozilla.gecko.home.HomeConfig.ViewConfig;
|
import org.mozilla.gecko.home.HomeConfig.ViewConfig;
|
||||||
import org.mozilla.gecko.home.HomePager.OnUrlOpenListener;
|
import org.mozilla.gecko.home.HomePager.OnUrlOpenListener;
|
||||||
import org.mozilla.gecko.home.PanelLayout.DatasetBacked;
|
import org.mozilla.gecko.home.PanelLayout.DatasetBacked;
|
||||||
|
@ -73,7 +74,12 @@ public class PanelListView extends HomeListView
|
||||||
int urlIndex = cursor.getColumnIndexOrThrow(HomeItems.URL);
|
int urlIndex = cursor.getColumnIndexOrThrow(HomeItems.URL);
|
||||||
final String url = cursor.getString(urlIndex);
|
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">
|
<merge xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
|
||||||
<ViewStub android:id="@+id/home_empty_view_stub"
|
<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_width="fill_parent"
|
||||||
android:layout_height="fill_parent"/>
|
android:layout_height="fill_parent"/>
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
<merge xmlns:android="http://schemas.android.com/apk/res/android">
|
<merge xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
|
||||||
<ViewStub android:id="@+id/home_empty_view_stub"
|
<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_width="fill_parent"
|
||||||
android:layout_height="fill_parent"/>
|
android:layout_height="fill_parent"/>
|
||||||
|
|
||||||
|
|
|
@ -8,7 +8,7 @@
|
||||||
android:layout_height="fill_parent">
|
android:layout_height="fill_parent">
|
||||||
|
|
||||||
<ViewStub android:id="@+id/home_empty_view_stub"
|
<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_width="fill_parent"
|
||||||
android:layout_height="fill_parent"/>
|
android:layout_height="fill_parent"/>
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
<merge xmlns:android="http://schemas.android.com/apk/res/android">
|
<merge xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
|
||||||
<ViewStub android:id="@+id/home_empty_view_stub"
|
<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_width="fill_parent"
|
||||||
android:layout_height="fill_parent"/>
|
android:layout_height="fill_parent"/>
|
||||||
|
|
||||||
|
|
|
@ -9,7 +9,7 @@
|
||||||
android:orientation="vertical">
|
android:orientation="vertical">
|
||||||
|
|
||||||
<ViewStub android:id="@+id/home_empty_view_stub"
|
<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_width="fill_parent"
|
||||||
android:layout_height="fill_parent"/>
|
android:layout_height="fill_parent"/>
|
||||||
|
|
||||||
|
|
|
@ -169,6 +169,12 @@ let HomePanels = Object.freeze({
|
||||||
REFRESH: "refresh"
|
REFRESH: "refresh"
|
||||||
}),
|
}),
|
||||||
|
|
||||||
|
// Valid item handlers for a panel view.
|
||||||
|
ItemHandler: Object.freeze({
|
||||||
|
BROWSER: "browser",
|
||||||
|
INTENT: "intent"
|
||||||
|
}),
|
||||||
|
|
||||||
// Holds the currrent set of registered panels.
|
// Holds the currrent set of registered panels.
|
||||||
_panels: {},
|
_panels: {},
|
||||||
|
|
||||||
|
@ -225,6 +231,13 @@ let HomePanels = Object.freeze({
|
||||||
throw "Home.panels: Invalid view type: panel.id = " + panel.id + ", view.type = " + view.type;
|
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) {
|
if (!view.dataset) {
|
||||||
throw "Home.panels: No dataset provided for view: panel.id = " + panel.id + ", view.type = " + view.type;
|
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.
|
/* Any copyright is dedicated to the Public Domain.
|
||||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||||
|
|
||||||
|
"use strict";
|
||||||
|
|
||||||
Cu.import("resource://gre/modules/Promise.jsm");
|
Cu.import("resource://gre/modules/Promise.jsm");
|
||||||
Cu.import("resource://services-common/hawk.js");
|
Cu.import("resource://services-common/hawk.js");
|
||||||
|
|
||||||
|
@ -20,7 +22,6 @@ add_task(function test_now() {
|
||||||
let client = new HawkClient("https://example.com");
|
let client = new HawkClient("https://example.com");
|
||||||
|
|
||||||
do_check_true(client.now() - Date.now() < SECOND_MS);
|
do_check_true(client.now() - Date.now() < SECOND_MS);
|
||||||
run_next_test();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
add_task(function test_updateClockOffset() {
|
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
|
// that it agrees with the server. We are one hour ahead of the server, so
|
||||||
// our offset should be -1 hour.
|
// our offset should be -1 hour.
|
||||||
do_check_true(Math.abs(client.localtimeOffsetMsec + HOUR_MS) <= SECOND_MS);
|
do_check_true(Math.abs(client.localtimeOffsetMsec + HOUR_MS) <= SECOND_MS);
|
||||||
|
|
||||||
run_next_test();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
add_task(function test_authenticated_get_request() {
|
add_task(function test_authenticated_get_request() {
|
||||||
|
@ -105,6 +104,7 @@ add_task(function test_credentials_optional() {
|
||||||
let client = new HawkClient(server.baseURI);
|
let client = new HawkClient(server.baseURI);
|
||||||
let result = yield client.request("/foo", method); // credentials undefined
|
let result = yield client.request("/foo", method); // credentials undefined
|
||||||
do_check_eq(JSON.parse(result).msg, "you're in the friend zone");
|
do_check_eq(JSON.parse(result).msg, "you're in the friend zone");
|
||||||
|
|
||||||
yield deferredStop(server);
|
yield deferredStop(server);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -122,6 +122,7 @@ add_task(function test_server_error() {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
yield client.request("/foo", method, TEST_CREDS);
|
yield client.request("/foo", method, TEST_CREDS);
|
||||||
|
do_throw("Expected an error");
|
||||||
} catch(err) {
|
} catch(err) {
|
||||||
do_check_eq(418, err.code);
|
do_check_eq(418, err.code);
|
||||||
do_check_eq("I am a Teapot", err.message);
|
do_check_eq("I am a Teapot", err.message);
|
||||||
|
@ -144,6 +145,7 @@ add_task(function test_server_error_json() {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
yield client.request("/foo", method, TEST_CREDS);
|
yield client.request("/foo", method, TEST_CREDS);
|
||||||
|
do_throw("Expected an error");
|
||||||
} catch(err) {
|
} catch(err) {
|
||||||
do_check_eq("Cannot get ye flask.", err.error);
|
do_check_eq("Cannot get ye flask.", err.error);
|
||||||
}
|
}
|
||||||
|
@ -243,7 +245,6 @@ add_task(function test_2xx_success() {
|
||||||
do_check_eq(response, "");
|
do_check_eq(response, "");
|
||||||
|
|
||||||
yield deferredStop(server);
|
yield deferredStop(server);
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
add_task(function test_retry_request_on_fail() {
|
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);
|
do_check_true(delta > MINUTE_MS);
|
||||||
let message = "never!!!";
|
let message = "never!!!";
|
||||||
response.setStatusLine(request.httpVersion, 401, "Unauthorized");
|
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
|
// 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!!!";
|
let message = "i love you!!!";
|
||||||
response.setStatusLine(request.httpVersion, 200, "OK");
|
response.setStatusLine(request.httpVersion, 200, "OK");
|
||||||
response.bodyOutputStream.write(message, message.length);
|
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() {
|
add_task(function test_500_no_retry() {
|
||||||
// If we get a 500 error, the client should not retry (as it would with a
|
// If we get a 500 error, the client should not retry (as it would with a
|
||||||
// 401)
|
// 401)
|
||||||
let attempts = 0;
|
|
||||||
let credentials = {
|
let credentials = {
|
||||||
id: "eyJleHBpcmVzIjogMTM2NTAxMDg5OC4x",
|
id: "eyJleHBpcmVzIjogMTM2NTAxMDg5OC4x",
|
||||||
key: "qTZf4ZFpAMpMoeSsX3zVRjiqmNs=",
|
key: "qTZf4ZFpAMpMoeSsX3zVRjiqmNs=",
|
||||||
|
@ -360,7 +362,6 @@ add_task(function test_500_no_retry() {
|
||||||
|
|
||||||
let server = httpd_setup({
|
let server = httpd_setup({
|
||||||
"/no-shutup": function() {
|
"/no-shutup": function() {
|
||||||
attempts += 1;
|
|
||||||
let message = "Cannot get ye flask.";
|
let message = "Cannot get ye flask.";
|
||||||
response.setStatusLine(request.httpVersion, 500, "Internal server error");
|
response.setStatusLine(request.httpVersion, 500, "Internal server error");
|
||||||
response.bodyOutputStream.write(message, message.length);
|
response.bodyOutputStream.write(message, message.length);
|
||||||
|
@ -381,13 +382,12 @@ add_task(function test_500_no_retry() {
|
||||||
// Request will 500; no retries
|
// Request will 500; no retries
|
||||||
try {
|
try {
|
||||||
yield client.request("/no-shutup", method, credentials);
|
yield client.request("/no-shutup", method, credentials);
|
||||||
|
do_throw("Expected an error");
|
||||||
} catch(err) {
|
} catch(err) {
|
||||||
do_check_eq(err.code, 500);
|
do_check_eq(err.code, 500);
|
||||||
}
|
}
|
||||||
do_check_eq(attempts, 1);
|
|
||||||
|
|
||||||
yield deferredStop(server);
|
yield deferredStop(server);
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
add_task(function test_401_then_500() {
|
add_task(function test_401_then_500() {
|
||||||
|
@ -417,7 +417,8 @@ add_task(function test_401_then_500() {
|
||||||
do_check_true(delta > MINUTE_MS);
|
do_check_true(delta > MINUTE_MS);
|
||||||
let message = "never!!!";
|
let message = "never!!!";
|
||||||
response.setStatusLine(request.httpVersion, 401, "Unauthorized");
|
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
|
// 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.";
|
let message = "Cannot get ye flask.";
|
||||||
response.setStatusLine(request.httpVersion, 500, "Internal server error");
|
response.setStatusLine(request.httpVersion, 500, "Internal server error");
|
||||||
response.bodyOutputStream.write(message, message.length);
|
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() {
|
add_task(function throw_if_not_json_body() {
|
||||||
do_test_pending();
|
|
||||||
let client = new HawkClient("https://example.com");
|
let client = new HawkClient("https://example.com");
|
||||||
try {
|
try {
|
||||||
yield client.request("/bogus", "GET", {}, "I am not json");
|
yield client.request("/bogus", "GET", {}, "I am not json");
|
||||||
|
do_throw("Expected an error");
|
||||||
} catch(err) {
|
} catch(err) {
|
||||||
do_check_true(!!err.message);
|
do_check_true(!!err.message);
|
||||||
do_test_finished();
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -274,6 +274,8 @@
|
||||||
"dom/browser-element/mochitest/test_browserElement_inproc_CloseFromOpener.html":"",
|
"dom/browser-element/mochitest/test_browserElement_inproc_CloseFromOpener.html":"",
|
||||||
"dom/browser-element/":"",
|
"dom/browser-element/":"",
|
||||||
|
|
||||||
|
"dom/downloads/tests/test_downloads_pause_resume.html":"bug 947167",
|
||||||
|
|
||||||
"dom/events/test/test_bug226361.xhtml":"",
|
"dom/events/test/test_bug226361.xhtml":"",
|
||||||
"dom/events/test/test_bug238987.html":"",
|
"dom/events/test/test_bug238987.html":"",
|
||||||
"dom/events/test/test_bug409604.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_OpenWindowRejected.html":"",
|
||||||
"dom/browser-element/mochitest/test_browserElement_oop_SecurityChange.html":"",
|
"dom/browser-element/mochitest/test_browserElement_oop_SecurityChange.html":"",
|
||||||
"dom/browser-element/mochitest/test_browserElement_oop_TargetBlank.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_bug226361.xhtml":"",
|
||||||
"dom/events/test/test_bug238987.html":"",
|
"dom/events/test/test_bug238987.html":"",
|
||||||
"dom/events/test/test_bug409604.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>
|
<head>
|
||||||
<title>&aboutAbout.title;</title>
|
<title>&aboutAbout.title;</title>
|
||||||
<link rel="stylesheet" href="chrome://global/skin/about.css" type="text/css"/>
|
<link rel="stylesheet" href="chrome://global/skin/about.css" type="text/css"/>
|
||||||
<script type="application/javascript"><![CDATA[
|
<script type="application/javascript" src="chrome://global/content/aboutAbout.js"></script>
|
||||||
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>
|
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body dir="&locale.dir;">
|
<body dir="&locale.dir;">
|
||||||
|
|
|
@ -12,6 +12,7 @@ toolkit.jar:
|
||||||
content/global/menulist.css (menulist.css)
|
content/global/menulist.css (menulist.css)
|
||||||
content/global/about.js (about.js)
|
content/global/about.js (about.js)
|
||||||
content/global/about.xhtml (about.xhtml)
|
content/global/about.xhtml (about.xhtml)
|
||||||
|
content/global/aboutAbout.js (aboutAbout.js)
|
||||||
content/global/aboutAbout.xhtml (aboutAbout.xhtml)
|
content/global/aboutAbout.xhtml (aboutAbout.xhtml)
|
||||||
content/global/aboutRights.xhtml (aboutRights.xhtml)
|
content/global/aboutRights.xhtml (aboutRights.xhtml)
|
||||||
content/global/aboutRights-unbranded.xhtml (aboutRights-unbranded.xhtml)
|
content/global/aboutRights-unbranded.xhtml (aboutRights-unbranded.xhtml)
|
||||||
|
|
|
@ -463,6 +463,13 @@
|
||||||
return str.replace(/\n/g, "\\n");
|
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.
|
* Add the given token to the pretty printed results.
|
||||||
*
|
*
|
||||||
|
@ -475,7 +482,7 @@
|
||||||
*/
|
*/
|
||||||
function addToken(token, write, options) {
|
function addToken(token, write, options) {
|
||||||
if (token.type.type == "string") {
|
if (token.type.type == "string") {
|
||||||
write("'" + sanitizeNewlines(token.value) + "'",
|
write("'" + sanitizeSingleQuotes(sanitizeNewlines(token.value)) + "'",
|
||||||
token.startLoc.line,
|
token.startLoc.line,
|
||||||
token.startLoc.column);
|
token.startLoc.column);
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -179,7 +179,7 @@ var testCases = [
|
||||||
{
|
{
|
||||||
name: "String with quote",
|
name: "String with quote",
|
||||||
input: "var foo = \"'\";\n",
|
input: "var foo = \"'\";\n",
|
||||||
output: "var foo = '\'';\n"
|
output: "var foo = '\\'';\n"
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
|
|
|
@ -123,15 +123,17 @@ function safeGetState(state) {
|
||||||
let data = JSON.parse(string);
|
let data = JSON.parse(string);
|
||||||
// Simplify the rest of the code by ensuring that we can simply
|
// Simplify the rest of the code by ensuring that we can simply
|
||||||
// concatenate the result to a message.
|
// concatenate the result to a message.
|
||||||
data.toString = function() {
|
if (data && typeof data == "object") {
|
||||||
return string;
|
data.toString = function() {
|
||||||
};
|
return string;
|
||||||
|
};
|
||||||
|
}
|
||||||
return data;
|
return data;
|
||||||
} catch (ex) {
|
} catch (ex) {
|
||||||
try {
|
try {
|
||||||
return "Error getting state: " + ex;
|
return "Error getting state: " + ex + " at " + ex.stack;
|
||||||
} catch (ex2) {
|
} 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
|
//// 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({
|
this.Promise = function Promise(aExecutor)
|
||||||
/**
|
{
|
||||||
* Creates a new pending promise and provides methods to resolve or reject it.
|
if (typeof(aExecutor) != "function") {
|
||||||
*
|
throw new TypeError("Promise constructor must be called with an executor.");
|
||||||
* @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.
|
/*
|
||||||
|
* Internal status of the promise. This can be equal to STATUS_PENDING,
|
||||||
|
* STATUS_RESOLVED, or STATUS_REJECTED.
|
||||||
*/
|
*/
|
||||||
defer: function ()
|
Object.defineProperty(this, N_STATUS, { value: STATUS_PENDING,
|
||||||
{
|
writable: true });
|
||||||
return new Deferred();
|
|
||||||
},
|
/*
|
||||||
|
* 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
|
* When the N_STATUS property is STATUS_REJECTED and until there is
|
||||||
* state of an existing promise.
|
* a rejection callback, this contains an array
|
||||||
*
|
* - {string} id An id for use with |PendingErrors|;
|
||||||
* @param aValue
|
* - {FinalizationWitness} witness A witness broadcasting |id| on
|
||||||
* If this value is not a promise, including "undefined", it becomes
|
* notification "promise-finalization-witness".
|
||||||
* 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.
|
|
||||||
*/
|
*/
|
||||||
resolve: function (aValue)
|
Object.defineProperty(this, N_WITNESS, { writable: true });
|
||||||
{
|
|
||||||
let promise = new PromiseImpl();
|
|
||||||
PromiseWalker.completePromise(promise, STATUS_RESOLVED, aValue);
|
|
||||||
return promise;
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
Object.seal(this);
|
||||||
* 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;
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
let resolve = PromiseWalker.completePromise
|
||||||
* Returns a promise that is resolved or rejected when all values are
|
.bind(PromiseWalker, this, STATUS_RESOLVED);
|
||||||
* resolved or any is rejected.
|
let reject = PromiseWalker.completePromise
|
||||||
*
|
.bind(PromiseWalker, this, STATUS_REJECTED);
|
||||||
* @param aValues
|
|
||||||
* Array of promises that may be pending, resolved, or rejected. When
|
try {
|
||||||
* all are resolved or any is rejected, the returned promise will be
|
Function.prototype.call.call(aExecutor, this, resolve, reject);
|
||||||
* resolved or rejected as well.
|
} catch (ex) {
|
||||||
*
|
reject(ex);
|
||||||
* @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
|
* Calls one of the provided functions as soon as this promise is either
|
||||||
* given promises to be rejected.
|
* resolved or rejected. A new promise is returned, whose state evolves
|
||||||
*/
|
* depending on this promise and the provided callback functions.
|
||||||
all: function (aValues)
|
*
|
||||||
{
|
* The appropriate callback is always invoked after this method returns, even
|
||||||
if (!Array.isArray(aValues)) {
|
* if this promise is already resolved or rejected. You can also call the
|
||||||
throw new Error("Promise.all() expects an array of promises or values.");
|
* "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) {
|
PromiseWalker.schedulePromise(this);
|
||||||
return Promise.resolve([]);
|
}
|
||||||
|
|
||||||
|
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;
|
for (let i = 0; i < aValues.length; i++) {
|
||||||
let deferred = Promise.defer();
|
let index = i;
|
||||||
let resolutionValues = new Array(countdown);
|
let value = aValues[i];
|
||||||
|
let resolve = val => checkForCompletion(val, index);
|
||||||
|
|
||||||
function checkForCompletion(aValue, aIndex) {
|
if (value && typeof(value.then) == "function") {
|
||||||
resolutionValues[aIndex] = aValue;
|
value.then(resolve, deferred.reject);
|
||||||
|
} else {
|
||||||
if (--countdown === 0) {
|
// Given value is not a promise, forward it as a resolution value.
|
||||||
deferred.resolve(resolutionValues);
|
resolve(value);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for (let i = 0; i < aValues.length; i++) {
|
return deferred.promise;
|
||||||
let index = i;
|
};
|
||||||
let value = aValues[i];
|
|
||||||
let resolve = val => checkForCompletion(val, index);
|
|
||||||
|
|
||||||
if (value && typeof(value.then) == "function") {
|
Object.freeze(Promise);
|
||||||
value.then(resolve, deferred.reject);
|
|
||||||
} else {
|
|
||||||
// Given value is not a promise, forward it as a resolution value.
|
|
||||||
resolve(value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return deferred.promise;
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
//// PromiseWalker
|
//// PromiseWalker
|
||||||
|
@ -545,10 +667,10 @@ PromiseWalker.walkerLoop = PromiseWalker.walkerLoop.bind(PromiseWalker);
|
||||||
*/
|
*/
|
||||||
function Deferred()
|
function Deferred()
|
||||||
{
|
{
|
||||||
this.promise = new PromiseImpl();
|
this.promise = new Promise((aResolve, aReject) => {
|
||||||
this.resolve = this.resolve.bind(this);
|
this.resolve = aResolve;
|
||||||
this.reject = this.reject.bind(this);
|
this.reject = aReject;
|
||||||
|
});
|
||||||
Object.freeze(this);
|
Object.freeze(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -576,9 +698,7 @@ Deferred.prototype = {
|
||||||
* and then calling it again with another value before the promise is
|
* and then calling it again with another value before the promise is
|
||||||
* resolved or rejected, has unspecified behavior and should be avoided.
|
* resolved or rejected, has unspecified behavior and should be avoided.
|
||||||
*/
|
*/
|
||||||
resolve: function (aValue) {
|
resolve: null,
|
||||||
PromiseWalker.completePromise(this.promise, STATUS_RESOLVED, aValue);
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Rejects the associated promise with the specified reason. If the promise
|
* 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 equal to the rejected promise itself, not to the
|
||||||
* rejection reason of the rejected promise.
|
* rejection reason of the rejected promise.
|
||||||
*/
|
*/
|
||||||
reject: function (aReason) {
|
reject: null,
|
||||||
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;
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
@ -724,7 +731,7 @@ function Handler(aThisPromise, aOnResolve, aOnReject)
|
||||||
this.thisPromise = aThisPromise;
|
this.thisPromise = aThisPromise;
|
||||||
this.onResolve = aOnResolve;
|
this.onResolve = aOnResolve;
|
||||||
this.onReject = aOnReject;
|
this.onReject = aOnReject;
|
||||||
this.nextPromise = new PromiseImpl();
|
this.nextPromise = new Promise(() => {});
|
||||||
}
|
}
|
||||||
|
|
||||||
Handler.prototype = {
|
Handler.prototype = {
|
||||||
|
|
|
@ -662,6 +662,77 @@ tests.push(
|
||||||
return Promise.all([p1, p2]);
|
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
|
// Test deadlock in Promise.jsm with nested event loops
|
||||||
// The scenario being tested is:
|
// The scenario being tested is:
|
||||||
// promise_1.then({
|
// promise_1.then({
|
||||||
|
|
Загрузка…
Ссылка в новой задаче