Merge m-c to b2g-inbound.
Merge m-c to b2g-inbound.
--- a/browser/base/content/aboutneterror/netError.css
+++ b/browser/base/content/aboutneterror/netError.css
@@ -29,17 +29,16 @@ ul {
#errorPageContainer {
min-width: 320px;
max-width: 512px;
}
#errorTitle {
background: url("info.svg") left 0 no-repeat;
- background-size: 3em 3em;
-moz-margin-start: -5em;
-moz-padding-start: 5em;
}
#errorTitle:-moz-locale-dir(rtl) {
background-position: right 0;
}
--- a/browser/components/customizableui/src/CustomizableUI.jsm
+++ b/browser/components/customizableui/src/CustomizableUI.jsm
@@ -1381,31 +1381,38 @@ let CustomizableUIInternal = {
closemenuVal : "auto";
}
if (isMenuItem && target.hasAttribute("closemenu")) {
let closemenuVal = target.getAttribute("closemenu");
menuitemCloseMenu = (closemenuVal == "single" || closemenuVal == "none") ?
closemenuVal : "auto";
}
+ // Break out of the loop immediately for disabled items, as we need to
+ // keep the menu open in that case.
+ if (target.getAttribute("disabled") == "true") {
+ return true;
+ }
+
// This isn't in the loop condition because we want to break before
// changing |target| if any of these conditions are true
if (inInput || inItem || target == panel) {
break;
}
// We need specific code for popups: the item on which they were invoked
// isn't necessarily in their parentNode chain:
if (isMenuItem) {
let topmostMenuPopup = getMenuPopupForDescendant(target);
target = (topmostMenuPopup && topmostMenuPopup.triggerNode) ||
target.parentNode;
} else {
target = target.parentNode;
}
}
+
// If the user clicked a menu item...
if (inMenu) {
// We care if we're in an input also,
// or if the user specified closemenu!="auto":
if (inInput || menuitemCloseMenu != "auto") {
return true;
}
// Otherwise, we're probably fine to close the panel
--- a/browser/components/customizableui/test/browser_940307_panel_click_closure_handling.js
+++ b/browser/components/customizableui/test/browser_940307_panel_click_closure_handling.js
@@ -54,17 +54,17 @@ add_task(function() {
let menuHidden = promisePanelElementHidden(window, menuPopup);
// Then click the menu item to close all the things
EventUtils.synthesizeMouseAtCenter(menuItem, {});
yield menuHidden;
yield hiddenAgain;
menuButton.remove();
});
-add_task(function() {
+add_task(function*() {
let searchbar = document.getElementById("searchbar");
gCustomizeMode.addToPanel(searchbar);
let placement = CustomizableUI.getPlacementOfWidget("search-container");
is(placement.area, CustomizableUI.AREA_PANEL, "Should be in panel");
yield PanelUI.show();
yield waitForCondition(() => "value" in searchbar && searchbar.value === "");
searchbar.value = "foo";
@@ -86,16 +86,32 @@ add_task(function() {
ok(isPanelUIOpen(), "Panel should still be open");
let hiddenPanelPromise = promisePanelHidden(window);
EventUtils.synthesizeKey("VK_ESCAPE", {});
yield hiddenPanelPromise;
ok(!isPanelUIOpen(), "Panel should no longer be open");
});
+add_task(function*() {
+ button = document.createElement("toolbarbutton");
+ button.id = "browser_946166_button_disabled";
+ button.setAttribute("disabled", "true");
+ button.setAttribute("label", "Button");
+ PanelUI.contents.appendChild(button);
+ yield PanelUI.show();
+ EventUtils.synthesizeMouseAtCenter(button, {});
+ is(PanelUI.panel.state, "open", "Popup stays open");
+ button.removeAttribute("disabled");
+ let hiddenAgain = promisePanelHidden(window);
+ EventUtils.synthesizeMouseAtCenter(button, {});
+ yield hiddenAgain;
+ button.remove();
+});
+
registerCleanupFunction(function() {
if (button && button.parentNode) {
button.remove();
}
if (menuButton && menuButton.parentNode) {
menuButton.remove();
}
// Sadly this isn't task.jsm-enabled, so we can't wait for this to happen. But we should
--- a/browser/components/translation/Translation.jsm
+++ b/browser/components/translation/Translation.jsm
@@ -77,17 +77,20 @@ TranslationUI.prototype = {
let notification =
PopupNotifications.getNotification(removeId, this.browser);
if (notification)
PopupNotifications.remove(notification);
let callback = aTopic => {
if (aTopic != "showing")
return false;
- if (!this.notificationBox.getNotificationWithValue("translation"))
+ let notification = this.notificationBox.getNotificationWithValue("translation");
+ if (notification)
+ notification.close();
+ else
this.showTranslationInfoBar();
return true;
};
let addId = aTranslated ? "translated" : "translate";
PopupNotifications.show(this.browser, addId, null,
addId + "-notification-icon", null, null,
{dismissed: true, eventCallback: callback});
--- a/browser/components/translation/test/browser_translation_infobar.js
+++ b/browser/components/translation/test/browser_translation_infobar.js
@@ -56,16 +56,20 @@ function showTranslationUI(aDetectedLang
let browser = gBrowser.selectedBrowser;
Translation.languageDetected(browser, aDetectedLanguage);
let ui = browser.translationUI;
for (let name of ["translate", "_reset", "failTranslation", "finishTranslation"])
ui[name] = TranslationStub[name];
return ui.notificationBox.getNotificationWithValue("translation");
}
+function hasTranslationInfoBar() {
+ return !!gBrowser.getNotificationBox().getNotificationWithValue("translation");
+}
+
function test() {
waitForExplicitFinish();
let tab = gBrowser.addTab();
gBrowser.selectedTab = tab;
tab.linkedBrowser.addEventListener("load", function onload() {
tab.linkedBrowser.removeEventListener("load", onload, true);
TranslationStub.browser = gBrowser.selectedBrowser;
@@ -178,23 +182,28 @@ function run_tests(aFinishCallback) {
is(notif.state, notif.translation.STATE_TRANSLATING, "the infobar is in the translating state");
ok(!!notif.translation.translatedFrom, "Translation.translate has been called");
is(notif.translation.translatedFrom, "ja", "from language correct");
notif.close();
info("Reopen to check the 'Not Now' button closes the notification.");
notif = showTranslationUI("fr");
let notificationBox = gBrowser.getNotificationBox();
- ok(!!notificationBox.getNotificationWithValue("translation"), "there's a 'translate' notification");
+ is(hasTranslationInfoBar(), true, "there's a 'translate' notification");
notif._getAnonElt("notNow").click();
- ok(!notificationBox.getNotificationWithValue("translation"), "no 'translate' notification after clicking 'not now'");
+ is(hasTranslationInfoBar(), false, "no 'translate' notification after clicking 'not now'");
+
+ info("Reopen to check the url bar icon closes the notification.");
+ notif = showTranslationUI("fr");
+ is(hasTranslationInfoBar(), true, "there's a 'translate' notification");
+ PopupNotifications.getNotification("translate").anchorElement.click();
+ is(hasTranslationInfoBar(), false, "no 'translate' notification after clicking the url bar icon");
info("Check that clicking the url bar icon reopens the info bar");
checkURLBarIcon();
// Clicking the anchor element causes a 'showing' event to be sent
// asynchronously to our callback that will then show the infobar.
PopupNotifications.getNotification("translate").anchorElement.click();
- waitForCondition(() => !!notificationBox.getNotificationWithValue("translation"), () => {
- ok(!!notificationBox.getNotificationWithValue("translation"),
- "there's a 'translate' notification");
+ waitForCondition(hasTranslationInfoBar, () => {
+ ok(hasTranslationInfoBar(), "there's a 'translate' notification");
aFinishCallback();
}, "timeout waiting for the info bar to reappear");
}
--- a/browser/devtools/debugger/test/head.js
+++ b/browser/devtools/debugger/test/head.js
@@ -684,17 +684,17 @@ AddonDebugger.prototype = {
}
function initChromeDebugger(aOnClose) {
info("Initializing a chrome debugger process.");
let deferred = promise.defer();
// Wait for the toolbox process to start...
- BrowserToolboxProcess.init(aOnClose, aProcess => {
+ BrowserToolboxProcess.init(aOnClose, (aEvent, aProcess) => {
info("Browser toolbox process started successfully.");
prepareDebugger(aProcess);
deferred.resolve(aProcess);
});
return deferred.promise;
}
--- a/browser/devtools/inspector/selector-search.js
+++ b/browser/devtools/inspector/selector-search.js
@@ -393,48 +393,57 @@ SelectorSearch.prototype = {
this._onHTMLSearch();
break;
}
},
/**
* Populates the suggestions list and show the suggestion popup.
*/
- _showPopup: function(aList, aFirstPart) {
+ _showPopup: function(aList, aFirstPart, aState) {
let total = 0;
let query = this.searchBox.value;
- let toLowerCase = false;
let items = [];
- // In case of tagNames, change the case to small.
- if (query.match(/.*[\.#][^\.#]{0,}$/) == null) {
- toLowerCase = true;
- }
- for (let [value, count] of aList) {
+
+ for (let [value, count, state] of aList) {
// for cases like 'div ' or 'div >' or 'div+'
if (query.match(/[\s>+]$/)) {
value = query + value;
}
// for cases like 'div #a' or 'div .a' or 'div > d' and likewise
else if (query.match(/[\s>+][\.#a-zA-Z][^\s>+\.#]*$/)) {
let lastPart = query.match(/[\s>+][\.#a-zA-Z][^>\s+\.#]*$/)[0];
value = query.slice(0, -1 * lastPart.length + 1) + value;
}
// for cases like 'div.class' or '#foo.bar' and likewise
else if (query.match(/[a-zA-Z][#\.][^#\.\s+>]*$/)) {
let lastPart = query.match(/[a-zA-Z][#\.][^#\.\s>+]*$/)[0];
value = query.slice(0, -1 * lastPart.length + 1) + value;
}
+
let item = {
preLabel: query,
label: value,
count: count
};
- if (toLowerCase) {
+
+ // In case of tagNames, change te case to small
+ if (value.match(/.*[\.#][^\.#]{0,}$/) == null) {
item.label = value.toLowerCase();
}
+
+ // In case the query's state is tag and the item's state is id or class
+ // adjust the preLabel
+ if (aState === this.States.TAG && state === this.States.CLASS) {
+ item.preLabel = "." + item.preLabel;
+ }
+ if (aState === this.States.TAG && state === this.States.ID) {
+ item.preLabel = "#" + item.preLabel;
+ }
+
items.unshift(item);
if (++total > MAX_SUGGESTIONS - 1) {
break;
}
}
if (total > 0) {
this.searchPopup.setItems(items);
this.searchPopup.openPopup(this.searchBox);
@@ -445,48 +454,53 @@ SelectorSearch.prototype = {
},
/**
* Suggests classes,ids and tags based on the user input as user types in the
* searchbox.
*/
showSuggestions: function() {
let query = this.searchBox.value;
+ let state = this.state;
let firstPart = "";
- if (this.state == this.States.TAG) {
+
+ if (state == this.States.TAG) {
// gets the tag that is being completed. For ex. 'div.foo > s' returns 's',
// 'di' returns 'di' and likewise.
firstPart = (query.match(/[\s>+]?([a-zA-Z]*)$/) || ["", query])[1];
query = query.slice(0, query.length - firstPart.length);
}
- else if (this.state == this.States.CLASS) {
+ else if (state == this.States.CLASS) {
// gets the class that is being completed. For ex. '.foo.b' returns 'b'
firstPart = query.match(/\.([^\.]*)$/)[1];
query = query.slice(0, query.length - firstPart.length - 1);
}
- else if (this.state == this.States.ID) {
+ else if (state == this.States.ID) {
// gets the id that is being completed. For ex. '.foo#b' returns 'b'
firstPart = query.match(/#([^#]*)$/)[1];
query = query.slice(0, query.length - firstPart.length - 1);
}
// TODO: implement some caching so that over the wire request is not made
// everytime.
if (/[\s+>~]$/.test(query)) {
query += "*";
}
+
this._currentSuggesting = query;
- return this.walker.getSuggestionsForQuery(query, firstPart, this.state).then(result => {
+ return this.walker.getSuggestionsForQuery(query, firstPart, state).then(result => {
if (this._currentSuggesting != result.query) {
// This means that this response is for a previous request and the user
// as since typed something extra leading to a new request.
return;
}
this._lastToLastValidSearch = this._lastValidSearch;
- if (this.state == this.States.CLASS) {
+
+ if (state == this.States.CLASS) {
firstPart = "." + firstPart;
}
- else if (this.state == this.States.ID) {
+ else if (state == this.States.ID) {
firstPart = "#" + firstPart;
}
- this._showPopup(result.suggestions, firstPart);
+
+ this._showPopup(result.suggestions, firstPart, state);
});
}
};
--- a/browser/devtools/inspector/test/browser.ini
+++ b/browser/devtools/inspector/test/browser.ini
@@ -45,8 +45,9 @@ support-files =
[browser_inspector_select_last_selected.js]
[browser_inspector_sidebarstate.js]
[browser_inspector_bug_848731_reset_selection_on_delete.js]
[browser_inspector_bug_922125_destroy_on_navigate.js]
[browser_inspector_bug_958456_highlight_comments.js]
[browser_inspector_bug_958169_switch_to_inspector_on_pick.js]
[browser_inspector_bug_961771_picker_stops_on_tool_select.js]
[browser_inspector_bug_962478_picker_stops_on_destroy.js]
+[browser_inspector_search-suggests-ids-and-classes.js]
--- a/browser/devtools/inspector/test/browser_inspector_bug_831693_combinator_suggestions.js
+++ b/browser/devtools/inspector/test/browser_inspector_bug_831693_combinator_suggestions.js
@@ -10,27 +10,27 @@ function test()
// The various states of the inspector: [key, suggestions array]
// [
// what key to press,
// suggestions array with count [
// [suggestion1, count1], [suggestion2] ...
// ] count can be left to represent 1
// ]
let keyStates = [
- ["d", [["div", 4]]],
+ ["d", [["div", 4], ["#d1", 1], ["#d2", 1]]],
["i", [["div", 4]]],
["v", []],
[" ", [["div div", 2], ["div span", 2]]],
[">", [["div >div", 2], ["div >span", 2]]],
["VK_BACK_SPACE", [["div div", 2], ["div span", 2]]],
["+", [["div +span"]]],
["VK_BACK_SPACE", [["div div", 2], ["div span", 2]]],
["VK_BACK_SPACE", []],
["VK_BACK_SPACE", [["div", 4]]],
- ["VK_BACK_SPACE", [["div", 4]]],
+ ["VK_BACK_SPACE", [["div", 4], ["#d1", 1], ["#d2", 1]]],
["VK_BACK_SPACE", []],
["p", []],
[" ", [["p strong"]]],
["+", [["p +button"], ["p +p"]]],
["b", [["p +button"]]],
["u", [["p +button"]]],
["t", [["p +button"]]],
["t", [["p +button"]]],
--- a/browser/devtools/inspector/test/browser_inspector_bug_831693_input_suggestion.js
+++ b/browser/devtools/inspector/test/browser_inspector_bug_831693_input_suggestion.js
@@ -10,25 +10,25 @@ function test()
// The various states of the inspector: [key, suggestions array]
// [
// what key to press,
// suggestions array with count [
// [suggestion1, count1], [suggestion2] ...
// ] count can be left to represent 1
// ]
let keyStates = [
- ["d", [["div", 2]]],
+ ["d", [["div", 2], ["#d1", 1], ["#d2", 1]]],
["i", [["div", 2]]],
["v", []],
[".", [["div.c1"]]],
["VK_BACK_SPACE", []],
["#", [["div#d1"], ["div#d2"]]],
["VK_BACK_SPACE", []],
["VK_BACK_SPACE", [["div", 2]]],
- ["VK_BACK_SPACE", [["div", 2]]],
+ ["VK_BACK_SPACE", [["div", 2], ["#d1", 1], ["#d2", 1]]],
["VK_BACK_SPACE", []],
[".", [[".c1", 3], [".c2"]]],
["c", [[".c1", 3], [".c2"]]],
["2", []],
["VK_BACK_SPACE", [[".c1", 3], [".c2"]]],
["1", []],
["#", [["#d2"], ["#p1"], ["#s2"]]],
["VK_BACK_SPACE", []],
new file mode 100644
--- /dev/null
+++ b/browser/devtools/inspector/test/browser_inspector_search-suggests-ids-and-classes.js
@@ -0,0 +1,125 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Test that the selector-search input proposes ids and classes even when . and
+// # is missing, but that this only occurs when the query is one word (no
+// selector combination)
+
+function test()
+{
+ waitForExplicitFinish();
+
+ let inspector, searchBox, state, popup;
+
+ // The various states of the inspector: [key, suggestions array]
+ // [
+ // what key to press,
+ // suggestions array with count [
+ // [suggestion1, count1], [suggestion2] ...
+ // ] count can be left to represent 1
+ // ]
+ let keyStates = [
+ ["s", [["span", 1], [".span", 1], ["#span", 1]]],
+ ["p", [["span", 1], [".span", 1], ["#span", 1]]],
+ ["a", [["span", 1], [".span", 1], ["#span", 1]]],
+ ["n", []],
+ [" ", [["span div", 1]]],
+ ["d", [["span div", 1]]], // mixed tag/class/id suggestions only work for the first word
+ ["VK_BACK_SPACE", [["span div", 1]]],
+ ["VK_BACK_SPACE", []],
+ ["VK_BACK_SPACE", [["span", 1], [".span", 1], ["#span", 1]]],
+ ["VK_BACK_SPACE", [["span", 1], [".span", 1], ["#span", 1]]],
+ ["VK_BACK_SPACE", [["span", 1], [".span", 1], ["#span", 1]]],
+ ["VK_BACK_SPACE", []],
+ // Test that mixed tags, classes and ids are grouped by types, sorted by
+ // count and alphabetical order
+ ["b", [
+ ["button", 3],
+ ["body", 1],
+ [".bc", 3],
+ [".ba", 1],
+ [".bb", 1],
+ ["#ba", 1],
+ ["#bb", 1],
+ ["#bc", 1]
+ ]],
+ ];
+
+ gBrowser.selectedTab = gBrowser.addTab();
+ gBrowser.selectedBrowser.addEventListener("load", function onload() {
+ gBrowser.selectedBrowser.removeEventListener("load", onload, true);
+ waitForFocus(setupTest, content);
+ }, true);
+
+ content.location = "data:text/html," +
+ "<span class='span' id='span'>" +
+ " <div class='div' id='div'></div>" +
+ "</span>" +
+ "<button class='ba bc' id='bc'></button>" +
+ "<button class='bb bc' id='bb'></button>" +
+ "<button class='bc' id='ba'></button>";
+
+ function $(id) {
+ if (id == null) return null;
+ return content.document.getElementById(id);
+ }
+
+ function setupTest()
+ {
+ openInspector(startTest);
+ }
+
+ function startTest(aInspector)
+ {
+ inspector = aInspector;
+
+ searchBox =
+ inspector.panelWin.document.getElementById("inspector-searchbox");
+ popup = inspector.searchSuggestions.searchPopup;
+
+ focusSearchBoxUsingShortcut(inspector.panelWin, function() {
+ searchBox.addEventListener("command", checkState, true);
+ checkStateAndMoveOn(0);
+ });
+ }
+
+ function checkStateAndMoveOn(index) {
+ if (index == keyStates.length) {
+ finishUp();
+ return;
+ }
+
+ let [key, suggestions] = keyStates[index];
+ state = index;
+
+ info("pressing key " + key + " to get suggestions " +
+ JSON.stringify(suggestions));
+ EventUtils.synthesizeKey(key, {}, inspector.panelWin);
+ }
+
+ function checkState(event) {
+ inspector.searchSuggestions._lastQuery.then(() => {
+ let [key, suggestions] = keyStates[state];
+ let actualSuggestions = popup.getItems();
+ is(popup.isOpen ? actualSuggestions.length: 0, suggestions.length,
+ "There are expected number of suggestions at " + state + "th step.");
+ actualSuggestions.reverse();
+ for (let i = 0; i < suggestions.length; i++) {
+ is(suggestions[i][0], actualSuggestions[i].label,
+ "The suggestion at " + i + "th index for " + state +
+ "th step is correct.")
+ is(suggestions[i][1] || 1, actualSuggestions[i].count,
+ "The count for suggestion at " + i + "th index for " + state +
+ "th step is correct.")
+ }
+ checkStateAndMoveOn(state + 1);
+ });
+ }
+
+ function finishUp() {
+ searchBox = null;
+ popup = null;
+ gBrowser.removeCurrentTab();
+ finish();
+ }
+}
--- a/browser/devtools/sourceeditor/css-autocompleter.js
+++ b/browser/devtools/sourceeditor/css-autocompleter.js
@@ -752,49 +752,64 @@ CSSCompleter.prototype = {
*/
prepareSelectorResults: function(result) {
if (this._currentQuery != result.query)
return [];
result = result.suggestions;
let query = this.selector;
let completion = [];
- for (let value of result) {
+ for (let [value, count, state] of result) {
switch(this.selectorState) {
case SELECTOR_STATES.id:
case SELECTOR_STATES.class:
case SELECTOR_STATES.pseudo:
if (/^[.:#]$/.test(this.completing)) {
- value[0] = query.slice(0, query.length - this.completing.length) +
- value[0];
+ value = query.slice(0, query.length - this.completing.length) +
+ value;
} else {
- value[0] = query.slice(0, query.length - this.completing.length - 1) +
- value[0];
+ value = query.slice(0, query.length - this.completing.length - 1) +
+ value;
}
break;
case SELECTOR_STATES.tag:
- value[0] = query.slice(0, query.length - this.completing.length) +
- value[0];
+ value = query.slice(0, query.length - this.completing.length) +
+ value;
break;
case SELECTOR_STATES.null:
- value[0] = query + value[0];
+ value = query + value;
break;
default:
- value[0] = query.slice(0, query.length - this.completing.length) +
- value[0];
+ value = query.slice(0, query.length - this.completing.length) +
+ value;
}
- completion.push({
- label: value[0],
+
+ let item = {
+ label: value,
preLabel: query,
- text: value[0],
- score: value[1]
- });
+ text: value,
+ score: count
+ };
+
+ // In case the query's state is tag and the item's state is id or class
+ // adjust the preLabel
+ if (this.selectorState === SELECTOR_STATES.tag &&
+ state === SELECTOR_STATES.class) {
+ item.preLabel = "." + item.preLabel;
+ }
+ if (this.selectorState === SELECTOR_STATES.tag &&
+ state === SELECTOR_STATES.id) {
+ item.preLabel = "#" + item.preLabel;
+ }
+
+ completion.push(item);
+
if (completion.length > this.maxEntries - 1)
break;
}
return completion;
},
/**
* Returns CSS property name suggestions based on the input.
--- a/browser/devtools/sourceeditor/test/css_autocompletion_tests.json
+++ b/browser/devtools/sourceeditor/test/css_autocompletion_tests.json
@@ -16,21 +16,21 @@
'-moz-animation-play-state', '-moz-animation-timing-function',
'-moz-appearance']],
[[12, 20], ['none', 'number-input']],
[[12, 22], ['none']],
[[17, 22], ['hsl', 'hsla']],
[[21, 9], ["-moz-calc", "auto", "calc", "inherit", "initial","unset"]],
[[22, 5], ['color', 'color-interpolation', 'color-interpolation-filters']],
[[25, 26], ['.devtools-toolbarbutton > tab',
- '.devtools-toolbarbutton > .toolbarbutton-menubutton-button',
- '.devtools-toolbarbutton > hbox']],
+ '.devtools-toolbarbutton > hbox',
+ '.devtools-toolbarbutton > .toolbarbutton-menubutton-button']],
[[25, 31], ['.devtools-toolbarbutton > hbox.toolbarbutton-menubutton-button']],
[[29, 20], ['.devtools-menulist:after', '.devtools-menulist:active']],
[[30, 10], ['#devtools-anotherone', '#devtools-itjustgoeson', '#devtools-menu',
'#devtools-okstopitnow', '#devtools-toolbarbutton', '#devtools-yetagain']],
[[39, 39], ['.devtools-toolbarbutton:not([label]) > tab']],
[[43, 51], ['.devtools-toolbarbutton:not([checked=true]):hover:after',
'.devtools-toolbarbutton:not([checked=true]):hover:active']],
[[58, 36], ['!important;']],
- [[73, 42], [':last-child', ':lang(', ':last-of-type', ':link']],
+ [[73, 42], [':lang(', ':last-of-type', ':link', ':last-child']],
[[77, 25], ['.visible']],
]
--- a/browser/devtools/styleinspector/ruleview.css
+++ b/browser/devtools/styleinspector/ruleview.css
@@ -55,11 +55,10 @@
line-height: 1.5em;
}
.ruleview-header.ruleview-expandable-header {
cursor: pointer;
}
.ruleview-colorswatch {
- display: inline-block;
cursor: pointer;
}
--- a/browser/experiments/Experiments.jsm
+++ b/browser/experiments/Experiments.jsm
@@ -377,16 +377,20 @@ Experiments.Experiments = function (poli
// * saving the cache (if _dirty)
this._mainTask = null;
// Timer for re-evaluating experiment status.
this._timer = null;
this._shutdown = false;
+ // We need to tell when we first evaluated the experiments to fire an
+ // experiments-changed notification when we only loaded completed experiments.
+ this._firstEvaluate = true;
+
this.init();
};
Experiments.Experiments.prototype = {
QueryInterface: XPCOMUtils.generateQI([Ci.nsITimerCallback, Ci.nsIObserver]),
init: function () {
this._shutdown = false;
@@ -1170,18 +1174,19 @@ Experiments.Experiments.prototype = {
experiment._enabled = false;
yield experiment.reconcileAddonState();
}
}
}
gPrefs.set(PREF_ACTIVE_EXPERIMENT, activeExperiment != null);
- if (activeChanged) {
+ if (activeChanged || this._firstEvaluate) {
Services.obs.notifyObservers(null, EXPERIMENTS_CHANGED_TOPIC, null);
+ this._firstEvaluate = false;
}
if ("@mozilla.org/toolkit/crash-reporter;1" in Cc && activeExperiment) {
try {
gCrashReporter.annotateCrashReport("ActiveExperiment", activeExperiment.id);
} catch (e) {
// It's ok if crash reporting is disabled.
}
--- a/browser/experiments/ExperimentsService.js
+++ b/browser/experiments/ExperimentsService.js
@@ -67,31 +67,43 @@ ExperimentsService.prototype = {
},
observe: function (subject, topic, data) {
switch (topic) {
case "profile-after-change":
if (gExperimentsEnabled) {
Services.obs.addObserver(this, "quit-application", false);
Services.obs.addObserver(this, "sessionstore-state-finalized", false);
+ Services.obs.addObserver(this, "EM-loaded", false);
if (gActiveExperiment) {
this._initialized = true;
Experiments.instance(); // for side effects
}
}
break;
case "sessionstore-state-finalized":
if (!this._initialized) {
CommonUtils.namedTimer(this._delayedInit, DELAY_INIT_MS, this, "_delayedInitTimer");
}
break;
+ case "EM-loaded":
+ if (!this._initialized) {
+ Experiments.instance(); // for side effects
+ this._initialized = true;
+
+ if (this._delayedInitTimer) {
+ this._delayedInitTimer.clear();
+ }
+ }
+ break;
case "quit-application":
Services.obs.removeObserver(this, "quit-application");
Services.obs.removeObserver(this, "sessionstore-state-finalized");
+ Services.obs.removeObserver(this, "EM-loaded");
if (this._delayedInitTimer) {
this._delayedInitTimer.clear();
}
break;
}
},
};
--- a/browser/experiments/test/xpcshell/test_api.js
+++ b/browser/experiments/test/xpcshell/test_api.js
@@ -156,18 +156,18 @@ add_task(function* test_getExperiments()
// Trigger update, clock set to before any activation.
// Use updateManifest() to provide for coverage of that path.
let now = baseDate;
gTimerScheduleOffset = -1;
defineNow(gPolicy, now);
yield experiments.updateManifest();
- Assert.equal(observerFireCount, 0,
- "Experiments observer should not have been called yet.");
+ Assert.equal(observerFireCount, ++expectedObserverFireCount,
+ "Experiments observer should have been called.");
Assert.equal(experiments.getActiveExperimentID(), null,
"getActiveExperimentID should return null");
let list = yield experiments.getExperiments();
Assert.equal(list.length, 0, "Experiment list should be empty.");
let addons = yield getExperimentAddons();
Assert.equal(addons.length, 0, "Precondition: No experiment add-ons are installed.");
@@ -372,18 +372,18 @@ add_task(function* test_addonAlreadyInst
let experiments = new Experiments.Experiments(gPolicy);
// Trigger update, clock set to before any activation.
let now = baseDate;
defineNow(gPolicy, now);
yield experiments.updateManifest();
- Assert.equal(observerFireCount, 0,
- "Experiments observer should not have been called yet.");
+ Assert.equal(observerFireCount, ++expectedObserverFireCount,
+ "Experiments observer should have been called.");
let list = yield experiments.getExperiments();
Assert.equal(list.length, 0, "Experiment list should be empty.");
// Trigger update, clock set for the experiment to start.
now = futureDate(startDate, 10 * MS_IN_ONE_DAY);
defineNow(gPolicy, now);
yield experiments.updateManifest();
@@ -676,18 +676,18 @@ add_task(function* test_installFailure()
let experiments = new Experiments.Experiments(gPolicy);
// Trigger update, clock set to before any activation.
let now = baseDate;
defineNow(gPolicy, now);
yield experiments.updateManifest();
- Assert.equal(observerFireCount, 0,
- "Experiments observer should not have been called yet.");
+ Assert.equal(observerFireCount, ++expectedObserverFireCount,
+ "Experiments observer should have been called.");
let list = yield experiments.getExperiments();
Assert.equal(list.length, 0, "Experiment list should be empty.");
// Trigger update, clock set for experiment 1 & 2 to start,
// invalid hash for experiment 1.
// Order in the manifest matters, so we should start experiment 1,
// fail to install it & start experiment 2 instead.
@@ -782,18 +782,18 @@ add_task(function* test_userDisabledAndU
let experiments = new Experiments.Experiments(gPolicy);
// Trigger update, clock set to before any activation.
let now = baseDate;
defineNow(gPolicy, now);
yield experiments.updateManifest();
- Assert.equal(observerFireCount, 0,
- "Experiments observer should not have been called yet.");
+ Assert.equal(observerFireCount, ++expectedObserverFireCount,
+ "Experiments observer should have been called.");
let list = yield experiments.getExperiments();
Assert.equal(list.length, 0, "Experiment list should be empty.");
// Trigger update, clock set for experiment 1 to start.
now = futureDate(startDate, 10 * MS_IN_ONE_DAY);
defineNow(gPolicy, now);
yield experiments.updateManifest();
@@ -881,18 +881,18 @@ add_task(function* test_updateActiveExpe
let experiments = new Experiments.Experiments(gPolicy);
// Trigger update, clock set to before any activation.
let now = baseDate;
defineNow(gPolicy, now);
yield experiments.updateManifest();
- Assert.equal(observerFireCount, 0,
- "Experiments observer should not have been called yet.");
+ Assert.equal(observerFireCount, ++expectedObserverFireCount,
+ "Experiments observer should have been called.");
let list = yield experiments.getExperiments();
Assert.equal(list.length, 0, "Experiment list should be empty.");
let todayActive = yield experiments.lastActiveToday();
Assert.equal(todayActive, null, "No experiment active today.");
// Trigger update, clock set for the experiment to start.
@@ -972,18 +972,18 @@ add_task(function* test_disableActiveExp
let experiments = new Experiments.Experiments(gPolicy);
// Trigger update, clock set to before any activation.
let now = baseDate;
defineNow(gPolicy, now);
yield experiments.updateManifest();
- Assert.equal(observerFireCount, 0,
- "Experiments observer should not have been called yet.");
+ Assert.equal(observerFireCount, ++expectedObserverFireCount,
+ "Experiments observer should have been called.");
let list = yield experiments.getExperiments();
Assert.equal(list.length, 0, "Experiment list should be empty.");
// Trigger update, clock set for the experiment to start.
now = futureDate(startDate, 10 * MS_IN_ONE_DAY);
defineNow(gPolicy, now);
yield experiments.updateManifest();
@@ -1065,29 +1065,29 @@ add_task(function* test_freezePendingExp
let experiments = new Experiments.Experiments(gPolicy);
// Trigger update, clock set to before any activation.
let now = baseDate;
defineNow(gPolicy, now);
yield experiments.updateManifest();
- Assert.equal(observerFireCount, 0,
- "Experiments observer should not have been called yet.");
+ Assert.equal(observerFireCount, ++expectedObserverFireCount,
+ "Experiments observer should have been called.");
let list = yield experiments.getExperiments();
Assert.equal(list.length, 0, "Experiment list should be empty.");
// Trigger update, clock set for the experiment to start but frozen.
now = futureDate(startDate, 10 * MS_IN_ONE_DAY);
defineNow(gPolicy, now);
gManifestObject.experiments[0].frozen = true;
yield experiments.updateManifest();
- Assert.equal(observerFireCount, 0,
- "Experiments observer should not have been called.");
+ Assert.equal(observerFireCount, expectedObserverFireCount,
+ "Experiments observer should have not been called.");
list = yield experiments.getExperiments();
Assert.equal(list.length, 0, "Experiment list should have no entries yet.");
// Trigger an update with the experiment not being frozen anymore.
now = futureDate(now, 1 * MS_IN_ONE_DAY);
defineNow(gPolicy, now);
@@ -1143,18 +1143,18 @@ add_task(function* test_freezeActiveExpe
let experiments = new Experiments.Experiments(gPolicy);
// Trigger update, clock set to before any activation.
let now = baseDate;
defineNow(gPolicy, now);
yield experiments.updateManifest();
- Assert.equal(observerFireCount, 0,
- "Experiments observer should not have been called yet.");
+ Assert.equal(observerFireCount, ++expectedObserverFireCount,
+ "Experiments observer should have been called.");
let list = yield experiments.getExperiments();
Assert.equal(list.length, 0, "Experiment list should be empty.");
// Trigger update, clock set for the experiment to start.
now = futureDate(startDate, 10 * MS_IN_ONE_DAY);
defineNow(gPolicy, now);
yield experiments.updateManifest();
@@ -1235,18 +1235,18 @@ add_task(function* test_removeActiveExpe
let experiments = new Experiments.Experiments(gPolicy);
// Trigger update, clock set to before any activation.
let now = baseDate;
defineNow(gPolicy, now);
yield experiments.updateManifest();
- Assert.equal(observerFireCount, 0,
- "Experiments observer should not have been called yet.");
+ Assert.equal(observerFireCount, ++expectedObserverFireCount,
+ "Experiments observer should have been called.");
let list = yield experiments.getExperiments();
Assert.equal(list.length, 0, "Experiment list should be empty.");
// Trigger update, clock set for the experiment to start.
now = futureDate(startDate, 10 * MS_IN_ONE_DAY);
defineNow(gPolicy, now);
yield experiments.updateManifest();
@@ -1316,18 +1316,18 @@ add_task(function* test_invalidUrl() {
// Trigger update, clock set for the experiment to start.
let now = futureDate(startDate, 10 * MS_IN_ONE_DAY);
defineNow(gPolicy, now);
gTimerScheduleOffset = null;
yield experiments.updateManifest();
- Assert.equal(observerFireCount, expectedObserverFireCount,
- "Experiments observer should not have been called.");
+ Assert.equal(observerFireCount, ++expectedObserverFireCount,
+ "Experiments observer should have been called.");
Assert.equal(gTimerScheduleOffset, null, "No new timer should have been scheduled.");
let list = yield experiments.getExperiments();
Assert.equal(list.length, 0, "Experiment list should be empty.");
// Cleanup.
Services.obs.removeObserver(observer, OBSERVER_TOPIC);
@@ -1370,18 +1370,18 @@ add_task(function* test_unexpectedUninst
let experiments = new Experiments.Experiments(gPolicy);
// Trigger update, clock set to before any activation.
let now = baseDate;
defineNow(gPolicy, now);
yield experiments.updateManifest();
- Assert.equal(observerFireCount, 0,
- "Experiments observer should not have been called yet.");
+ Assert.equal(observerFireCount, ++expectedObserverFireCount,
+ "Experiments observer should have been called.");
let list = yield experiments.getExperiments();
Assert.equal(list.length, 0, "Experiment list should be empty.");
// Trigger update, clock set for the experiment to start.
now = futureDate(startDate, 10 * MS_IN_ONE_DAY);
defineNow(gPolicy, now);
yield experiments.updateManifest();
--- a/browser/experiments/test/xpcshell/test_disableExperiments.js
+++ b/browser/experiments/test/xpcshell/test_disableExperiments.js
@@ -117,18 +117,18 @@ add_task(function* test_disableExperimen
// Trigger update, clock set to before any activation.
// Use updateManifest() to provide for coverage of that path.
let now = baseDate;
defineNow(gPolicy, now);
yield experiments.updateManifest();
- Assert.equal(observerFireCount, 0,
- "Experiments observer should not have been called yet.");
+ Assert.equal(observerFireCount, ++expectedObserverFireCount,
+ "Experiments observer should have been called.");
let list = yield experiments.getExperiments();
Assert.equal(list.length, 0, "Experiment list should be empty.");
let addons = yield getExperimentAddons();
Assert.equal(addons.length, 0, "Precondition: No experiment add-ons are installed.");
// Trigger update, clock set for experiment 1 to start.
now = futureDate(startDate1, 5 * MS_IN_ONE_DAY);
--- a/browser/themes/linux/devtools/computedview.css
+++ b/browser/themes/linux/devtools/computedview.css
@@ -145,15 +145,29 @@ body {
.link {
padding: 0 3px;
cursor: pointer;
float: right;
}
.computedview-colorswatch {
- display: inline-block;
border-radius: 50%;
width: 1em;
height: 1em;
vertical-align: text-top;
-moz-margin-end: 5px;
+ display: inline-block;
}
+
+.computedview-colorswatch::before {
+ content: '';
+ background-color: #eee;
+ background-image: linear-gradient(45deg, #ccc 25%, transparent 25%, transparent 75%, #ccc 75%, #ccc),
+ linear-gradient(45deg, #ccc 25%, transparent 25%, transparent 75%, #ccc 75%, #ccc);
+ background-size: 12px 12px;
+ background-position: 0 0, 6px 6px;
+ position: absolute;
+ border-radius: 50%;
+ width: 1em;
+ height: 1em;
+ z-index: -1;
+}
--- a/browser/themes/osx/devtools/computedview.css
+++ b/browser/themes/osx/devtools/computedview.css
@@ -163,15 +163,29 @@ body {
.link {
padding: 0 3px;
cursor: pointer;
float: right;
}
.computedview-colorswatch {
- display: inline-block;
border-radius: 50%;
width: 1em;
height: 1em;
vertical-align: text-top;
-moz-margin-end: 5px;
+ display: inline-block;
}
+
+.computedview-colorswatch::before {
+ content: '';
+ background-color: #eee;
+ background-image: linear-gradient(45deg, #ccc 25%, transparent 25%, transparent 75%, #ccc 75%, #ccc),
+ linear-gradient(45deg, #ccc 25%, transparent 25%, transparent 75%, #ccc 75%, #ccc);
+ background-size: 12px 12px;
+ background-position: 0 0, 6px 6px;
+ position: absolute;
+ border-radius: 50%;
+ width: 1em;
+ height: 1em;
+ z-index: -1;
+}
--- a/browser/themes/shared/devtools/dark-theme.css
+++ b/browser/themes/shared/devtools/dark-theme.css
@@ -134,18 +134,17 @@
border-color: hsla(210,8%,5%,.6);
}
.theme-fg-contrast { /* To be used for text on theme-bg-contrast */
color: black;
}
.ruleview-colorswatch,
-.computedview-colorswatch,
-.markupview-colorswatch {
+.computedview-colorswatch {
box-shadow: 0 0 0 1px #818181;
}
/* CodeMirror specific styles.
* Best effort to match the existing theme, some of the colors
* are duplicated here to prevent weirdness in the main theme. */
.CodeMirror { /* Inherit platform specific font sizing and styles */
--- a/browser/themes/shared/devtools/light-theme.css
+++ b/browser/themes/shared/devtools/light-theme.css
@@ -133,18 +133,17 @@
.devtools-toolbar,
.devtools-sidebar-tabs > tabs { /* General toolbar styling */
color: #585959;
background-color: #f0f1f2;
border-color: #aaa;
}
.ruleview-colorswatch,
-.computedview-colorswatch,
-.markupview-colorswatch {
+.computedview-colorswatch {
box-shadow: 0 0 0 1px #c4c4c4;
}
/* CodeMirror specific styles.
* Best effort to match the existing theme, some of the colors
* are duplicated here to prevent weirdness in the main theme. */
.CodeMirror { /* Inherit platform specific font sizing and styles */
--- a/browser/themes/shared/devtools/ruleview.css
+++ b/browser/themes/shared/devtools/ruleview.css
@@ -113,16 +113,31 @@
}
.ruleview-colorswatch {
border-radius: 50%;
width: 1em;
height: 1em;
vertical-align: text-top;
-moz-margin-end: 5px;
+ display: inline-block;
+}
+
+.ruleview-colorswatch::before {
+ content: '';
+ background-color: #eee;
+ background-image: linear-gradient(45deg, #ccc 25%, transparent 25%, transparent 75%, #ccc 75%, #ccc),
+ linear-gradient(45deg, #ccc 25%, transparent 25%, transparent 75%, #ccc 75%, #ccc);
+ background-size: 12px 12px;
+ background-position: 0 0, 6px 6px;
+ position: absolute;
+ border-radius: 50%;
+ width: 1em;
+ height: 1em;
+ z-index: -1;
}
.ruleview-overridden {
text-decoration: line-through;
}
.theme-light .ruleview-overridden {
-moz-text-decoration-color: #667380; /* Content (Text) - Dark Grey */
--- a/browser/themes/windows/browser.css
+++ b/browser/themes/windows/browser.css
@@ -764,16 +764,17 @@ toolbarbutton[sdk-button="true"][cui-are
}
#nav-bar .toolbarbutton-1 > .toolbarbutton-menubutton-button > .toolbarbutton-icon:-moz-locale-dir(rtl),
#nav-bar .toolbarbutton-1 > .toolbarbutton-menubutton-dropmarker > .dropmarker-icon:-moz-locale-dir(ltr) {
border-top-left-radius: 0;
border-bottom-left-radius: 0;
}
+ #nav-bar .toolbarbutton-1:not([disabled=true]) > .toolbarbutton-menubutton-button[open] + .toolbarbutton-menubutton-dropmarker > .dropmarker-icon,
#nav-bar .toolbarbutton-1:not([disabled]):-moz-any(:hover,[open]) > .toolbarbutton-menubutton-button > .toolbarbutton-icon,
#nav-bar .toolbarbutton-1:not([disabled]):hover > .toolbarbutton-menubutton-dropmarker > .dropmarker-icon,
#nav-bar .toolbarbutton-1:not([disabled]):not([checked]):not([open]):not(:active):hover > .toolbarbutton-icon,
#nav-bar .toolbarbutton-1:not([disabled]):not([checked]):not([open]):not(:active):hover > .toolbarbutton-text,
#nav-bar .toolbarbutton-1:not([disabled]):not([checked]):not([open]):not(:active):hover > .toolbarbutton-badge-container,
@conditionalForwardWithUrlbar@ > .toolbarbutton-1:-moz-any([disabled],:not([open]):not([disabled]):not(:active)) > .toolbarbutton-icon {
background-image: linear-gradient(hsla(0,0%,100%,.6), hsla(0,0%,100%,.1));
background-color: transparent;
@@ -806,17 +807,17 @@ toolbarbutton[sdk-button="true"][cui-are
transition-duration: 10ms;
}
%ifdef WINDOWS_AERO
@media (-moz-os-version: windows-vista),
(-moz-os-version: windows-win7) {
%endif
/* < Win8 */
- #nav-bar .toolbarbutton-1 > .toolbarbutton-menubutton-button:not([disabled]):hover:active > .toolbarbutton-icon,
+ #nav-bar .toolbarbutton-1 > .toolbarbutton-menubutton-button:not([disabled=true]):-moz-any(:hover:active, [open]) > .toolbarbutton-icon,
#nav-bar .toolbarbutton-1[open] > .toolbarbutton-menubutton-dropmarker:not([disabled]) > .dropmarker-icon,
#nav-bar .toolbarbutton-1:not([disabled]):-moz-any([open],[checked],:hover:active) > .toolbarbutton-icon,
#nav-bar .toolbarbutton-1:not([disabled]):-moz-any([open],[checked],:hover:active) > .toolbarbutton-text,
#nav-bar .toolbarbutton-1:not([disabled]):-moz-any([open],[checked],:hover:active) > .toolbarbutton-badge-container {
background-image: linear-gradient(hsla(0,0%,100%,.6), hsla(0,0%,100%,.1));
background-color: hsla(210,54%,20%,.15);
border-color: hsla(210,54%,20%,.3) hsla(210,54%,20%,.35) hsla(210,54%,20%,.4);
box-shadow: 0 1px 1px hsla(210,54%,20%,.1) inset,
--- a/browser/themes/windows/devtools/computedview.css
+++ b/browser/themes/windows/devtools/computedview.css
@@ -163,15 +163,29 @@ body {
.link {
padding: 0 3px;
cursor: pointer;
float: right;
}
.computedview-colorswatch {
- display: inline-block;
border-radius: 50%;
width: 1em;
height: 1em;
vertical-align: text-top;
-moz-margin-end: 5px;
+ display: inline-block;
}
+
+.computedview-colorswatch::before {
+ content: '';
+ background-color: #eee;
+ background-image: linear-gradient(45deg, #ccc 25%, transparent 25%, transparent 75%, #ccc 75%, #ccc),
+ linear-gradient(45deg, #ccc 25%, transparent 25%, transparent 75%, #ccc 75%, #ccc);
+ background-size: 12px 12px;
+ background-position: 0 0, 6px 6px;
+ position: absolute;
+ border-radius: 50%;
+ width: 1em;
+ height: 1em;
+ z-index: -1;
+}
--- a/build/mobile/sutagent/android/SUTAgentAndroid.java
+++ b/build/mobile/sutagent/android/SUTAgentAndroid.java
@@ -593,22 +593,18 @@ public class SUTAgentAndroid extends Act
if (eap.contentEquals("peap"))
{
wc.eap.setValue("PEAP");
wc.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_EAP);
wc.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.IEEE8021X);
}
- wc.hiddenSSID = false;
wc.status = WifiConfiguration.Status.ENABLED;
- wc.password.setValue("\"password\"");
- wc.identity.setValue("\"bmoss@mozilla.com\"");
-
if (!wifi.isWifiEnabled())
wifi.setWifiEnabled(true);
while(wifi.getWifiState() != WifiManager.WIFI_STATE_ENABLED)
{
Thread.yield();
if (++lcv > 10000)
return(bRet);
--- a/content/media/omx/OMXCodecWrapper.cpp
+++ b/content/media/omx/OMXCodecWrapper.cpp
@@ -446,16 +446,23 @@ OMXVideoEncoder::SetBitrate(int32_t aKbp
msg->setInt32("videoBitrate", aKbps * 1000 /* kbps -> bps */);
status_t result = mCodec->setParameters(msg);
MOZ_ASSERT(result == OK);
return result == OK ? NS_OK : NS_ERROR_FAILURE;
}
#endif
nsresult
+OMXVideoEncoder::RequestIDRFrame()
+{
+ MOZ_ASSERT(mStarted, "Configure() should be called before RequestIDRFrame().");
+ return mCodec->requestIDRFrame() == OK ? NS_OK : NS_ERROR_FAILURE;
+}
+
+nsresult
OMXAudioEncoder::Configure(int aChannels, int aInputSampleRate,
int aEncodedSampleRate)
{
MOZ_ASSERT(!mStarted);
NS_ENSURE_TRUE(aChannels > 0 && aInputSampleRate > 0 && aEncodedSampleRate >= 0,
NS_ERROR_INVALID_ARG);
--- a/content/media/omx/OMXCodecWrapper.h
+++ b/content/media/omx/OMXCodecWrapper.h
@@ -277,16 +277,22 @@ public:
#endif
/**
* Get current AVC codec config blob. The output format depends on the
* aBlobFormat argument given when Configure() was called.
*/
nsresult GetCodecConfig(nsTArray<uint8_t>* aOutputBuf);
+ /**
+ * Ask codec to generate an instantaneous decoding refresh (IDR) frame
+ * (defined in ISO/IEC 14496-10).
+ */
+ nsresult RequestIDRFrame();
+
protected:
virtual status_t AppendDecoderConfig(nsTArray<uint8_t>* aOutputBuf,
ABuffer* aData) MOZ_OVERRIDE;
// If configured to output MP4 format blob, AVC/H.264 encoder has to replace
// NAL unit start code with the unit length as specified in
// ISO/IEC 14496-15 5.2.3.
virtual void AppendFrame(nsTArray<uint8_t>* aOutputBuf,
--- a/dom/xbl/nsXBLMaybeCompiled.h
+++ b/dom/xbl/nsXBLMaybeCompiled.h
@@ -77,22 +77,27 @@ private:
friend class js::GCMethods<nsXBLMaybeCompiled<UncompiledT> >;
};
/* Add support for JS::Heap<nsXBLMaybeCompiled>. */
namespace js {
template <class UncompiledT>
-struct GCMethods<nsXBLMaybeCompiled<UncompiledT> > : public GCMethods<JSObject *>
+struct GCMethods<nsXBLMaybeCompiled<UncompiledT> >
{
typedef struct GCMethods<JSObject *> Base;
static nsXBLMaybeCompiled<UncompiledT> initial() { return nsXBLMaybeCompiled<UncompiledT>(); }
+ /*
+ * No implementation of kind() is provided to prevent
+ * Root<nsXBLMaybeCompiled<UncompiledT>> from being used.
+ */
+
static bool poisoned(nsXBLMaybeCompiled<UncompiledT> function)
{
return function.IsCompiled() && Base::poisoned(function.GetJSFunction());
}
static bool needsPostBarrier(nsXBLMaybeCompiled<UncompiledT> function)
{
return function.IsCompiled() && Base::needsPostBarrier(function.GetJSFunction());
--- a/gfx/2d/DrawTargetSkia.cpp
+++ b/gfx/2d/DrawTargetSkia.cpp
@@ -272,22 +272,22 @@ struct AutoPaintSetup {
// TODO: We could skip the temporary for operator_source and just
// clear the clip rect. The other operators would be harder
// but could be worth it to skip pushing a group.
if (needsGroup) {
mPaint.setXfermodeMode(SkXfermode::kSrcOver_Mode);
SkPaint temp;
temp.setXfermodeMode(GfxOpToSkiaOp(aOptions.mCompositionOp));
- temp.setAlpha(U8CPU(aOptions.mAlpha*255));
+ temp.setAlpha(U8CPU(aOptions.mAlpha*255+0.5));
//TODO: Get a rect here
mCanvas->saveLayer(nullptr, &temp);
mNeedsRestore = true;
} else {
- mPaint.setAlpha(U8CPU(aOptions.mAlpha*255.0));
+ mPaint.setAlpha(U8CPU(aOptions.mAlpha*255.0+0.5));
mAlpha = aOptions.mAlpha;
}
mPaint.setFilterLevel(SkPaint::kLow_FilterLevel);
}
// TODO: Maybe add an operator overload to access this easier?
SkPaint mPaint;
TempBitmap mTmpBitmap;
--- a/gfx/layers/apz/util/APZCCallbackHelper.cpp
+++ b/gfx/layers/apz/util/APZCCallbackHelper.cpp
@@ -21,100 +21,40 @@ APZCCallbackHelper::HasValidPresShellId(
MOZ_ASSERT(aUtils);
uint32_t presShellId;
nsresult rv = aUtils->GetPresShellId(&presShellId);
MOZ_ASSERT(NS_SUCCEEDED(rv));
return NS_SUCCEEDED(rv) && aMetrics.mPresShellId == presShellId;
}
-/**
- * Expands a given rectangle to the next tile boundary. Note, this will
- * expand the rectangle if it is already on tile boundaries.
- */
-static CSSRect ExpandDisplayPortToTileBoundaries(
- const CSSRect& aDisplayPort,
- const CSSToLayerScale& aLayerPixelsPerCSSPixel)
+static void
+AdjustDisplayPortForScrollDelta(mozilla::layers::FrameMetrics& aFrameMetrics,
+ const CSSPoint& aActualScrollOffset)
{
- // Convert the given rect to layer coordinates so we can inflate to tile
- // boundaries (layer space corresponds to texture pixel space here).
- LayerRect displayPortInLayerSpace = aDisplayPort * aLayerPixelsPerCSSPixel;
-
- // Inflate the rectangle by 1 so that we always push to the next tile
- // boundary. This is desirable to stop from having a rectangle with a
- // moving origin occasionally being smaller when it coincidentally lines
- // up to tile boundaries.
- displayPortInLayerSpace.Inflate(1);
-
- // Now nudge the rectangle to the nearest equal or larger tile boundary.
- int32_t tileWidth = gfxPrefs::LayersTileWidth();
- int32_t tileHeight = gfxPrefs::LayersTileHeight();
- gfxFloat left = tileWidth * floor(displayPortInLayerSpace.x / tileWidth);
- gfxFloat right = tileWidth * ceil(displayPortInLayerSpace.XMost() / tileWidth);
- gfxFloat top = tileHeight * floor(displayPortInLayerSpace.y / tileHeight);
- gfxFloat bottom = tileHeight * ceil(displayPortInLayerSpace.YMost() / tileHeight);
-
- displayPortInLayerSpace = LayerRect(left, top, right - left, bottom - top);
- CSSRect displayPort = displayPortInLayerSpace / aLayerPixelsPerCSSPixel;
-
- return displayPort;
-}
-
-static void
-MaybeAlignAndClampDisplayPort(mozilla::layers::FrameMetrics& aFrameMetrics,
- const CSSPoint& aActualScrollOffset)
-{
- // Correct the display-port by the difference between the requested scroll
- // offset and the resulting scroll offset after setting the requested value.
- if (!aFrameMetrics.GetUseDisplayPortMargins()) {
- CSSRect& displayPort = aFrameMetrics.mDisplayPort;
- displayPort += aFrameMetrics.GetScrollOffset() - aActualScrollOffset;
-
- // Expand the display port to the next tile boundaries, if tiled thebes layers
- // are enabled.
- if (gfxPrefs::LayersTilesEnabled()) {
- // We don't use LayersPixelsPerCSSPixel() here as mCumulativeResolution on
- // this FrameMetrics may be incorrect (and is about to be reset by mZoom).
- displayPort =
- ExpandDisplayPortToTileBoundaries(displayPort + aActualScrollOffset,
- aFrameMetrics.GetZoom() *
- ScreenToLayerScale(1.0))
- - aActualScrollOffset;
- }
-
- // Finally, clamp the display port to the expanded scrollable rect.
- CSSRect scrollableRect = aFrameMetrics.GetExpandedScrollableRect();
- displayPort = scrollableRect.Intersect(displayPort + aActualScrollOffset)
- - aActualScrollOffset;
- } else {
- LayerPoint shift =
- (aFrameMetrics.GetScrollOffset() - aActualScrollOffset) *
- aFrameMetrics.LayersPixelsPerCSSPixel();
- LayerMargin margins = aFrameMetrics.GetDisplayPortMargins();
- margins.left -= shift.x;
- margins.right += shift.x;
- margins.top -= shift.y;
- margins.bottom += shift.y;
- aFrameMetrics.SetDisplayPortMargins(margins);
- }
+ // Correct the display-port by the difference between the requested scroll
+ // offset and the resulting scroll offset after setting the requested value.
+ LayerPoint shift =
+ (aFrameMetrics.GetScrollOffset() - aActualScrollOffset) *
+ aFrameMetrics.LayersPixelsPerCSSPixel();
+ LayerMargin margins = aFrameMetrics.GetDisplayPortMargins();
+ margins.left -= shift.x;
+ margins.right += shift.x;
+ margins.top -= shift.y;
+ margins.bottom += shift.y;
+ aFrameMetrics.SetDisplayPortMargins(margins);
}
static void
RecenterDisplayPort(mozilla::layers::FrameMetrics& aFrameMetrics)
{
- if (!aFrameMetrics.GetUseDisplayPortMargins()) {
- CSSSize compositionSize = aFrameMetrics.CalculateCompositedSizeInCssPixels();
- aFrameMetrics.mDisplayPort.x = (compositionSize.width - aFrameMetrics.mDisplayPort.width) / 2;
- aFrameMetrics.mDisplayPort.y = (compositionSize.height - aFrameMetrics.mDisplayPort.height) / 2;
- } else {
- LayerMargin margins = aFrameMetrics.GetDisplayPortMargins();
- margins.right = margins.left = margins.LeftRight() / 2;
- margins.top = margins.bottom = margins.TopBottom() / 2;
- aFrameMetrics.SetDisplayPortMargins(margins);
- }
+ LayerMargin margins = aFrameMetrics.GetDisplayPortMargins();
+ margins.right = margins.left = margins.LeftRight() / 2;
+ margins.top = margins.bottom = margins.TopBottom() / 2;
+ aFrameMetrics.SetDisplayPortMargins(margins);
}
static CSSPoint
ScrollFrameTo(nsIScrollableFrame* aFrame, const CSSPoint& aPoint, bool& aSuccessOut)
{
aSuccessOut = false;
if (!aFrame) {
@@ -156,16 +96,17 @@ ScrollFrameTo(nsIScrollableFrame* aFrame
}
void
APZCCallbackHelper::UpdateRootFrame(nsIDOMWindowUtils* aUtils,
FrameMetrics& aMetrics)
{
// Precondition checks
MOZ_ASSERT(aUtils);
+ MOZ_ASSERT(aMetrics.GetUseDisplayPortMargins());
if (aMetrics.GetScrollId() == FrameMetrics::NULL_SCROLL_ID) {
return;
}
// Set the scroll port size, which determines the scroll range. For example if
// a 500-pixel document is shown in a 100-pixel frame, the scroll port length would
// be 100, and gecko would limit the maximum scroll offset to 400 (so as to prevent
// overscroll). Note that if the content here was zoomed to 2x, the document would
@@ -175,31 +116,30 @@ APZCCallbackHelper::UpdateRootFrame(nsID
CSSSize scrollPort = aMetrics.CalculateCompositedSizeInCssPixels();
aUtils->SetScrollPositionClampingScrollPortSize(scrollPort.width, scrollPort.height);
// Scroll the window to the desired spot
nsIScrollableFrame* sf = nsLayoutUtils::FindScrollableFrameFor(aMetrics.GetScrollId());
bool scrollUpdated = false;
CSSPoint actualScrollOffset = ScrollFrameTo(sf, aMetrics.GetScrollOffset(), scrollUpdated);
- if (!scrollUpdated) {
- // For whatever reason we couldn't update the scroll offset on the scroll frame,
- // which means the data APZ used for its displayport calculation is stale. Fall
- // back to a sane default behaviour. Note that we don't tile-align the recentered
- // displayport because tile-alignment depends on the scroll position, and the
- // scroll position here is out of our control. See bug 966507 comment 21 for a
- // more detailed explanation.
- RecenterDisplayPort(aMetrics);
+ if (scrollUpdated) {
+ // Correct the display port due to the difference between mScrollOffset and the
+ // actual scroll offset.
+ AdjustDisplayPortForScrollDelta(aMetrics, actualScrollOffset);
+ } else {
+ // For whatever reason we couldn't update the scroll offset on the scroll frame,
+ // which means the data APZ used for its displayport calculation is stale. Fall
+ // back to a sane default behaviour. Note that we don't tile-align the recentered
+ // displayport because tile-alignment depends on the scroll position, and the
+ // scroll position here is out of our control. See bug 966507 comment 21 for a
+ // more detailed explanation.
+ RecenterDisplayPort(aMetrics);
}
- // Correct the display port due to the difference between mScrollOffset and the
- // actual scroll offset, possibly align it to tile boundaries (if tiled layers are
- // enabled), and clamp it to the scrollable rect.
- MaybeAlignAndClampDisplayPort(aMetrics, actualScrollOffset);
-
aMetrics.SetScrollOffset(actualScrollOffset);
// The mZoom variable on the frame metrics stores the CSS-to-screen scale for this
// frame. This scale includes all of the (cumulative) resolutions set on the presShells
// from the root down to this frame. However, when setting the resolution, we only
// want the piece of the resolution that corresponds to this presShell, rather than
// all of the cumulative stuff, so we need to divide out the parent resolutions.
// Finally, we multiply by a ScreenToLayerScale of 1.0f because the goal here is to
@@ -216,49 +156,43 @@ APZCCallbackHelper::UpdateRootFrame(nsID
nsCOMPtr<nsIContent> content = nsLayoutUtils::FindContentFor(aMetrics.GetScrollId());
if (!content) {
return;
}
nsCOMPtr<nsIDOMElement> element = do_QueryInterface(content);
if (!element) {
return;
}
- if (!aMetrics.GetUseDisplayPortMargins()) {
- aUtils->SetDisplayPortForElement(aMetrics.mDisplayPort.x,
- aMetrics.mDisplayPort.y,
- aMetrics.mDisplayPort.width,
- aMetrics.mDisplayPort.height,
- element, 0);
- } else {
- gfx::IntSize alignment = gfxPrefs::LayersTilesEnabled()
- ? gfx::IntSize(gfxPrefs::LayersTileWidth(), gfxPrefs::LayersTileHeight()) :
- gfx::IntSize(0, 0);
- LayerMargin margins = aMetrics.GetDisplayPortMargins();
- aUtils->SetDisplayPortMarginsForElement(margins.left,
- margins.top,
- margins.right,
- margins.bottom,
- alignment.width,
- alignment.height,
- element, 0);
- CSSRect baseCSS = aMetrics.mCompositionBounds / aMetrics.GetZoomToParent();
- nsRect base(baseCSS.x * nsPresContext::AppUnitsPerCSSPixel(),
- baseCSS.y * nsPresContext::AppUnitsPerCSSPixel(),
- baseCSS.width * nsPresContext::AppUnitsPerCSSPixel(),
- baseCSS.height * nsPresContext::AppUnitsPerCSSPixel());
- nsLayoutUtils::SetDisplayPortBaseIfNotSet(content, base);
- }
+
+ gfx::IntSize alignment = gfxPrefs::LayersTilesEnabled()
+ ? gfx::IntSize(gfxPrefs::LayersTileWidth(), gfxPrefs::LayersTileHeight()) :
+ gfx::IntSize(0, 0);
+ LayerMargin margins = aMetrics.GetDisplayPortMargins();
+ aUtils->SetDisplayPortMarginsForElement(margins.left,
+ margins.top,
+ margins.right,
+ margins.bottom,
+ alignment.width,
+ alignment.height,
+ element, 0);
+ CSSRect baseCSS = aMetrics.mCompositionBounds / aMetrics.GetZoomToParent();
+ nsRect base(baseCSS.x * nsPresContext::AppUnitsPerCSSPixel(),
+ baseCSS.y * nsPresContext::AppUnitsPerCSSPixel(),
+ baseCSS.width * nsPresContext::AppUnitsPerCSSPixel(),
+ baseCSS.height * nsPresContext::AppUnitsPerCSSPixel());
+ nsLayoutUtils::SetDisplayPortBaseIfNotSet(content, base);
}
void
APZCCallbackHelper::UpdateSubFrame(nsIContent* aContent,
FrameMetrics& aMetrics)
{
// Precondition checks
MOZ_ASSERT(aContent);
+ MOZ_ASSERT(aMetrics.GetUseDisplayPortMargins());
if (aMetrics.GetScrollId() == FrameMetrics::NULL_SCROLL_ID) {
return;
}
nsCOMPtr<nsIDOMWindowUtils> utils = GetDOMWindowUtils(aContent);
if (!utils) {
return;
}
@@ -267,45 +201,38 @@ APZCCallbackHelper::UpdateSubFrame(nsICo
// be scrolled, so here we only have to set the scroll position and displayport.
nsIScrollableFrame* sf = nsLayoutUtils::FindScrollableFrameFor(aMetrics.GetScrollId());
bool scrollUpdated = false;
CSSPoint actualScrollOffset = ScrollFrameTo(sf, aMetrics.GetScrollOffset(), scrollUpdated);
nsCOMPtr<nsIDOMElement> element = do_QueryInterface(aContent);
if (element) {
- if (!scrollUpdated) {
+ if (scrollUpdated) {
+ AdjustDisplayPortForScrollDelta(aMetrics, actualScrollOffset);
+ } else {
RecenterDisplayPort(aMetrics);
}
- MaybeAlignAndClampDisplayPort(aMetrics, actualScrollOffset);
- if (!aMetrics.GetUseDisplayPortMargins()) {
- utils->SetDisplayPortForElement(aMetrics.mDisplayPort.x,
- aMetrics.mDisplayPort.y,
- aMetrics.mDisplayPort.width,
- aMetrics.mDisplayPort.height,
- element, 0);
- } else {
- gfx::IntSize alignment = gfxPrefs::LayersTilesEnabled()
- ? gfx::IntSize(gfxPrefs::LayersTileWidth(), gfxPrefs::LayersTileHeight()) :
- gfx::IntSize(0, 0);
- LayerMargin margins = aMetrics.GetDisplayPortMargins();
- utils->SetDisplayPortMarginsForElement(margins.left,
- margins.top,
- margins.right,
- margins.bottom,
- alignment.width,
- alignment.height,
- element, 0);
- CSSRect baseCSS = aMetrics.mCompositionBounds / aMetrics.GetZoomToParent();
- nsRect base(baseCSS.x * nsPresContext::AppUnitsPerCSSPixel(),
- baseCSS.y * nsPresContext::AppUnitsPerCSSPixel(),
- baseCSS.width * nsPresContext::AppUnitsPerCSSPixel(),
- baseCSS.height * nsPresContext::AppUnitsPerCSSPixel());
- nsLayoutUtils::SetDisplayPortBaseIfNotSet(aContent, base);
- }
+ gfx::IntSize alignment = gfxPrefs::LayersTilesEnabled()
+ ? gfx::IntSize(gfxPrefs::LayersTileWidth(), gfxPrefs::LayersTileHeight()) :
+ gfx::IntSize(0, 0);
+ LayerMargin margins = aMetrics.GetDisplayPortMargins();
+ utils->SetDisplayPortMarginsForElement(margins.left,
+ margins.top,
+ margins.right,
+ margins.bottom,
+ alignment.width,
+ alignment.height,
+ element, 0);
+ CSSRect baseCSS = aMetrics.mCompositionBounds / aMetrics.GetZoomToParent();
+ nsRect base(baseCSS.x * nsPresContext::AppUnitsPerCSSPixel(),
+ baseCSS.y * nsPresContext::AppUnitsPerCSSPixel(),
+ baseCSS.width * nsPresContext::AppUnitsPerCSSPixel(),
+ baseCSS.height * nsPresContext::AppUnitsPerCSSPixel());
+ nsLayoutUtils::SetDisplayPortBaseIfNotSet(aContent, base);
}
aMetrics.SetScrollOffset(actualScrollOffset);
}
already_AddRefed<nsIDOMWindowUtils>
APZCCallbackHelper::GetDOMWindowUtils(const nsIDocument* aDoc)
{
--- a/gfx/layers/basic/BasicCompositor.cpp
+++ b/gfx/layers/basic/BasicCompositor.cpp
@@ -389,19 +389,16 @@ BasicCompositor::BeginFrame(const nsIntR
// manager styling. We want to ignore that.
intRect.MoveTo(0, 0);
Rect rect = Rect(0, 0, intRect.width, intRect.height);
// Sometimes the invalid region is larger than we want to draw.
nsIntRegion invalidRegionSafe;
invalidRegionSafe.And(aInvalidRegion, intRect);
- // FIXME: Redraw the whole screen in every frame to work around bug 972728.
- invalidRegionSafe = intRect;
-
nsIntRect invalidRect = invalidRegionSafe.GetBounds();
mInvalidRect = IntRect(invalidRect.x, invalidRect.y, invalidRect.width, invalidRect.height);
mInvalidRegion = invalidRegionSafe;
if (aRenderBoundsOut) {
*aRenderBoundsOut = Rect();
}
--- a/gfx/layers/client/CanvasClient.cpp
+++ b/gfx/layers/client/CanvasClient.cpp
@@ -66,28 +66,27 @@ CanvasClient2D::Update(gfx::IntSize aSiz
: gfxContentType::COLOR_ALPHA;
gfxImageFormat format
= gfxPlatform::GetPlatform()->OptimalFormatForContent(contentType);
TextureFlags flags = TextureFlags::DEFAULT;
if (mTextureFlags & TextureFlags::NEEDS_Y_FLIP) {
flags |= TextureFlags::NEEDS_Y_FLIP;
}
+ gfx::SurfaceFormat surfaceFormat = gfx::ImageFormatToSurfaceFormat(format);
if (aLayer->IsGLLayer()) {
// We want a cairo backend here as we don't want to be copying into
// an accelerated backend and we like LockBits to work. This is currently
// the most effective way to make this work.
- mBuffer = CreateBufferTextureClient(gfx::ImageFormatToSurfaceFormat(format),
- flags,
- BackendType::CAIRO);
+ mBuffer = CreateBufferTextureClient(surfaceFormat, flags, BackendType::CAIRO);
} else {
- mBuffer = CreateTextureClientForDrawing(gfx::ImageFormatToSurfaceFormat(format),
- flags,
- gfxPlatform::GetPlatform()->GetPreferredCanvasBackend(),
- aSize);
+ // XXX - We should use CreateTextureClientForDrawing, but we first need
+ // to use double buffering.
+ mBuffer = CreateBufferTextureClient(surfaceFormat, flags,
+ gfxPlatform::GetPlatform()->GetPreferredCanvasBackend());
}
MOZ_ASSERT(mBuffer->CanExposeDrawTarget());
mBuffer->AllocateForSurface(aSize);
bufferCreated = true;
}
if (!mBuffer->Lock(OpenMode::OPEN_WRITE_ONLY)) {
--- a/js/src/gc/GCRuntime.h
+++ b/js/src/gc/GCRuntime.h
@@ -134,16 +134,17 @@ class GCRuntime
bool shouldPreserveJITCode(JSCompartment *comp, int64_t currentTime);
bool drainMarkStack(SliceBudget &sliceBudget, gcstats::Phase phase);
template <class CompartmentIterT> void markWeakReferences(gcstats::Phase phase);
void markWeakReferencesInCurrentGroup(gcstats::Phase phase);
template <class ZoneIterT, class CompartmentIterT> void markGrayReferences();
void markGrayReferencesInCurrentGroup();
void beginSweepPhase(bool lastGC);
void findZoneGroups();
+ bool findZoneEdgesForWeakMaps();
void getNextZoneGroup();
void endMarkingZoneGroup();
void beginSweepingZoneGroup();
bool releaseObservedTypes();
void endSweepingZoneGroup();
bool sweepPhase(SliceBudget &sliceBudget);
void endSweepPhase(JSGCInvocationKind gckind, bool lastGC);
void sweepZones(FreeOp *fop, bool lastGC);
--- a/js/src/gc/Zone.cpp
+++ b/js/src/gc/Zone.cpp
@@ -58,16 +58,21 @@ Zone::~Zone()
if (this == rt->gc.systemZone)
rt->gc.systemZone = nullptr;
#ifdef JS_ION
js_delete(jitZone_);
#endif
}
+bool Zone::init()
+{
+ return gcZoneGroupEdges.init();
+}
+
void
Zone::setNeedsBarrier(bool needs, ShouldUpdateIon updateIon)
{
#ifdef JS_ION
if (updateIon == UpdateIon && needs != ionUsingBarriers_) {
jit::ToggleBarriers(this, needs);
ionUsingBarriers_ = needs;
}
--- a/js/src/gc/Zone.h
+++ b/js/src/gc/Zone.h
@@ -3,16 +3,17 @@
* 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/. */
#ifndef gc_Zone_h
#define gc_Zone_h
#include "mozilla/Atomics.h"
+#include "mozilla/DebugOnly.h"
#include "mozilla/MemoryReporting.h"
#include "jscntxt.h"
#include "jsgc.h"
#include "jsinfer.h"
#include "gc/FindSCCs.h"
@@ -142,16 +143,17 @@ struct Zone : public JS::shadow::Zone,
Sweep,
Finished
};
private:
bool gcScheduled;
GCState gcState;
bool gcPreserveCode;
+ mozilla::DebugOnly<unsigned> gcLastZoneGroupIndex;
public:
bool isCollecting() const {
if (runtimeFromMainThread()->isHeapCollecting())
return gcState != NoGC;
else
return needsBarrier();
}
@@ -223,16 +225,26 @@ struct Zone : public JS::shadow::Zone,
bool isGCSweeping() {
return gcState == Sweep;
}
bool isGCFinished() {
return gcState == Finished;
}
+#ifdef DEBUG
+ /*
+ * For testing purposes, return the index of the zone group which this zone
+ * was swept in in the last GC.
+ */
+ unsigned lastZoneGroupIndex() {
+ return gcLastZoneGroupIndex;
+ }
+#endif
+
/* This is updated by both the main and GC helper threads. */
mozilla::Atomic<size_t, mozilla::ReleaseAcquire> gcBytes;
size_t gcTriggerBytes;
size_t gcMaxMallocBytes;
double gcHeapGrowthFactor;
bool isSystem;
@@ -267,22 +279,33 @@ struct Zone : public JS::shadow::Zone,
* This should be a bool, but Atomic only supports 32-bit and pointer-sized
* types.
*/
mozilla::Atomic<uint32_t, mozilla::ReleaseAcquire> gcMallocGCTriggered;
/* This compartment's gray roots. */
js::Vector<js::GrayRoot, 0, js::SystemAllocPolicy> gcGrayRoots;
+ /*
+ * A set of edges from this zone to other zones.
+ *
+ * This is used during GC while calculating zone groups to record edges that
+ * can't be determined by examining this zone by itself.
+ */
+ typedef js::HashSet<Zone *, js::DefaultHasher<Zone *>, js::SystemAllocPolicy> ZoneSet;
+ ZoneSet gcZoneGroupEdges;
+
/* Per-zone data for use by an embedder. */
void *data;
Zone(JSRuntime *rt);
~Zone();
+ bool init();
+
void findOutgoingEdges(js::gc::ComponentFinder<JS::Zone> &finder);
void discardJitCode(js::FreeOp *fop);
void addSizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf,
size_t *typePool,
size_t *baselineStubsOptimized);
--- a/js/src/irregexp/NativeRegExpMacroAssembler.cpp
+++ b/js/src/irregexp/NativeRegExpMacroAssembler.cpp
@@ -862,23 +862,26 @@ NativeRegExpMacroAssembler::LoadCurrentC
LoadCurrentCharacterUnchecked(cp_offset, characters);
}
void
NativeRegExpMacroAssembler::LoadCurrentCharacterUnchecked(int cp_offset, int characters)
{
IonSpew(SPEW_PREFIX "LoadCurrentCharacterUnchecked(%d, %d)", cp_offset, characters);
- JS_ASSERT(characters == 1);
if (mode_ == ASCII) {
MOZ_ASSUME_UNREACHABLE("Ascii loading not implemented");
} else {
JS_ASSERT(mode_ == JSCHAR);
- masm.load16ZeroExtend(BaseIndex(input_end_pointer, current_position, TimesOne, cp_offset * sizeof(jschar)),
- current_character);
+ JS_ASSERT(characters <= 2);
+ BaseIndex address(input_end_pointer, current_position, TimesOne, cp_offset * sizeof(jschar));
+ if (characters == 2)
+ masm.load32(address, current_character);
+ else
+ masm.load16ZeroExtend(address, current_character);
}
}
void
NativeRegExpMacroAssembler::PopCurrentPosition()
{
IonSpew(SPEW_PREFIX "PopCurrentPosition");
@@ -1230,19 +1233,17 @@ NativeRegExpMacroAssembler::CheckSpecial
default:
return false;
}
}
bool
NativeRegExpMacroAssembler::CanReadUnaligned()
{
- // XXX Bug 1006799 should this be enabled? Unaligned loads can be slow even
- // on platforms where they are supported.
- return false;
+ return true;
}
const uint8_t
NativeRegExpMacroAssembler::word_character_map[] =
{
0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u,
0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u,
0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u,
--- a/js/src/jit/BaselineIC.cpp
+++ b/js/src/jit/BaselineIC.cpp
@@ -1987,36 +1987,21 @@ ICCompare_String::Compiler::generateStub
JS_ASSERT(IsEqualityOp(op));
Register left = masm.extractString(R0, ExtractTemp0);
Register right = masm.extractString(R1, ExtractTemp1);
GeneralRegisterSet regs(availableGeneralRegs(2));
Register scratchReg = regs.takeAny();
- // x86 doesn't have the luxury of a second scratch.
- Register scratchReg2;
- if (regs.empty()) {
- scratchReg2 = BaselineStubReg;
- masm.push(BaselineStubReg);
- } else {
- scratchReg2 = regs.takeAny();
- }
- JS_ASSERT(scratchReg2 != scratchReg);
-
- Label inlineCompareFailed;
- masm.compareStrings(op, left, right, scratchReg2, scratchReg, &inlineCompareFailed);
- masm.tagValue(JSVAL_TYPE_BOOLEAN, scratchReg2, R0);
- if (scratchReg2 == BaselineStubReg)
- masm.pop(BaselineStubReg);
+
+ masm.compareStrings(op, left, right, scratchReg, &failure);
+ masm.tagValue(JSVAL_TYPE_BOOLEAN, scratchReg, R0);
EmitReturnFromIC(masm);
- masm.bind(&inlineCompareFailed);
- if (scratchReg2 == BaselineStubReg)
- masm.pop(BaselineStubReg);
masm.bind(&failure);
EmitStubGuardFailure(masm);
return true;
}
//
// Compare_Boolean
//
@@ -4219,20 +4204,20 @@ ICGetElemNativeCompiler::generateStubCod
Register strExtract = masm.extractString(R1, ExtractTemp1);
// If needsAtomize_ is true, and the string is not already an atom, then atomize the
// string before proceeding.
if (needsAtomize_) {
Label skipAtomize;
// If string is already an atom, skip the atomize.
- masm.branchTestPtr(Assembler::NonZero,
- Address(strExtract, JSString::offsetOfLengthAndFlags()),
- Imm32(JSString::ATOM_BIT),
- &skipAtomize);
+ masm.branchTest32(Assembler::NonZero,
+ Address(strExtract, JSString::offsetOfFlags()),
+ Imm32(JSString::ATOM_BIT),
+ &skipAtomize);
// Stow R0.
EmitStowICValues(masm, 1);
enterStubFrame(masm, R0.scratchReg());
// Atomize the string into a new value.
masm.push(strExtract);
@@ -4424,33 +4409,28 @@ ICGetElem_String::Compiler::generateStub
masm.branchTestInt32(Assembler::NotEqual, R1, &failure);
GeneralRegisterSet regs(availableGeneralRegs(2));
Register scratchReg = regs.takeAny();
// Unbox string in R0.
Register str = masm.extractString(R0, ExtractTemp0);
- // Load string lengthAndFlags
- Address lengthAndFlagsAddr(str, JSString::offsetOfLengthAndFlags());
- masm.loadPtr(lengthAndFlagsAddr, scratchReg);
-
// Check for non-linear strings.
- masm.branchTest32(Assembler::Zero, scratchReg, Imm32(JSString::FLAGS_MASK), &failure);
+ masm.branchIfRope(str, &failure);
// Unbox key.
Register key = masm.extractInt32(R1, ExtractTemp1);
- // Extract length and bounds check.
- masm.rshiftPtr(Imm32(JSString::LENGTH_SHIFT), scratchReg);
- masm.branch32(Assembler::BelowOrEqual, scratchReg, key, &failure);
+ // Bounds check.
+ masm.branch32(Assembler::BelowOrEqual, Address(str, JSString::offsetOfLength()),
+ key, &failure);
// Get char code.
- Address charsAddr(str, JSString::offsetOfChars());
- masm.loadPtr(charsAddr, scratchReg);
+ masm.loadStringChars(str, scratchReg);
masm.load16ZeroExtend(BaseIndex(scratchReg, key, TimesTwo, 0), scratchReg);
// Check if char code >= UNIT_STATIC_LIMIT.
masm.branch32(Assembler::AboveOrEqual, scratchReg, Imm32(StaticStrings::UNIT_STATIC_LIMIT),
&failure);
// Load static string.
masm.movePtr(ImmPtr(&cx->staticStrings().unitStaticTable), str);
--- a/js/src/jit/CodeGenerator.cpp
+++ b/js/src/jit/CodeGenerator.cpp
@@ -4,16 +4,17 @@
* 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/. */
#include "jit/CodeGenerator.h"
#include "mozilla/Assertions.h"
#include "mozilla/Attributes.h"
#include "mozilla/DebugOnly.h"
+#include "mozilla/MathAlgorithms.h"
#include "jslibmath.h"
#include "jsmath.h"
#include "jsnum.h"
#include "jsprf.h"
#include "builtin/Eval.h"
#include "builtin/TypedObject.h"
@@ -3345,16 +3346,17 @@ CodeGenerator::emitDebugResultChecks(LIn
case MIRType_Object:
case MIRType_String:
return emitObjectOrStringResultChecks(ins, mir);
case MIRType_Value:
return emitValueResultChecks(ins, mir);
default:
return true;
}
+ return true;
}
#endif
bool
CodeGenerator::generateBody()
{
IonScriptCounts *counts = maybeCreateScriptCounts();
@@ -3607,32 +3609,118 @@ CodeGenerator::visitNewObjectVMCall(LNew
if (ReturnReg != objReg)
masm.movePtr(ReturnReg, objReg);
restoreLive(lir);
return true;
}
+static bool
+ShouldInitFixedSlots(LInstruction *lir, JSObject *templateObj)
+{
+ // Look for StoreFixedSlot instructions following an object allocation
+ // that write to this object before a GC is triggered or this object is
+ // passed to a VM call. If all fixed slots will be initialized, the
+ // allocation code doesn't need to set the slots to |undefined|.
+
+ uint32_t nfixed = templateObj->numUsedFixedSlots();
+ if (nfixed == 0)
+ return false;
+
+ // Only optimize if all fixed slots are initially |undefined|, so that we
+ // can assume incremental pre-barriers are not necessary. See also the
+ // comment below.
+ for (uint32_t slot = 0; slot < nfixed; slot++) {
+ if (!templateObj->getSlot(slot).isUndefined())
+ return true;
+ }
+
+ // Keep track of the fixed slots that are initialized. initializedSlots is
+ // a bit mask with a bit for each slot.
+ MOZ_ASSERT(nfixed <= JSObject::MAX_FIXED_SLOTS);
+ static_assert(JSObject::MAX_FIXED_SLOTS <= 32, "Slot bits must fit in 32 bits");
+ uint32_t initializedSlots = 0;
+ uint32_t numInitialized = 0;
+
+ MInstruction *allocMir = lir->mirRaw()->toInstruction();
+ MBasicBlock *block = allocMir->block();
+
+ // Skip the allocation instruction.
+ MInstructionIterator iter = block->begin(allocMir);
+ MOZ_ASSERT(*iter == allocMir);
+ iter++;
+
+ while (true) {
+ for (; iter != block->end(); iter++) {
+ if (iter->isNop() || iter->isConstant() || iter->isPostWriteBarrier()) {
+ // These instructions won't trigger a GC or read object slots.
+ continue;
+ }
+
+ if (iter->isStoreFixedSlot()) {
+ MStoreFixedSlot *store = iter->toStoreFixedSlot();
+ if (store->object() != allocMir)
+ return true;
+
+ // We may not initialize this object slot on allocation, so the
+ // pre-barrier could read uninitialized memory. Simply disable
+ // the barrier for this store: the object was just initialized
+ // so the barrier is not necessary.
+ store->setNeedsBarrier(false);
+
+ uint32_t slot = store->slot();
+ MOZ_ASSERT(slot < nfixed);
+ if ((initializedSlots & (1 << slot)) == 0) {
+ numInitialized++;
+ initializedSlots |= (1 << slot);
+
+ if (numInitialized == nfixed) {
+ // All fixed slots will be initialized.
+ MOZ_ASSERT(mozilla::CountPopulation32(initializedSlots) == nfixed);
+ return false;
+ }
+ }
+ continue;
+ }
+
+ if (iter->isGoto()) {
+ block = iter->toGoto()->target();
+ if (block->numPredecessors() != 1)
+ return true;
+ break;
+ }
+
+ // Unhandled instruction, assume it bails or reads object slots.
+ return true;
+ }
+ iter = block->begin();
+ }
+
+ MOZ_ASSUME_UNREACHABLE("Shouldn't get here");
+}
+
bool
CodeGenerator::visitNewObject(LNewObject *lir)
{
JS_ASSERT(gen->info().executionMode() == SequentialExecution);
Register objReg = ToRegister(lir->output());
Register tempReg = ToRegister(lir->temp());
JSObject *templateObject = lir->mir()->templateObject();
if (lir->mir()->shouldUseVM())
return visitNewObjectVMCall(lir);
OutOfLineNewObject *ool = new(alloc()) OutOfLineNewObject(lir);
if (!addOutOfLineCode(ool))
return false;
- masm.createGCObject(objReg, tempReg, templateObject, lir->mir()->initialHeap(), ool->entry());
+ bool initFixedSlots = ShouldInitFixedSlots(lir, templateObject);
+ masm.createGCObject(objReg, tempReg, templateObject, lir->mir()->initialHeap(), ool->entry(),
+ initFixedSlots);
masm.bind(ool->rejoin());
return true;
}
bool
CodeGenerator::visitOutOfLineNewObject(OutOfLineNewObject *ool)
{
@@ -3657,17 +3745,19 @@ CodeGenerator::visitNewDeclEnvObject(LNe
// If we have a template object, we can inline call object creation.
OutOfLineCode *ool = oolCallVM(NewDeclEnvObjectInfo, lir,
(ArgList(), ImmGCPtr(info.funMaybeLazy()),
Imm32(gc::DefaultHeap)),
StoreRegisterTo(objReg));
if (!ool)
return false;
- masm.createGCObject(objReg, tempReg, templateObj, gc::DefaultHeap, ool->entry());
+ bool initFixedSlots = ShouldInitFixedSlots(lir, templateObj);
+ masm.createGCObject(objReg, tempReg, templateObj, gc::DefaultHeap, ool->entry(),
+ initFixedSlots);
masm.bind(ool->rejoin());
return true;
}
typedef JSObject *(*NewCallObjectFn)(JSContext *, HandleShape, HandleTypeObject);
static const VMFunction NewCallObjectInfo =
FunctionInfo<NewCallObjectFn>(NewCallObject);
@@ -3683,17 +3773,19 @@ CodeGenerator::visitNewCallObject(LNewCa
OutOfLineCode *ool = oolCallVM(NewCallObjectInfo, lir,
(ArgList(), ImmGCPtr(templateObj->lastProperty()),
ImmGCPtr(templateObj->type())),
StoreRegisterTo(objReg));
if (!ool)
return false;
// Inline call object creation, using the OOL path only for tricky cases.
- masm.createGCObject(objReg, tempReg, templateObj, gc::DefaultHeap, ool->entry());
+ bool initFixedSlots = ShouldInitFixedSlots(lir, templateObj);
+ masm.createGCObject(objReg, tempReg, templateObj, gc::DefaultHeap, ool->entry(),
+ initFixedSlots);
masm.bind(ool->rejoin());
return true;
}
typedef JSObject *(*NewSingletonCallObjectFn)(JSContext *, HandleShape);
static const VMFunction NewSingletonCallObjectInfo =
FunctionInfo<NewSingletonCallObjectFn>(NewSingletonCallObject);
@@ -4033,17 +4125,19 @@ CodeGenerator::visitCreateThisWithTempla
if (!ool)
return false;
// Allocate. If the FreeList is empty, call to VM, which may GC.
masm.newGCThing(objReg, tempReg, templateObject, lir->mir()->initialHeap(), ool->entry());
// Initialize based on the templateObject.
masm.bind(ool->rejoin());
- masm.initGCThing(objReg, tempReg, templateObject);
+
+ bool initFixedSlots = ShouldInitFixedSlots(lir, templateObject);
+ masm.initGCThing(objReg, tempReg, templateObject, initFixedSlots);
return true;
}
typedef JSObject *(*NewIonArgumentsObjectFn)(JSContext *cx, IonJSFrameLayout *frame, HandleObject);
static const VMFunction NewIonArgumentsObjectInfo =
FunctionInfo<NewIonArgumentsObjectFn>((NewIonArgumentsObjectFn) ArgumentsObject::createForIon);
@@ -4575,75 +4669,73 @@ static const VMFunctionsModal StringsEqu
FunctionInfo<StringCompareFn>(jit::StringsEqual<true>),
FunctionInfo<StringCompareParFn>(jit::StringsEqualPar));
static const VMFunctionsModal StringsNotEqualInfo = VMFunctionsModal(
FunctionInfo<StringCompareFn>(jit::StringsEqual<false>),
FunctionInfo<StringCompareParFn>(jit::StringsUnequalPar));
bool
CodeGenerator::emitCompareS(LInstruction *lir, JSOp op, Register left, Register right,
- Register output, Register temp)
+ Register output)
{
JS_ASSERT(lir->isCompareS() || lir->isCompareStrictS());
OutOfLineCode *ool = nullptr;
if (op == JSOP_EQ || op == JSOP_STRICTEQ) {
ool = oolCallVM(StringsEqualInfo, lir, (ArgList(), left, right), StoreRegisterTo(output));
} else {
JS_ASSERT(op == JSOP_NE || op == JSOP_STRICTNE);
ool = oolCallVM(StringsNotEqualInfo, lir, (ArgList(), left, right), StoreRegisterTo(output));
}
if (!ool)
return false;
- masm.compareStrings(op, left, right, output, temp, ool->entry());
+ masm.compareStrings(op, left, right, output, ool->entry());
masm.bind(ool->rejoin());
return true;
}
bool
CodeGenerator::visitCompareStrictS(LCompareStrictS *lir)
{
JSOp op = lir->mir()->jsop();
JS_ASSERT(op == JSOP_STRICTEQ || op == JSOP_STRICTNE);
const ValueOperand leftV = ToValue(lir, LCompareStrictS::Lhs);
Register right = ToRegister(lir->right());
Register output = ToRegister(lir->output());
- Register temp = ToRegister(lir->temp());
Register tempToUnbox = ToTempUnboxRegister(lir->tempToUnbox());
Label string, done;
masm.branchTestString(Assembler::Equal, leftV, &string);
masm.move32(Imm32(op == JSOP_STRICTNE), output);
masm.jump(&done);
masm.bind(&string);
Register left = masm.extractString(leftV, tempToUnbox);
- if (!emitCompareS(lir, op, left, right, output, temp))
+ if (!emitCompareS(lir, op, left, right, output))
return false;
masm.bind(&done);
return true;
}
bool
CodeGenerator::visitCompareS(LCompareS *lir)
{
JSOp op = lir->mir()->jsop();
Register left = ToRegister(lir->left());
Register right = ToRegister(lir->right());
Register output = ToRegister(lir->output());
- Register temp = ToRegister(lir->temp());
-
- return emitCompareS(lir, op, left, right, output, temp);
+
+ return emitCompareS(lir, op, left, right, output);
}
typedef bool (*CompareFn)(JSContext *, MutableHandleValue, MutableHandleValue, bool *);
typedef bool (*CompareParFn)(ForkJoinContext *, MutableHandleValue, MutableHandleValue, bool *);
static const VMFunctionsModal EqInfo = VMFunctionsModal(
FunctionInfo<CompareFn>(jit::LooselyEqual<true>),
FunctionInfo<CompareParFn>(jit::LooselyEqualPar));
static const VMFunctionsModal NeInfo = VMFunctionsModal(
@@ -5061,19 +5153,18 @@ JitCompartment::generateStringConcatStub
masm.pop(temp2);
masm.pop(temp1);
break;
default:
MOZ_ASSUME_UNREACHABLE("No such execution mode");
}
// Store lengthAndFlags.
- JS_STATIC_ASSERT(JSString::ROPE_FLAGS == 0);
- masm.lshiftPtr(Imm32(JSString::LENGTH_SHIFT), temp2);
- masm.storePtr(temp2, Address(output, JSString::offsetOfLengthAndFlags()));
+ masm.store32(Imm32(JSString::ROPE_FLAGS), Address(output, JSString::offsetOfFlags()));
+ masm.store32(temp2, Address(output, JSString::offsetOfLength()));
// Store left and right nodes.
masm.storePtr(lhs, Address(output, JSRope::offsetOfLeft()));
masm.storePtr(rhs, Address(output, JSRope::offsetOfRight()));
masm.ret();
masm.bind(&leftEmpty);
masm.mov(rhs, output);
@@ -5082,22 +5173,19 @@ JitCompartment::generateStringConcatStub
masm.bind(&rightEmpty);
masm.mov(lhs, output);
masm.ret();
masm.bind(&isFatInline);
// State: lhs length in temp1, result length in temp2.
- // Ensure both strings are linear (flags != 0).
- JS_STATIC_ASSERT(JSString::ROPE_FLAGS == 0);
- masm.branchTestPtr(Assembler::Zero, Address(lhs, JSString::offsetOfLengthAndFlags()),
- Imm32(JSString::FLAGS_MASK), &failure);
- masm.branchTestPtr(Assembler::Zero, Address(rhs, JSString::offsetOfLengthAndFlags()),
- Imm32(JSString::FLAGS_MASK), &failure);
+ // Ensure both strings are linear.
+ masm.branchIfRope(lhs, &failure);
+ masm.branchIfRope(rhs, &failure);
// Allocate a JSFatInlineString.
switch (mode) {
case SequentialExecution:
masm.newGCFatInlineString(output, temp3, &failure);
break;
case ParallelExecution:
masm.push(temp1);
@@ -5105,41 +5193,39 @@ JitCompartment::generateStringConcatStub
masm.newGCFatInlineStringPar(output, forkJoinContext, temp1, temp2, &failurePopTemps);
masm.pop(temp2);
masm.pop(temp1);
break;
default:
MOZ_ASSUME_UNREACHABLE("No such execution mode");
}
- // Set lengthAndFlags.
- masm.lshiftPtr(Imm32(JSString::LENGTH_SHIFT), temp2);
- masm.orPtr(Imm32(JSString::FIXED_FLAGS), temp2);
- masm.storePtr(temp2, Address(output, JSString::offsetOfLengthAndFlags()));
-
- // Set chars pointer, keep in temp2 for copy loop below.
- masm.computeEffectiveAddress(Address(output, JSFatInlineString::offsetOfInlineStorage()), temp2);
- masm.storePtr(temp2, Address(output, JSFatInlineString::offsetOfChars()));
+ // Set length and flags.
+ masm.store32(Imm32(JSString::INIT_FAT_INLINE_FLAGS), Address(output, JSString::offsetOfFlags()));
+ masm.store32(temp2, Address(output, JSString::offsetOfLength()));
+
+ // Load chars pointer in temp2.
+ masm.computeEffectiveAddress(Address(output, JSInlineString::offsetOfInlineStorage()), temp2);
{
// We use temp3 in this block, which in parallel execution also holds
// a live ForkJoinContext pointer. If we are compiling for parallel
// execution, be sure to save and restore the ForkJoinContext.
if (mode == ParallelExecution)
masm.push(temp3);
// Copy lhs chars. Temp1 still holds the lhs length. Note that this
// advances temp2 to point to the next char. Note that this also
// repurposes the lhs register.
- masm.loadPtr(Address(lhs, JSString::offsetOfChars()), lhs);
+ masm.loadStringChars(lhs, lhs);
CopyStringChars(masm, temp2, lhs, temp1, temp3);
// Copy rhs chars.
masm.loadStringLength(rhs, temp1);
- masm.loadPtr(Address(rhs, JSString::offsetOfChars()), rhs);
+ masm.loadStringChars(rhs, rhs);
CopyStringChars(masm, temp2, rhs, temp1, temp3);
if (mode == ParallelExecution)
masm.pop(temp3);
}
// Null-terminate.
masm.store16(Imm32(0), Address(temp2, 0));
@@ -5236,22 +5322,19 @@ CodeGenerator::visitCharCodeAt(LCharCode
Register str = ToRegister(lir->str());
Register index = ToRegister(lir->index());
Register output = ToRegister(lir->output());
OutOfLineCode *ool = oolCallVM(CharCodeAtInfo, lir, (ArgList(), str, index), StoreRegisterTo(output));
if (!ool)
return false;
- Address lengthAndFlagsAddr(str, JSString::offsetOfLengthAndFlags());
- masm.branchTest32(Assembler::Zero, lengthAndFlagsAddr, Imm32(JSString::FLAGS_MASK), ool->entry());
-
- // getChars
- Address charsAddr(str, JSString::offsetOfChars());
- masm.loadPtr(charsAddr, output);
+ masm.branchIfRope(str, ool->entry());
+
+ masm.loadStringChars(str, output);
masm.load16ZeroExtend(BaseIndex(output, index, TimesTwo, 0), output);
masm.bind(ool->rejoin());
return true;
}
typedef JSFlatString *(*StringFromCharCodeFn)(JSContext *, int32_t);
static const VMFunction StringFromCharCodeInfo = FunctionInfo<StringFromCharCodeFn>(jit::StringFromCharCode);
@@ -6440,20 +6523,28 @@ CodeGenerator::link(JSContext *cx, types
/* resetUses */ false, /* cancelOffThread*/ false))
{
return false;
}
}
// Check to make sure we didn't have a mid-build invalidation. If so, we
// will trickle to jit::Compile() and return Method_Skipped.
+ uint32_t useCount = script->getUseCount();
types::RecompileInfo recompileInfo;
if (!types::FinishCompilation(cx, script, executionMode, constraints, &recompileInfo))
return true;
+ // IonMonkey could have inferred better type information during
+ // compilation. Since adding the new information to the actual type
+ // information can reset the usecount, increase it back to what it was
+ // before.
+ if (useCount > script->getUseCount())
+ script->incUseCount(useCount - script->getUseCount());
+
uint32_t scriptFrameSize = frameClass_ == FrameSizeClass::None()
? frameDepth_
: FrameSizeClass::FromDepth(frameDepth_).frameSize();
// We encode safepoints after the OSI-point offsets have been determined.
encodeSafepoints();
// List of possible scripts that this graph may call. Currently this is
@@ -8574,28 +8665,41 @@ CodeGenerator::visitAssertRangeV(LAssert
masm.assumeUnreachable("Incorrect range for Value.");
masm.bind(&done);
return true;
}
typedef bool (*RecompileFn)(JSContext *);
static const VMFunction RecompileFnInfo = FunctionInfo<RecompileFn>(Recompile);
+typedef bool (*ForcedRecompileFn)(JSContext *);
+static const VMFunction ForcedRecompileFnInfo = FunctionInfo<ForcedRecompileFn>(ForcedRecompile);
+
bool
CodeGenerator::visitRecompileCheck(LRecompileCheck *ins)
{
Label done;
Register tmp = ToRegister(ins->scratch());
- OutOfLineCode *ool = oolCallVM(RecompileFnInfo, ins, (ArgList()), StoreRegisterTo(tmp));
+ OutOfLineCode *ool;
+ if (ins->mir()->forceRecompilation())
+ ool = oolCallVM(ForcedRecompileFnInfo, ins, (ArgList()), StoreRegisterTo(tmp));
+ else
+ ool = oolCallVM(RecompileFnInfo, ins, (ArgList()), StoreRegisterTo(tmp));
// Check if usecount is high enough.
- masm.movePtr(ImmPtr(ins->mir()->script()->addressOfUseCount()), tmp);
- Address ptr(tmp, 0);
- masm.add32(Imm32(1), tmp);
- masm.branch32(Assembler::BelowOrEqual, ptr, Imm32(ins->mir()->recompileThreshold()), &done);
+ AbsoluteAddress useCount = AbsoluteAddress(ins->mir()->script()->addressOfUseCount());
+ if (ins->mir()->increaseUseCount()) {
+ masm.load32(useCount, tmp);
+ masm.add32(Imm32(1), tmp);
+ masm.store32(tmp, useCount);
+ masm.branch32(Assembler::BelowOrEqual, tmp, Imm32(ins->mir()->recompileThreshold()), &done);
+ } else {
+ masm.branch32(Assembler::BelowOrEqual, useCount, Imm32(ins->mir()->recompileThreshold()),
+ &done);
+ }
// Check if not yet recompiling.
CodeOffsetLabel label = masm.movWithPatch(ImmWord(uintptr_t(-1)), tmp);
if (!ionScriptLabels_.append(label))
return false;
masm.branch32(Assembler::Equal,
Address(tmp, IonScript::offsetOfRecompiling()),
Imm32(0),
--- a/js/src/jit/CodeGenerator.h
+++ b/js/src/jit/CodeGenerator.h
@@ -198,18 +198,17 @@ class CodeGenerator : public CodeGenerat
bool visitPowI(LPowI *lir);
bool visitPowD(LPowD *lir);
bool visitRandom(LRandom *lir);
bool visitMathFunctionD(LMathFunctionD *ins);
bool visitMathFunctionF(LMathFunctionF *ins);
bool visitModD(LModD *ins);
bool visitMinMaxI(LMinMaxI *lir);
bool visitBinaryV(LBinaryV *lir);
- bool emitCompareS(LInstruction *lir, JSOp op, Register left, Register right,
- Register output, Register temp);
+ bool emitCompareS(LInstruction *lir, JSOp op, Register left, Register right, Register output);
bool visitCompareS(LCompareS *lir);
bool visitCompareStrictS(LCompareStrictS *lir);
bool visitCompareVM(LCompareVM *lir);
bool visitIsNullOrLikeUndefined(LIsNullOrLikeUndefined *lir);
bool visitIsNullOrLikeUndefinedAndBranch(LIsNullOrLikeUndefinedAndBranch *lir);
bool visitEmulatesUndefined(LEmulatesUndefined *lir);
bool visitEmulatesUndefinedAndBranch(LEmulatesUndefinedAndBranch *lir);
bool emitConcat(LInstruction *lir, Register lhs, Register rhs, Register output);
--- a/js/src/jit/Ion.cpp
+++ b/js/src/jit/Ion.cpp
@@ -1510,16 +1510,29 @@ OptimizeMIR(MIRGenerator *mir)
return false;
IonSpewPass("DCE");
AssertExtendedGraphCoherency(graph);
if (mir->shouldCancel("DCE"))
return false;
}
+ // Make loops contiguious. We do this after GVN/UCE and range analysis,
+ // which can remove CFG edges, exposing more blocks that can be moved.
+ {
+ AutoTraceLog log(logger, TraceLogger::MakeLoopsContiguous);
+ if (!MakeLoopsContiguous(graph))
+ return false;
+ IonSpewPass("Make loops contiguous");
+ AssertExtendedGraphCoherency(graph);
+
+ if (mir->shouldCancel("Make loops contiguous"))
+ return false;
+ }
+
// Passes after this point must not move instructions; these analyses
// depend on knowing the final order in which instructions will execute.
if (mir->optimizationInfo().edgeCaseAnalysisEnabled()) {
AutoTraceLog log(logger, TraceLogger::EdgeCaseAnalysis);
EdgeCaseAnalysis edgeCaseAnalysis(mir, graph);
if (!edgeCaseAnalysis.analyzeLate())
return false;
@@ -2059,17 +2072,17 @@ GetOptimizationLevel(HandleScript script
JS_ASSERT(executionMode == SequentialExecution);
return js_IonOptimizations.levelForScript(script, pc);
}
static MethodStatus
Compile(JSContext *cx, HandleScript script, BaselineFrame *osrFrame, jsbytecode *osrPc,
- bool constructing, ExecutionMode executionMode)
+ bool constructing, ExecutionMode executionMode, bool forceRecompile = false)
{
JS_ASSERT(jit::IsIonEnabled(cx));
JS_ASSERT(jit::IsBaselineEnabled(cx));
JS_ASSERT_IF(osrPc != nullptr, LoopEntryCanIonOsr(osrPc));
JS_ASSERT_IF(executionMode == ParallelExecution, !osrFrame && !osrPc);
JS_ASSERT_IF(executionMode == ParallelExecution, !HasIonScript(script, executionMode));
if (!script->hasBaselineScript())
@@ -2096,45 +2109,27 @@ Compile(JSContext *cx, HandleScript scri
if (optimizationLevel == Optimization_DontCompile)
return Method_Skipped;
IonScript *scriptIon = GetIonScript(script, executionMode);
if (scriptIon) {
if (!scriptIon->method())
return Method_CantCompile;
- MethodStatus failedState = Method_Compiled;
-
- // If we keep failing to enter the script due to an OSR pc mismatch,
- // recompile with the right pc.
- if (osrPc && script->ionScript()->osrPc() != osrPc) {
- uint32_t count = script->ionScript()->incrOsrPcMismatchCounter();
- if (count <= js_JitOptions.osrPcMismatchesBeforeRecompile)
- return Method_Skipped;
-
- failedState = Method_Skipped;
- }
-
// Don't recompile/overwrite higher optimized code,
// with a lower optimization level.
- if (optimizationLevel < scriptIon->optimizationLevel())
- return failedState;
-
- if (optimizationLevel == scriptIon->optimizationLevel() &&
- (!osrPc || script->ionScript()->osrPc() == osrPc))
- {
- return failedState;
- }
+ if (optimizationLevel <= scriptIon->optimizationLevel() && !forceRecompile)
+ return Method_Compiled;
// Don't start compiling if already compiling
if (scriptIon->isRecompiling())
- return failedState;
+ return Method_Compiled;
if (osrPc)
- script->ionScript()->resetOsrPcMismatchCounter();
+ scriptIon->resetOsrPcMismatchCounter();
recompile = true;
}
AbortReason reason = IonCompile(cx, script, osrFrame, osrPc, constructing, executionMode,
recompile, optimizationLevel);
if (reason == AbortReason_Error)
return Method_Error;
@@ -2143,21 +2138,18 @@ Compile(JSContext *cx, HandleScript scri
return Method_CantCompile;
if (reason == AbortReason_Alloc) {
js_ReportOutOfMemory(cx);
return Method_Error;
}
// Compilation succeeded or we invalidated right away or an inlining/alloc abort
- if (HasIonScript(script, executionMode)) {
- if (osrPc && script->ionScript()->osrPc() != osrPc)
- return Method_Skipped;
+ if (HasIonScript(script, executionMode))
return Method_Compiled;
- }
return Method_Skipped;
}
} // namespace jit
} // namespace js
// Decide if a transition from interpreter execution to Ion code should occur.
// May compile or recompile the target JSScript.
@@ -2186,29 +2178,45 @@ jit::CanEnterAtBranch(JSContext *cx, JSS
return Method_Skipped;
// Mark as forbidden if frame can't be handled.
if (!CheckFrame(osrFrame)) {
ForbidCompilation(cx, script);
return Method_CantCompile;
}
+ // By default a recompilation doesn't happen on osr mismatch.
+ // Decide if we want to force a recompilation if this happens too much.
+ bool force = false;
+ if (script->hasIonScript() && pc != script->ionScript()->osrPc()) {
+ uint32_t count = script->ionScript()->incrOsrPcMismatchCounter();
+ if (count <= js_JitOptions.osrPcMismatchesBeforeRecompile)
+ return Method_Skipped;
+ force = true;
+ }
+
// Attempt compilation.
// - Returns Method_Compiled if the right ionscript is present
// (Meaning it was present or a sequantial compile finished)
- // - Returns Method_Skipped if pc doesn't match
- // (This means a background thread compilation with that pc could have started or not.)
RootedScript rscript(cx, script);
- MethodStatus status = Compile(cx, rscript, osrFrame, pc, isConstructing, SequentialExecution);
+ MethodStatus status =
+ Compile(cx, rscript, osrFrame, pc, isConstructing, SequentialExecution, force);
if (status != Method_Compiled) {
if (status == Method_CantCompile)
ForbidCompilation(cx, script);
return status;
}
+ // Return the compilation was skipped when the osr pc wasn't adjusted.
+ // This can happen when there was still an IonScript available and a
+ // background compilation started, but hasn't finished yet.
+ // Or when we didn't force a recompile.
+ if (pc != script->ionScript()->osrPc())
+ return Method_Skipped;
+
return Method_Compiled;
}
MethodStatus
jit::CanEnter(JSContext *cx, RunState &state)
{
JS_ASSERT(jit::IsIonEnabled(cx));
@@ -2309,24 +2317,24 @@ jit::CompileFunctionForBaseline(JSContex
return status;
}
return Method_Compiled;
}
MethodStatus
jit::Recompile(JSContext *cx, HandleScript script, BaselineFrame *osrFrame, jsbytecode *osrPc,
- bool constructing)
+ bool constructing, bool force)
{
JS_ASSERT(script->hasIonScript());
if (script->ionScript()->isRecompiling())
return Method_Compiled;
MethodStatus status =
- Compile(cx, script, osrFrame, osrPc, constructing, SequentialExecution);
+ Compile(cx, script, osrFrame, osrPc, constructing, SequentialExecution, force);
if (status != Method_Compiled) {
if (status == Method_CantCompile)
ForbidCompilation(cx, script);
return status;
}
return Method_Compiled;
}
--- a/js/src/jit/Ion.h
+++ b/js/src/jit/Ion.h
@@ -90,17 +90,17 @@ MethodStatus CanEnter(JSContext *cx, Run
MethodStatus CompileFunctionForBaseline(JSContext *cx, HandleScript script, BaselineFrame *frame,
bool isConstructing);
MethodStatus CanEnterUsingFastInvoke(JSContext *cx, HandleScript script, uint32_t numActualArgs);
MethodStatus CanEnterInParallel(JSContext *cx, HandleScript script);
MethodStatus
Recompile(JSContext *cx, HandleScript script, BaselineFrame *osrFrame, jsbytecode *osrPc,
- bool constructing);
+ bool constructing, bool force);
enum IonExecStatus
{
// The method call had to be aborted due to a stack limit check. This
// error indicates that Ion never attempted to clean up frames.
IonExec_Aborted,
// The method call resulted in an error, and IonMonkey has cleaned up
--- a/js/src/jit/IonAnalysis.cpp
+++ b/js/src/jit/IonAnalysis.cpp
@@ -2558,8 +2558,101 @@ jit::AnalyzeArgumentsUsage(JSContext *cx
// arguments. The compiler can then assume that accesses through
// arguments[i] will be on unaliased variables.
if (script->funHasAnyAliasedFormal() && argumentsContentsObserved)
return true;
script->setNeedsArgsObj(false);
return true;
}
+
+// Reorder the blocks in the loop starting at the given header to be contiguous.
+static void
+MakeLoopContiguous(MIRGraph &graph, MBasicBlock *header, MBasicBlock *backedge, size_t numMarked)
+{
+ MOZ_ASSERT(header->isMarked(), "Loop header is not part of loop");
+ MOZ_ASSERT(backedge->isMarked(), "Loop backedge is not part of loop");
+
+ // If there are any blocks between the loop header and the loop backedge
+ // that are not part of the loop, prepare to move them to the end. We keep
+ // them in order, which preserves RPO.
+ ReversePostorderIterator insertIter = graph.rpoBegin(backedge);
+ insertIter++;
+ MBasicBlock *insertPt = *insertIter;
+
+ // Visit all the blocks from the loop header to the loop backedge.
+ size_t headerId = header->id();
+ size_t inLoopId = headerId;
+ size_t afterLoopId = inLoopId + numMarked;
+ ReversePostorderIterator i = graph.rpoBegin(header);
+ for (;;) {
+ MBasicBlock *block = *i++;
+ MOZ_ASSERT(block->id() >= header->id() && block->id() <= backedge->id(),
+ "Loop backedge should be last block in loop");
+
+ if (block->isMarked()) {
+ // This block is in the loop.
+ block->unmark();
+ block->setId(inLoopId++);
+ // If we've reached the loop backedge, we're done!
+ if (block == backedge)
+ break;
+ } else {
+ // This block is not in the loop. Move it to the end.
+ graph.moveBlockBefore(insertPt, block);
+ block->setId(afterLoopId++);
+ }
+ }
+ MOZ_ASSERT(header->id() == headerId, "Loop header id changed");
+ MOZ_ASSERT(inLoopId == headerId + numMarked, "Wrong number of blocks kept in loop");
+ MOZ_ASSERT(afterLoopId == (insertIter != graph.rpoEnd() ? insertPt->id() : graph.numBlocks()),
+ "Wrong number of blocks moved out of loop");
+}
+
+// Reorder the blocks in the graph so that loops are contiguous.
+bool
+jit::MakeLoopsContiguous(MIRGraph &graph)
+{
+ MBasicBlock *osrBlock = graph.osrBlock();
+ Vector<MBasicBlock *, 1, IonAllocPolicy> inlooplist(graph.alloc());
+
+ // Visit all loop headers (in any order).
+ for (MBasicBlockIterator i(graph.begin()); i != graph.end(); i++) {
+ MBasicBlock *header = *i;
+ if (!header->isLoopHeader())
+ continue;
+
+ // Mark all the blocks in the loop by marking all blocks in a path
+ // between the backedge and the loop header.
+ MBasicBlock *backedge = header->backedge();
+ size_t numMarked = 1;
+ backedge->mark();
+ if (!inlooplist.append(backedge))
+ return false;
+ do {
+ MBasicBlock *block = inlooplist.popCopy();
+ MOZ_ASSERT(block->id() >= header->id() && block->id() <= backedge->id(),
+ "Non-OSR predecessor of loop block not between header and backedge");
+ if (block == header)
+ continue;
+ for (size_t p = 0; p < block->numPredecessors(); p++) {
+ MBasicBlock *pred = block->getPredecessor(p);
+ if (pred->isMarked())
+ continue;
+ // Ignore paths entering the loop in the middle from an OSR
+ // entry. They won't pass through the loop header and they
+ // aren't part of the loop.
+ if (osrBlock && osrBlock->dominates(pred) && !osrBlock->dominates(header))
+ continue;
+ ++numMarked;
+ pred->mark();
+ if (!inlooplist.append(pred))
+ return false;
+ }
+ } while (!inlooplist.empty());
+
+ // Move all blocks between header and backedge that aren't marked to
+ // the end of the loop, making the loop itself contiguous.
+ MakeLoopContiguous(graph, header, backedge, numMarked);
+ }
+
+ return true;
+}
--- a/js/src/jit/IonAnalysis.h
+++ b/js/src/jit/IonAnalysis.h
@@ -27,16 +27,19 @@ enum Observability {
ConservativeObservability,
AggressiveObservability
};
bool
EliminatePhis(MIRGenerator *mir, MIRGraph &graph, Observability observe);
bool
+MakeLoopsContiguous(MIRGraph &graph);
+
+bool
EliminateDeadResumePointOperands(MIRGenerator *mir, MIRGraph &graph);
bool
EliminateDeadCode(MIRGenerator *mir, MIRGraph &graph);
bool
ApplyTypeInformation(MIRGenerator *mir, MIRGraph &graph);
--- a/js/src/jit/IonBuilder.cpp
+++ b/js/src/jit/IonBuilder.cpp
@@ -4171,17 +4171,19 @@ IonBuilder::makeInliningDecision(JSFunct
return DontInline(targetScript, "Vetoed: callee excessively large");
// Callee must have been called a few times to have somewhat stable
// type information, except for definite properties analysis,
// as the caller has not run yet.
if (targetScript->getUseCount() < optimizationInfo().usesBeforeInlining() &&
info().executionMode() != DefinitePropertiesAnalysis)
{
- return DontInline(targetScript, "Vetoed: callee is insufficiently hot.");
+ IonSpew(IonSpew_Inlining, "Cannot inline %s:%u: callee is insufficiently hot.",
+ targetScript->filename(), targetScript->lineno());
+ return InliningDecision_UseCountTooLow;
}
}
// TI calls ObjectStateChange to trigger invalidation of the caller.
types::TypeObjectKey *targetType = types::TypeObjectKey::get(target);
targetType->watchStateChangeForInlinedCall(constraints());
return InliningDecision_Inline;
@@ -4206,16 +4208,17 @@ IonBuilder::selectInliningTargets(Object
for (size_t i = 0; i < targets.length(); i++) {
JSFunction *target = &targets[i]->as<JSFunction>();
bool inlineable;
InliningDecision decision = makeInliningDecision(target, callInfo);
switch (decision) {
case InliningDecision_Error:
return false;
case InliningDecision_DontInline:
+ case InliningDecision_UseCountTooLow:
inlineable = false;
break;
case InliningDecision_Inline:
inlineable = true;
break;
default:
MOZ_ASSUME_UNREACHABLE("Unhandled InliningDecision value!");
}
@@ -4326,16 +4329,18 @@ IonBuilder::inlineCallsite(ObjectVector
if (!propCache && targets.length() == 1) {
JSFunction *target = &targets[0]->as<JSFunction>();
InliningDecision decision = makeInliningDecision(target, callInfo);
switch (decision) {
case InliningDecision_Error:
return InliningStatus_Error;
case InliningDecision_DontInline:
return InliningStatus_NotInlined;
+ case InliningDecision_UseCountTooLow:
+ return InliningStatus_UseCountTooLow;
case InliningDecision_Inline:
break;
}
// Inlining will elminate uses of the original callee, but it needs to
// be preserved in phis if we bail out. Mark the old callee definition as
// implicitly used to ensure this happens.
callInfo.fun()->setImplicitlyUsedUnchecked();
@@ -4950,16 +4955,17 @@ IonBuilder::jsop_funcall(uint32_t argc)
// Try to inline the call.
if (!zeroArguments) {
InliningDecision decision = makeInliningDecision(target, callInfo);
switch (decision) {
case InliningDecision_Error:
return false;
case InliningDecision_DontInline:
+ case InliningDecision_UseCountTooLow:
break;
case InliningDecision_Inline:
if (target->isInterpreted())
return inlineScriptedCall(callInfo, target);
break;
}
}
@@ -5090,16 +5096,17 @@ IonBuilder::jsop_funapplyarguments(uint3
nativeFunc->setImplicitlyUsedUnchecked();
// Try to inline the call.
InliningDecision decision = makeInliningDecision(target, callInfo);
switch (decision) {
case InliningDecision_Error:
return false;
case InliningDecision_DontInline:
+ case InliningDecision_UseCountTooLow:
break;
case InliningDecision_Inline:
if (target->isInterpreted())
return inlineScriptedCall(callInfo, target);
}
return makeCall(target, callInfo, false);
}
@@ -5160,16 +5167,23 @@ IonBuilder::jsop_call(uint32_t argc, boo
if (status == InliningStatus_Error)
return false;
// No inline, just make the call.
JSFunction *target = nullptr;
if (targets.length() == 1)
target = &targets[0]->as<JSFunction>();
+ if (target && status == InliningStatus_UseCountTooLow) {
+ MRecompileCheck *check = MRecompileCheck::New(alloc(), target->nonLazyScript(),
+ optimizationInfo().usesBeforeInlining(),
+ MRecompileCheck::RecompileCheck_Inlining);
+ current->add(check);
+ }
+
return makeCall(target, callInfo, hasClones);
}
MDefinition *
IonBuilder::makeCallsiteClone(JSFunction *target, MDefinition *fun)
{
// Bake in the clone eagerly if we have a known target. We have arrived here
// because TI told us that the known target is a should-clone-at-callsite
@@ -6132,17 +6146,20 @@ IonBuilder::insertRecompileCheck()
while (topBuilder->callerBuilder_)
topBuilder = topBuilder->callerBuilder_;
// Add recompile check to recompile when the usecount reaches the usecount
// of the next optimization level.
OptimizationLevel nextLevel = js_IonOptimizations.nextLevel(curLevel);
const OptimizationInfo *info = js_IonOptimizations.get(nextLevel);
uint32_t useCount = info->usesBeforeCompile(topBuilder->script());
- current->add(MRecompileCheck::New(alloc(), topBuilder->script(), useCount));
+
+ MRecompileCheck *check = MRecompileCheck::New(alloc(), topBuilder->script(), useCount,
+ MRecompileCheck::RecompileCheck_OptimizationLevel);
+ current->add(check);
}
JSObject *
IonBuilder::testSingletonProperty(JSObject *obj, PropertyName *name)
{
// We would like to completely no-op property/global accesses which can
// produce only a particular JSObject. When indicating the access result is
// definitely an object, type inference does not account for the
@@ -8864,16 +8881,17 @@ IonBuilder::getPropTryCommonGetter(bool
// Inline if we can, otherwise, forget it and just generate a call.
bool inlineable = false;
if (commonGetter->isInterpreted()) {
InliningDecision decision = makeInliningDecision(commonGetter, callInfo);
switch (decision) {
case InliningDecision_Error:
return false;
case InliningDecision_DontInline:
+ case InliningDecision_UseCountTooLow:
break;
case InliningDecision_Inline:
inlineable = true;
break;
}
}
if (inlineable) {
@@ -9280,16 +9298,17 @@ IonBuilder::setPropTryCommonSetter(bool
// Inline the setter if we can.
if (commonSetter->isInterpreted()) {
InliningDecision decision = makeInliningDecision(commonSetter, callInfo);
switch (decision) {
case InliningDecision_Error:
return false;
case InliningDecision_DontInline:
+ case InliningDecision_UseCountTooLow:
break;
case InliningDecision_Inline:
if (!inlineScriptedCall(callInfo, commonSetter))
return false;
*emitted = true;
return true;
}
}
--- a/js/src/jit/IonBuilder.h
+++ b/js/src/jit/IonBuilder.h
@@ -628,24 +628,26 @@ class IonBuilder : public MIRGenerator
bool jsop_setaliasedvar(ScopeCoordinate sc);
/* Inlining. */
enum InliningStatus
{
InliningStatus_Error,
InliningStatus_NotInlined,
+ InliningStatus_UseCountTooLow,
InliningStatus_Inlined
};
enum InliningDecision
{
InliningDecision_Error,
InliningDecision_Inline,
- InliningDecision_DontInline
+ InliningDecision_DontInline,
+ InliningDecision_UseCountTooLow
};
static InliningDecision DontInline(JSScript *targetScript, const char *reason);
// Oracles.
InliningDecision canInlineTarget(JSFunction *target, CallInfo &callInfo);
InliningDecision makeInliningDecision(JSFunction *target, CallInfo &callInfo);
bool selectInliningTargets(ObjectVector &targets, CallInfo &callInfo,
--- a/js/src/jit/IonCaches.cpp
+++ b/js/src/jit/IonCaches.cpp
@@ -3003,22 +3003,22 @@ GetElementIC::attachGetProp(JSContext *c
Register scratch = output().valueReg().scratchReg();
masm.unboxString(val, scratch);
Label equal;
masm.branchPtr(Assembler::Equal, scratch, ImmGCPtr(name), &equal);
// The pointers are not equal, so if the input string is also an atom it
// must be a different string.
- masm.loadPtr(Address(scratch, JSString::offsetOfLengthAndFlags()), scratch);
- masm.branchTest32(Assembler::NonZero, scratch, Imm32(JSString::ATOM_BIT), &failures);
+ masm.branchTest32(Assembler::NonZero, Address(scratch, JSString::offsetOfFlags()),
+ Imm32(JSString::ATOM_BIT), &failures);
// Check the length.
- masm.rshiftPtr(Imm32(JSString::LENGTH_SHIFT), scratch);
- masm.branch32(Assembler::NotEqual, scratch, Imm32(name->length()), &failures);
+ masm.branch32(Assembler::NotEqual, Address(scratch, JSString::offsetOfLength()),
+ Imm32(name->length()), &failures);
// We have a non-atomized string with the same length. For now call a helper
// function to do the comparison.
RegisterSet volatileRegs = RegisterSet::Volatile();
masm.PushRegsInMask(volatileRegs);
Register objReg = object();
JS_ASSERT(objReg != scratch);
--- a/js/src/jit/IonMacroAssembler.cpp
+++ b/js/src/jit/IonMacroAssembler.cpp
@@ -586,24 +586,24 @@ MacroAssembler::newGCThing(Register resu
gc::AllocKind allocKind = templateObj->tenuredGetAllocKind();
JS_ASSERT(allocKind >= gc::FINALIZE_OBJECT0 && allocKind <= gc::FINALIZE_OBJECT_LAST);
allocateObject(result, temp, allocKind, templateObj->numDynamicSlots(), initialHeap, fail);
}
void
MacroAssembler::createGCObject(Register obj, Register temp, JSObject *templateObj,
- gc::InitialHeap initialHeap, Label *fail)
+ gc::InitialHeap initialHeap, Label *fail, bool initFixedSlots)
{
uint32_t nDynamicSlots = templateObj->numDynamicSlots();
gc::AllocKind allocKind = templateObj->tenuredGetAllocKind();
JS_ASSERT(allocKind >= gc::FINALIZE_OBJECT0 && allocKind <= gc::FINALIZE_OBJECT_LAST);
allocateObject(obj, temp, allocKind, nDynamicSlots, initialHeap, fail);
- initGCThing(obj, temp, templateObj);
+ initGCThing(obj, temp, templateObj, initFixedSlots);
}
// Inlined equivalent of gc::AllocateNonObject, without failure case handling.
// Non-object allocation does not need to worry about slots, so can take a
// simpler path.
void
MacroAssembler::allocateNonObject(Register result, Register temp, gc::AllocKind allocKind, Label *fail)
@@ -737,56 +737,60 @@ FindStartOfUndefinedSlots(JSObject *temp
for (uint32_t first = nslots; first != 0; --first) {
if (templateObj->getSlot(first - 1) != UndefinedValue())
return first;
}
return 0;
}
void
-MacroAssembler::initGCSlots(Register obj, Register slots, JSObject *templateObj)
+MacroAssembler::initGCSlots(Register obj, Register slots, JSObject *templateObj,
+ bool initFixedSlots)
{
// Slots of non-array objects are required to be initialized.
// Use the values currently in the template object.
uint32_t nslots = templateObj->lastProperty()->slotSpan(templateObj->getClass());
if (nslots == 0)
return;
- uint32_t nfixed = Min(templateObj->numFixedSlots(), nslots);
+ uint32_t nfixed = templateObj->numUsedFixedSlots();
uint32_t ndynamic = templateObj->numDynamicSlots();
// Attempt to group slot writes such that we minimize the amount of
// duplicated data we need to embed in code and load into registers. In
// general, most template object slots will be undefined except for any
// reserved slots. Since reserved slots come first, we split the object
// logically into independent non-UndefinedValue writes to the head and
// duplicated writes of UndefinedValue to the tail. For the majority of
// objects, the "tail" will be the entire slot range.
uint32_t startOfUndefined = FindStartOfUndefinedSlots(templateObj, nslots);
JS_ASSERT(startOfUndefined <= nfixed); // Reserved slots must be fixed.
// Copy over any preserved reserved slots.
copySlotsFromTemplate(obj, templateObj, 0, startOfUndefined);
// Fill the rest of the fixed slots with undefined.
- fillSlotsWithUndefined(Address(obj, JSObject::getFixedSlotOffset(startOfUndefined)), slots,
- startOfUndefined, nfixed);
+ if (initFixedSlots) {
+ fillSlotsWithUndefined(Address(obj, JSObject::getFixedSlotOffset(startOfUndefined)), slots,
+ startOfUndefined, nfixed);
+ }
if (ndynamic) {
// We are short one register to do this elegantly. Borrow the obj
// register briefly for our slots base address.
push(obj);
loadPtr(Address(obj, JSObject::offsetOfSlots()), obj);
fillSlotsWithUndefined(Address(obj, 0), slots, 0, ndynamic);
pop(obj);
}
}
void
-MacroAssembler::initGCThing(Register obj, Register slots, JSObject *templateObj)
+MacroAssembler::initGCThing(Register obj, Register slots, JSObject *templateObj,
+ bool initFixedSlots)
{
// Fast initialization of an empty object returned by allocateObject().
JS_ASSERT(!templateObj->hasDynamicElements());
storePtr(ImmGCPtr(templateObj->lastProperty()), Address(obj, JSObject::offsetOfShape()));
storePtr(ImmGCPtr(templateObj->type()), Address(obj, JSObject::offsetOfType()));
if (templateObj->hasDynamicSlots())
@@ -813,63 +817,76 @@ MacroAssembler::initGCThing(Register obj
store32(Imm32(templateObj->shouldConvertDoubleElements()
? ObjectElements::CONVERT_DOUBLE_ELEMENTS
: 0),
Address(obj, elementsOffset + ObjectElements::offsetOfFlags()));
JS_ASSERT(!templateObj->hasPrivate());
} else {
storePtr(ImmPtr(emptyObjectElements), Address(obj, JSObject::offsetOfElements()));
- initGCSlots(obj, slots, templateObj);
+ initGCSlots(obj, slots, templateObj, initFixedSlots);
if (templateObj->hasPrivate()) {
uint32_t nfixed = templateObj->numFixedSlots();
storePtr(ImmPtr(templateObj->getPrivate()),
Address(obj, JSObject::getPrivateDataOffset(nfixed)));
}
}
}
void
MacroAssembler::compareStrings(JSOp op, Register left, Register right, Register result,
- Register temp, Label *fail)
+ Label *fail)
{
JS_ASSERT(IsEqualityOp(op));
Label done;
Label notPointerEqual;
// Fast path for identical strings.
branchPtr(Assembler::NotEqual, left, right, ¬PointerEqual);
move32(Imm32(op == JSOP_EQ || op == JSOP_STRICTEQ), result);
jump(&done);
bind(¬PointerEqual);
- loadPtr(Address(left, JSString::offsetOfLengthAndFlags()), result);
- loadPtr(Address(right, JSString::offsetOfLengthAndFlags()), temp);
Label notAtom;
// Optimize the equality operation to a pointer compare for two atoms.
Imm32 atomBit(JSString::ATOM_BIT);
- branchTest32(Assembler::Zero, result, atomBit, ¬Atom);
- branchTest32(Assembler::Zero, temp, atomBit, ¬Atom);
+ branchTest32(Assembler::Zero, Address(left, JSString::offsetOfFlags()), atomBit, ¬Atom);
+ branchTest32(Assembler::Zero, Address(right, JSString::offsetOfFlags()), atomBit, ¬Atom);
cmpPtrSet(JSOpToCondition(MCompare::Compare_String, op), left, right, result);
jump(&done);
bind(¬Atom);
// Strings of different length can never be equal.
- rshiftPtr(Imm32(JSString::LENGTH_SHIFT), result);
- rshiftPtr(Imm32(JSString::LENGTH_SHIFT), temp);
- branchPtr(Assembler::Equal, result, temp, fail);
+ loadStringLength(left, result);
+ branch32(Assembler::Equal, Address(right, JSString::offsetOfLength()), result, fail);
move32(Imm32(op == JSOP_NE || op == JSOP_STRICTNE), result);
bind(&done);
}
void
+MacroAssembler::loadStringChars(Register str, Register dest)
+{
+ Label isInline, done;
+ branchTest32(Assembler::NonZero, Address(str, JSString::offsetOfFlags()),
+ Imm32(JSString::INLINE_CHARS_BIT), &isInline);
+
+ loadPtr(Address(str, JSString::offsetOfNonInlineChars()), dest);
+ jump(&done);
+
+ bind(&isInline);
+ computeEffectiveAddress(Address(str, JSInlineString::offsetOfInlineStorage()), dest);
+
+ bind(&done);
+}
+
+void
MacroAssembler::checkInterruptFlagPar(Register tempReg, Label *fail)
{
#ifdef JS_THREADSAFE
movePtr(ImmPtr(GetIonContext()->runtime->addressOfInterruptPar()), tempReg);
branch32(Assembler::NonZero, Address(tempReg, 0), Imm32(0), fail);
#else
MOZ_ASSUME_UNREACHABLE("JSRuntime::interruptPar doesn't exist on non-threadsafe builds.");
#endif
--- a/js/src/jit/IonMacroAssembler.h
+++ b/js/src/jit/IonMacroAssembler.h
@@ -372,18 +372,24 @@ class MacroAssembler : public MacroAssem
}
void loadObjProto(Register obj, Register dest) {
loadPtr(Address(obj, JSObject::offsetOfType()), dest);
loadPtr(Address(dest, types::TypeObject::offsetOfProto()), dest);
}
void loadStringLength(Register str, Register dest) {
- loadPtr(Address(str, JSString::offsetOfLengthAndFlags()), dest);
- rshiftPtr(Imm32(JSString::LENGTH_SHIFT), dest);
+ load32(Address(str, JSString::offsetOfLength()), dest);
+ }
+
+ void loadStringChars(Register str, Register dest);
+
+ void branchIfRope(Register str, Label *label) {
+ Address flags(str, JSString::offsetOfFlags());
+ branch32(Assembler::Equal, flags, Imm32(JSString::ROPE_FLAGS), label);
}
void loadSliceBounds(Register worker, Register dest) {
loadPtr(Address(worker, ThreadPoolWorker::offsetOfSliceBounds()), dest);
}
void loadJSContext(Register dest) {
loadPtr(AbsoluteAddress(GetIonContext()->runtime->addressOfJSContext()), dest);
@@ -797,27 +803,28 @@ class MacroAssembler : public MacroAssem
size_t nDynamicSlots, gc::InitialHeap initialHeap, Label *fail);
void freeSpanAllocate(Register result, Register temp, gc::AllocKind allocKind, Label *fail);
void allocateObject(Register result, Register slots, gc::AllocKind allocKind,
uint32_t nDynamicSlots, gc::InitialHeap initialHeap, Label *fail);
void allocateNonObject(Register result, Register temp, gc::AllocKind allocKind, Label *fail);
void copySlotsFromTemplate(Register obj, const JSObject *templateObj,
uint32_t start, uint32_t end);
void fillSlotsWithUndefined(Address addr, Register temp, uint32_t start, uint32_t end);
- void initGCSlots(Register obj, Register temp, JSObject *templateObj);
+ void initGCSlots(Register obj, Register temp, JSObject *templateObj, bool initFixedSlots);
public:
void callMallocStub(size_t nbytes, Register result, Label *fail);
void callFreeStub(Register slots);
void createGCObject(Register result, Register temp, JSObject *templateObj,
- gc::InitialHeap initialHeap, Label *fail);
+ gc::InitialHeap initialHeap, Label *fail, bool initFixedSlots = true);
void newGCThing(Register result, Register temp, JSObject *templateObj,
gc::InitialHeap initialHeap, Label *fail);
- void initGCThing(Register obj, Register temp, JSObject *templateObj);
+ void initGCThing(Register obj, Register temp, JSObject *templateObj,
+ bool initFixedSlots = true);
void newGCString(Register result, Register temp, Label *fail);
void newGCFatInlineString(Register result, Register temp, Label *fail);
void newGCThingPar(Register result, Register cx, Register tempReg1, Register tempReg2,
gc::AllocKind allocKind, Label *fail);
void newGCThingPar(Register result, Register cx, Register tempReg1, Register tempReg2,
JSObject *templateObject, Label *fail);
@@ -825,17 +832,17 @@ class MacroAssembler : public MacroAssem
Label *fail);
void newGCFatInlineStringPar(Register result, Register cx, Register tempReg1, Register tempReg2,
Label *fail);
// Compares two strings for equality based on the JSOP.
// This checks for identical pointers, atoms and length and fails for everything else.
void compareStrings(JSOp op, Register left, Register right, Register result,
- Register temp, Label *fail);
+ Label *fail);
// Checks the flags that signal that parallel code may need to interrupt or
// abort. Branches to fail in that case.
void checkInterruptFlagPar(Register tempReg, Label *fail);
// If the JitCode that created this assembler needs to transition into the VM,
// we want to store the JitCode on the stack in order to mark it during a GC.
// This is a reference to a patch location where the JitCode* will be written.
--- a/js/src/jit/LIR-Common.h
+++ b/js/src/jit/LIR-Common.h
@@ -1865,64 +1865,54 @@ class LCompareFAndBranch : public LContr
MTest *mir() const {
return mir_->toTest();
}
MCompare *cmpMir() const {
return cmpMir_;
}
};
-class LCompareS : public LInstructionHelper<1, 2, 1>
+class LCompareS : public LInstructionHelper<1, 2, 0>
{
public:
LIR_HEADER(CompareS)
- LCompareS(const LAllocation &left, const LAllocation &right,
- const LDefinition &temp) {
+ LCompareS(const LAllocation &left, const LAllocation &right) {
setOperand(0, left);
setOperand(1, right);
- setTemp(0, temp);
}
const LAllocation *left() {
return getOperand(0);
}
const LAllocation *right() {
return getOperand(1);
}
- const LDefinition *temp() {
- return getTemp(0);
- }
MCompare *mir() {
return mir_->toCompare();
}
};
// strict-equality between value and string.
-class LCompareStrictS : public LInstructionHelper<1, BOX_PIECES + 1, 2>
+class LCompareStrictS : public LInstructionHelper<1, BOX_PIECES + 1, 1>
{
public:
LIR_HEADER(CompareStrictS)
- LCompareStrictS(const LAllocation &rhs, const LDefinition &temp0,
- const LDefinition &temp1) {
+ LCompareStrictS(const LAllocation &rhs, const LDefinition &temp) {
setOperand(BOX_PIECES, rhs);
- setTemp(0, temp0);
- setTemp(1, temp1);
+ setTemp(0, temp);
}
static const size_t Lhs = 0;
const LAllocation *right() {
return getOperand(BOX_PIECES);
}
- const LDefinition *temp() {
+ const LDefinition *tempToUnbox() {
return getTemp(0);
}
- const LDefinition *tempToUnbox() {
- return getTemp(1);
- }
MCompare *mir() {
return mir_->toCompare();
}
};
// Used for strict-equality comparisons where one side is a boolean
// and the other is a value. Note that CompareI is used to compare
// two booleans.
--- a/js/src/jit/Lowering.cpp
+++ b/js/src/jit/Lowering.cpp
@@ -891,28 +891,28 @@ LIRGenerator::visitCompare(MCompare *com
bool result;
if (comp->tryFold(&result))
return define(new(alloc()) LInteger(result), comp);
// Move below the emitAtUses call if we ever implement
// LCompareSAndBranch. Doing this now wouldn't be wrong, but doesn't
// make sense and avoids confusion.
if (comp->compareType() == MCompare::Compare_String) {
- LCompareS *lir = new(alloc()) LCompareS(useRegister(left), useRegister(right), temp());
+ LCompareS *lir = new(alloc()) LCompareS(useRegister(left), useRegister(right));
if (!define(lir, comp))
return false;
return assignSafepoint(lir, comp);
}
// Strict compare between value and string
if (comp->compareType() == MCompare::Compare_StrictString) {
JS_ASSERT(left->type() == MIRType_Value);
JS_ASSERT(right->type() == MIRType_String);
- LCompareStrictS *lir = new(alloc()) LCompareStrictS(useRegister(right), temp(), tempToUnbox());
+ LCompareStrictS *lir = new(alloc()) LCompareStrictS(useRegister(right), tempToUnbox());
if (!useBox(lir, LCompareStrictS::Lhs, left))
return false;
if (!define(lir, comp))
return false;
return assignSafepoint(lir, comp);
}
// Unknown/unspecialized compare use a VM call.
--- a/js/src/jit/MIR.h
+++ b/js/src/jit/MIR.h
@@ -6995,18 +6995,18 @@ class MStoreFixedSlot
}
AliasSet getAliasSet() const {
return AliasSet::Store(AliasSet::FixedSlot);
}
bool needsBarrier() const {
return needsBarrier_;
}
- void setNeedsBarrier() {
- needsBarrier_ = true;
+ void setNeedsBarrier(bool needsBarrier = true) {
+ needsBarrier_ = needsBarrier;
}
};
typedef Vector<JSObject *, 4, IonAllocPolicy> ObjectVector;
typedef Vector<bool, 4, IonAllocPolicy> BoolVector;
class InlinePropertyTable : public TempObject
{
@@ -9864,41 +9864,73 @@ class MHasClass
}
};
// Increase the usecount of the provided script upon execution and test if
// the usecount surpasses the threshold. Upon hit it will recompile the
// outermost script (i.e. not the inlined script).
class MRecompileCheck : public MNullaryInstruction
{
+ public:
+ enum RecompileCheckType {
+ RecompileCheck_OptimizationLevel,
+ RecompileCheck_Inlining
+ };
+
+ private:
JSScript *script_;
uint32_t recompileThreshold_;
-
- MRecompileCheck(JSScript *script, uint32_t recompileThreshold)
+ bool forceRecompilation_;
+ bool increaseUseCount_;
+
+ MRecompileCheck(JSScript *script, uint32_t recompileThreshold, RecompileCheckType type)
: script_(script),
recompileThreshold_(recompileThreshold)
{
+ switch (type) {
+ case RecompileCheck_OptimizationLevel:
+ forceRecompilation_ = false;
+ increaseUseCount_ = true;
+ break;
+ case RecompileCheck_Inlining:
+ forceRecompilation_ = true;
+ increaseUseCount_ = false;
+ break;
+ default:
+ MOZ_ASSUME_UNREACHABLE("Unexpected recompile check type");
+ }
+
setGuard();
}
public:
INSTRUCTION_HEADER(RecompileCheck);
- static MRecompileCheck *New(TempAllocator &alloc, JSScript *script_, uint32_t useCount) {
- return new(alloc) MRecompileCheck(script_, useCount);
+ static MRecompileCheck *New(TempAllocator &alloc, JSScript *script_,
+ uint32_t useCount, RecompileCheckType type)
+ {
+ return new(alloc) MRecompileCheck(script_, useCount, type);
}
JSScript *script() const {
return script_;
}
uint32_t recompileThreshold() const {
return recompileThreshold_;
}
+ bool forceRecompilation() const {
+ return forceRecompilation_;
+ }
+
+ bool increaseUseCount() const {
+ return increaseUseCount_;
+ }
+
AliasSet getAliasSet() const {
return AliasSet::None();
}
};
class MAsmJSNeg : public MUnaryInstruction
{
MAsmJSNeg(MDefinition *op, MIRType type)
--- a/js/src/jit/MIRGraph.h
+++ b/js/src/jit/MIRGraph.h
@@ -611,16 +611,21 @@ class MIRGraph
}
void removeBlocksAfter(MBasicBlock *block);
void removeBlock(MBasicBlock *block);
void moveBlockToEnd(MBasicBlock *block) {
JS_ASSERT(block->id());
blocks_.remove(block);
blocks_.pushBack(block);
}
+ void moveBlockBefore(MBasicBlock *at, MBasicBlock *block) {
+ JS_ASSERT(block->id());
+ blocks_.remove(block);
+ blocks_.insertBefore(at, block);
+ }
size_t numBlocks() const {
return numBlocks_;
}
uint32_t numBlockIds() const {
return blockIdGen_;
}
void allocDefinitionId(MDefinition *ins) {
ins->setId(idGen_++);
--- a/js/src/jit/VMFunctions.cpp
+++ b/js/src/jit/VMFunctions.cpp
@@ -1018,41 +1018,53 @@ StringReplace(JSContext *cx, HandleStrin
RootedValue rval(cx);
if (!str_replace_string_raw(cx, string, pattern, repl, &rval))
return nullptr;
return rval.toString();
}
-bool
-Recompile(JSContext *cx)
+static bool
+RecompileImpl(JSContext *cx, bool force)
{
JS_ASSERT(cx->currentlyRunningInJit());
JitActivationIterator activations(cx->runtime());
JitFrameIterator iter(activations);
JS_ASSERT(iter.type() == JitFrame_Exit);
++iter;
bool isConstructing = iter.isConstructing();
RootedScript script(cx, iter.script());
JS_ASSERT(script->hasIonScript());
if (!IsIonEnabled(cx))
return true;
- MethodStatus status = Recompile(cx, script, nullptr, nullptr, isConstructing);
+ MethodStatus status = Recompile(cx, script, nullptr, nullptr, isConstructing, force);
if (status == Method_Error)
return false;
return true;
}
bool
+ForcedRecompile(JSContext *cx)
+{
+ return RecompileImpl(cx, /* force = */ true);
+}
+
+bool
+Recompile(JSContext *cx)
+{
+ return RecompileImpl(cx, /* force = */ false);
+}
+
+bool
SetDenseElement(JSContext *cx, HandleObject obj, int32_t index, HandleValue value,
bool strict)
{
// This function is called from Ion code for StoreElementHole's OOL path.
// In this case we know the object is native, has no indexed properties
// and we can use setDenseElement instead of setDenseElementWithType.
MOZ_ASSERT(obj->isNative());
--- a/js/src/jit/VMFunctions.h
+++ b/js/src/jit/VMFunctions.h
@@ -671,16 +671,17 @@ bool InitBaselineFrameForOsr(BaselineFra
uint32_t numStackValues);
JSObject *CreateDerivedTypedObj(JSContext *cx, HandleObject descr,
HandleObject owner, int32_t offset);
bool ArraySpliceDense(JSContext *cx, HandleObject obj, uint32_t start, uint32_t deleteCount);
bool Recompile(JSContext *cx);
+bool ForcedRecompile(JSContext *cx);
JSString *RegExpReplace(JSContext *cx, HandleString string, HandleObject regexp,
HandleString repl);
JSString *StringReplace(JSContext *cx, HandleString string, HandleString pattern,
HandleString repl);
bool SetDenseElement(JSContext *cx, HandleObject obj, int32_t index, HandleValue value,
bool strict);
--- a/js/src/jit/arm/MacroAssembler-arm.cpp
+++ b/js/src/jit/arm/MacroAssembler-arm.cpp
@@ -4079,24 +4079,19 @@ MacroAssemblerARMCompat::handleFailureWi
ma_ldr(Operand(sp, offsetof(ResumeFromException, target)), r1);
jump(r1);
}
Assembler::Condition
MacroAssemblerARMCompat::testStringTruthy(bool truthy, const ValueOperand &value)
{
Register string = value.payloadReg();
-
- size_t mask = (0xFFFFFFFF << JSString::LENGTH_SHIFT);
- ma_dtr(IsLoad, string, Imm32(JSString::offsetOfLengthAndFlags()), ScratchRegister);
- // Bit clear into the scratch register. This is done because there is performs the operation
- // dest <- src1 & ~ src2. There is no instruction that does this without writing
- // the result somewhere, so the Scratch Register is sacrificed.
- ma_bic(Imm32(~mask), ScratchRegister, SetCond);
- return truthy ? Assembler::NonZero : Assembler::Zero;
+ ma_dtr(IsLoad, string, Imm32(JSString::offsetOfLength()), ScratchRegister);
+ ma_cmp(ScratchRegister, Imm32(0));
+ return truthy ? Assembler::NotEqual : Assembler::Equal;
}
void
MacroAssemblerARMCompat::floor(FloatRegister input, Register output, Label *bail)
{
Label handleZero;
Label handleNeg;
Label fin;
--- a/js/src/jit/arm/Simulator-arm.cpp
+++ b/js/src/jit/arm/Simulator-arm.cpp
@@ -1484,30 +1484,20 @@ Simulator::setCallResult(int64_t res)
{
set_register(r0, static_cast<int32_t>(res));
set_register(r1, static_cast<int32_t>(res >> 32));
}
int
Simulator::readW(int32_t addr, SimInstruction *instr)
{
-#ifdef JS_YARR
- // YARR emits unaligned loads, so we don't check for them here like the
- // other methods below.
+ // The regexp engines emit unaligned loads, so we don't check for them here
+ // like the other methods below.
intptr_t *ptr = reinterpret_cast<intptr_t*>(addr);
return *ptr;
-#else // JS_YARR
- if ((addr & 3) == 0) {
- intptr_t *ptr = reinterpret_cast<intptr_t*>(addr);
- return *ptr;
- } else {
- printf("Unaligned write at 0x%08x, pc=%p\n", addr, instr);
- MOZ_CRASH();
- }
-#endif // JS_YARR
}
void
Simulator::writeW(int32_t addr, int value, SimInstruction *instr)
{
if ((addr & 3) == 0) {
intptr_t *ptr = reinterpret_cast<intptr_t*>(addr);
*ptr = value;
--- a/js/src/jit/x64/MacroAssembler-x64.h
+++ b/js/src/jit/x64/MacroAssembler-x64.h
@@ -1187,20 +1187,18 @@ class MacroAssemblerX64 : public MacroAs
j(cond, label);
}
void branchTestBooleanTruthy(bool truthy, const ValueOperand &operand, Label *label) {
testl(operand.valueReg(), operand.valueReg());
j(truthy ? NonZero : Zero, label);
}
Condition testStringTruthy(bool truthy, const ValueOperand &value) {
unboxString(value, ScratchReg);
-
- Operand lengthAndFlags(ScratchReg, JSString::offsetOfLengthAndFlags());
- testq(lengthAndFlags, Imm32(-1 << JSString::LENGTH_SHIFT));
- return truthy ? Assembler::NonZero : Assembler::Zero;
+ cmpl(Operand(ScratchReg, JSString::offsetOfLength()), Imm32(0));
+ return truthy ? Assembler::NotEqual : Assembler::Equal;
}
void branchTestStringTruthy(bool truthy, const ValueOperand &value, Label *label) {
Condition cond = testStringTruthy(truthy, value);
j(cond, label);
}
void loadInt32OrDouble(const Operand &operand, FloatRegister dest) {
Label notInt32, end;
--- a/js/src/jit/x86/MacroAssembler-x86.h
+++ b/js/src/jit/x86/MacroAssembler-x86.h
@@ -948,21 +948,18 @@ class MacroAssemblerX86 : public MacroAs
j(cond, label);
}
void branchTestBooleanTruthy(bool truthy, const ValueOperand &operand, Label *label) {
testl(operand.payloadReg(), operand.payloadReg());
j(truthy ? NonZero : Zero, label);
}
Condition testStringTruthy(bool truthy, const ValueOperand &value) {
Register string = value.payloadReg();
- Operand lengthAndFlags(string, JSString::offsetOfLengthAndFlags());
-
- size_t mask = (0xFFFFFFFF << JSString::LENGTH_SHIFT);
- testl(lengthAndFlags, Imm32(mask));
- return truthy ? Assembler::NonZero : Assembler::Zero;
+ cmpl(Operand(string, JSString::offsetOfLength()), Imm32(0));
+ return truthy ? Assembler::NotEqual : Assembler::Equal;
}
void branchTestStringTruthy(bool truthy, const ValueOperand &value, Label *label) {
Condition cond = testStringTruthy(truthy, value);
j(cond, label);
}
void loadInt32OrDouble(const Operand &operand, FloatRegister dest) {
Label notInt32, end;
--- a/js/src/jsapi-tests/moz.build
+++ b/js/src/jsapi-tests/moz.build
@@ -67,16 +67,17 @@ UNIFIED_SOURCES += [
'testSourcePolicy.cpp',
'testStringBuffer.cpp',
'testStructuredClone.cpp',
'testToIntWidth.cpp',
'testTrap.cpp',
'testTypedArrays.cpp',
'testUncaughtError.cpp',
'testUTF8.cpp',
+ 'testWeakMap.cpp',
'testXDR.cpp',
]
if CONFIG['ENABLE_ION']:
UNIFIED_SOURCES += [
'testJitRValueAlloc.cpp',
]
new file mode 100644
--- /dev/null
+++ b/js/src/jsapi-tests/testWeakMap.cpp
@@ -0,0 +1,246 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+* vim: set ts=8 sts=4 et sw=4 tw=99:
+*/
+/* 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/. */
+
+#include "gc/Zone.h"
+
+#include "jsapi-tests/tests.h"
+
+using namespace JS;
+
+#ifdef JSGC_USE_EXACT_ROOTING
+
+BEGIN_TEST(testWeakMap_basicOperations)
+{
+ RootedObject map(cx, NewWeakMapObject(cx));
+ CHECK(IsWeakMapObject(map));
+
+ RootedObject key(cx, newKey());
+ CHECK(key);
+ CHECK(!IsWeakMapObject(key));
+
+ RootedValue r(cx);
+ CHECK(GetWeakMapEntry(cx, map, key, &r));
+ CHECK(r.isUndefined());
+
+ CHECK(checkSize(map, 0));
+
+ RootedValue val(cx, Int32Value(1));
+ CHECK(SetWeakMapEntry(cx, map, key, val));
+
+ CHECK(GetWeakMapEntry(cx, map, key, &r));
+ CHECK(r == val);
+ CHECK(checkSize(map, 1));
+
+ JS_GC(rt);
+
+ CHECK(GetWeakMapEntry(cx, map, key, &r));
+ CHECK(r == val);
+ CHECK(checkSize(map, 1));
+
+ key = nullptr;
+ JS_GC(rt);
+
+ CHECK(checkSize(map, 0));
+
+ return true;
+}
+
+JSObject *newKey()
+{
+ return JS_NewObject(cx, nullptr, JS::NullPtr(), JS::NullPtr());
+}
+
+bool
+checkSize(HandleObject map, uint32_t expected)
+{
+ RootedObject keys(cx);
+ CHECK(JS_NondeterministicGetWeakMapKeys(cx, map, &keys));
+
+ uint32_t length;
+ CHECK(JS_GetArrayLength(cx, keys, &length));
+ CHECK(length == expected);
+
+ return true;
+}
+END_TEST(testWeakMap_basicOperations)
+
+BEGIN_TEST(testWeakMap_keyDelegates)
+{
+ JS_SetGCParameter(rt, JSGC_MODE, JSGC_MODE_INCREMENTAL);
+ JS_GC(rt);
+
+ RootedObject map(cx, NewWeakMapObject(cx));
+ CHECK(map);
+
+ RootedObject key(cx, newKey());
+ CHECK(key);
+
+ RootedObject delegate(cx, newDelegate());
+ CHECK(delegate);
+
+ SetKeyDelegate(key, delegate);
+
+ /*
+ * Perform an incremental GC, introducing an unmarked CCW to force the map
+ * zone to finish marking before the delegate zone.
+ */
+ CHECK(newCCW(map, delegate));
+ GCDebugSlice(rt, true, 1000000);
+#ifdef DEBUG
+ CHECK(map->zone()->lastZoneGroupIndex() < delegate->zone()->lastZoneGroupIndex());
+#endif
+
+ /* Add our entry to the weakmap. */
+ RootedValue val(cx, Int32Value(1));
+ CHECK(SetWeakMapEntry(cx, map, key, val));
+ CHECK(checkSize(map, 1));
+
+ /* Check the delegate keeps the entry alive even if the key is not reachable. */
+ key = nullptr;
+ CHECK(newCCW(map, delegate));
+ GCDebugSlice(rt, true, 100000);
+ CHECK(checkSize(map, 1));
+
+ /*
+ * Check that the zones finished marking at the same time, which is
+ * neccessary because of the presence of the delegate and the CCW.
+ */
+#ifdef DEBUG
+ CHECK(map->zone()->lastZoneGroupIndex() == delegate->zone()->lastZoneGroupIndex());
+#endif
+
+ /* Check that when the delegate becomes unreacable the entry is removed. */
+ delegate = nullptr;
+ JS_GC(rt);
+ CHECK(checkSize(map, 0));
+
+ return true;
+}
+
+static void SetKeyDelegate(JSObject *key, JSObject *delegate)
+{
+ JS_SetPrivate(key, delegate);
+}
+
+static JSObject *GetKeyDelegate(JSObject *obj)
+{
+ return static_cast<JSObject*>(JS_GetPrivate(obj));
+}
+
+JSObject *newKey()
+{
+ static const js::Class keyClass = {
+ "keyWithDelgate",
+ JSCLASS_HAS_PRIVATE | JSCLASS_HAS_RESERVED_SLOTS(1),
+ JS_PropertyStub, /* addProperty */
+ JS_DeletePropertyStub, /* delProperty */
+ JS_PropertyStub, /* getProperty */
+ JS_StrictPropertyStub, /* setProperty */
+ JS_EnumerateStub,
+ JS_ResolveStub,
+ JS_ConvertStub,
+ nullptr,
+ nullptr,
+ nullptr,
+ nullptr,
+ nullptr,
+ JS_NULL_CLASS_SPEC,
+ {
+ nullptr,
+ nullptr,
+ nullptr,
+ false,
+ GetKeyDelegate
+ },
+ JS_NULL_OBJECT_OPS
+ };
+
+ RootedObject key(cx);
+ key = JS_NewObject(cx,
+ reinterpret_cast<const JSClass *>(&keyClass),
+ JS::NullPtr(),
+ JS::NullPtr());
+ if (!key)
+ return nullptr;
+
+ SetKeyDelegate(key, nullptr);
+
+ return key;
+}
+
+JSObject *newCCW(HandleObject sourceZone, HandleObject destZone)
+{
+ /*
+ * Now ensure that this zone will be swept first by adding a cross
+ * compartment wrapper to a new objct in the same zone as the
+ * delegate obejct.
+ */
+ RootedObject object(cx);
+ {
+ JSAutoCompartment ac(cx, destZone);
+ object = JS_NewObject(cx, nullptr, NullPtr(), NullPtr());
+ if (!object)
+ return nullptr;
+ }
+ {
+ JSAutoCompartment ac(cx, sourceZone);
+ if (!JS_WrapObject(cx, &object))
+ return nullptr;
+ }
+ return object;
+}
+
+JSObject *newDelegate()
+{
+ static const JSClass delegateClass = {
+ "delegate",
+ JSCLASS_GLOBAL_FLAGS | JSCLASS_HAS_RESERVED_SLOTS(1),
+ JS_PropertyStub,
+ JS_DeletePropertyStub,
+ JS_PropertyStub,
+ JS_StrictPropertyStub,
+ JS_EnumerateStub,
+ JS_ResolveStub,
+ JS_ConvertStub,
+ nullptr,
+ nullptr,
+ nullptr,
+ nullptr,
+ JS_GlobalObjectTraceHook
+ };
+
+ /* Create the global object. */
+ JS::CompartmentOptions options;
+ options.setVersion(JSVERSION_LATEST);
+ JS::RootedObject global(cx);
+ global = JS_NewGlobalObject(cx, &delegateClass, nullptr, JS::FireOnNewGlobalHook, options);
+ JS_SetReservedSlot(global, 0, Int32Value(42));
+
+ /*
+ * Ensure the delegate is not in the nursery because for the purpose of this
+ * test we're going to put it in a private slot where it won't get updated.
+ */
+ JS_GC(rt);
+
+ return global;
+}
+
+bool
+checkSize(HandleObject map, uint32_t expected)
+{
+ RootedObject keys(cx);
+ CHECK(JS_NondeterministicGetWeakMapKeys(cx, map, &keys));
+
+ uint32_t length;
+ CHECK(JS_GetArrayLength(cx, keys, &length));
+ CHECK(length == expected);
+
+ return true;
+}
+END_TEST(testWeakMap_keyDelegates)
+
+#endif // JSGC_USE_EXACT_ROOTING
--- a/js/src/jsfriendapi.h
+++ b/js/src/jsfriendapi.h
@@ -581,19 +581,23 @@ struct Function {
uint16_t flags;
/* Used only for natives */
JSNative native;
const JSJitInfo *jitinfo;
void *_1;
};
struct Atom {
- static const size_t LENGTH_SHIFT = 4;
- size_t lengthAndFlags;
- const jschar *chars;
+ static const uint32_t INLINE_CHARS_BIT = JS_BIT(2);
+ uint32_t flags;
+ uint32_t length;
+ union {
+ const jschar *nonInlineChars;
+ char inlineStorage[1];
+ };
};
} /* namespace shadow */
// This is equal to |&JSObject::class_|. Use it in places where you don't want
// to #include jsobj.h.
extern JS_FRIEND_DATA(const js::Class* const) ObjectClassPtr;
@@ -762,24 +766,29 @@ GetObjectSlot(JSObject *obj, size_t slot
{
JS_ASSERT(slot < GetObjectSlotSpan(obj));
return reinterpret_cast<const shadow::Object *>(obj)->slotRef(slot);
}
inline const jschar *
GetAtomChars(JSAtom *atom)
{
- return reinterpret_cast<shadow::Atom *>(atom)->chars;
+ using shadow::Atom;
+ Atom *atom_ = reinterpret_cast<Atom *>(atom);
+ if (atom_->flags & Atom::INLINE_CHARS_BIT) {
+ char *p = reinterpret_cast<char *>(atom);
+ return reinterpret_cast<const jschar *>(p + offsetof(Atom, inlineStorage));
+ }
+ return atom_->nonInlineChars;
}
inline size_t
GetAtomLength(JSAtom *atom)
{
- using shadow::Atom;
- return reinterpret_cast<Atom*>(atom)->lengthAndFlags >> Atom::LENGTH_SHIFT;
+ return reinterpret_cast<shadow::Atom*>(atom)->length;
}
inline JSLinearString *
AtomToLinearString(JSAtom *atom)
{
return reinterpret_cast<JSLinearString *>(atom);
}
--- a/js/src/jsgc.cpp
+++ b/js/src/jsgc.cpp
@@ -2967,18 +2967,18 @@ GCRuntime::beginMarkPhase()
gcstats::AutoPhase ap2(stats, gcstats::PHASE_MARK_ROOTS);
for (GCZonesIter zone(rt); !zone.done(); zone.next()) {
/* Unmark everything in the zones being collected. */
zone->allocator.arenas.unmarkAll();
}
for (GCCompartmentsIter c(rt); !c.done(); c.next()) {
- /* Reset weak map list for the compartments being collected. */
- WeakMapBase::resetCompartmentWeakMapList(c);
+ /* Unmark all weak maps in the compartments being collected. */
+ WeakMapBase::unmarkCompartment(c);
}
if (isFull)
UnmarkScriptData(rt);
MarkRuntime(gcmarker);
if (isIncremental)
BufferGrayRoots(gcmarker);
@@ -3168,38 +3168,41 @@ js::gc::MarkingValidator::nonIncremental
return;
memcpy((void *)entry->bitmap, (void *)bitmap->bitmap, sizeof(bitmap->bitmap));
if (!map.putNew(r.front(), entry))
return;
}
/*
- * Temporarily clear the lists of live weakmaps and array buffers for the
- * compartments we are collecting.
+ * Temporarily clear the weakmaps' mark flags and the lists of live array
+ * buffers for the compartments we are collecting.
*/
- WeakMapVector weakmaps;
+ WeakMapSet markedWeakMaps;
+ if (!markedWeakMaps.init())
+ return;
+
ArrayBufferVector arrayBuffers;
for (GCCompartmentsIter c(runtime); !c.done(); c.next()) {
- if (!WeakMapBase::saveCompartmentWeakMapList(c, weakmaps) ||
+ if (!WeakMapBase::saveCompartmentMarkedWeakMaps(c, markedWeakMaps) ||
!ArrayBufferObject::saveArrayBufferList(c, arrayBuffers))
{
return;
}
}
/*
* After this point, the function should run to completion, so we shouldn't
* do anything fallible.
*/
initialized = true;
for (GCCompartmentsIter c(runtime); !c.done(); c.next()) {
- WeakMapBase::resetCompartmentWeakMapList(c);
+ WeakMapBase::unmarkCompartment(c);
ArrayBufferObject::resetArrayBufferList(c);
}
/* Re-do all the marking, but non-incrementally. */
js::gc::State state = gc->incrementalState;
gc->incrementalState = MARK_ROOTS;
JS_ASSERT(gcmarker->isDrained());
@@ -3245,20 +3248,20 @@ js::gc::MarkingValidator::nonIncremental
for (GCChunkSet::Range r(gc->chunkSet.all()); !r.empty(); r.popFront()) {
Chunk *chunk = r.front();
ChunkBitmap *bitmap = &chunk->bitmap;
ChunkBitmap *entry = map.lookup(chunk)->value();
Swap(*entry, *bitmap);
}
for (GCCompartmentsIter c(runtime); !c.done(); c.next()) {
- WeakMapBase::resetCompartmentWeakMapList(c);
+ WeakMapBase::unmarkCompartment(c);
ArrayBufferObject::resetArrayBufferList(c);
}
- WeakMapBase::restoreCompartmentWeakMapLists(weakmaps);
+ WeakMapBase::restoreCompartmentMarkedWeakMaps(markedWeakMaps);
ArrayBufferObject::restoreArrayBufferLists(arrayBuffers);
gc->incrementalState = state;
}
void
js::gc::MarkingValidator::validate()
{
@@ -3434,23 +3437,48 @@ Zone::findOutgoingEdges(ComponentFinder<
* compartment, and these aren't in the cross compartment map.
*/
JSRuntime *rt = runtimeFromMainThread();
if (rt->atomsCompartment()->zone()->isGCMarking())
finder.addEdgeTo(rt->atomsCompartment()->zone());
for (CompartmentsInZoneIter comp(this); !comp.done(); comp.next())
comp->findOutgoingEdges(finder);
+
+ for (ZoneSet::Range r = gcZoneGroupEdges.all(); !r.empty(); r.popFront())
+ finder.addEdgeTo(r.front());
+ gcZoneGroupEdges.clear();
+}
+
+bool
+GCRuntime::findZoneEdgesForWeakMaps()
+{
+ /*
+ * Weakmaps which have keys with delegates in a different zone introduce the
+ * need for zone edges from the delegate's zone to the weakmap zone.
+ *
+ * Since the edges point into and not away from the zone the weakmap is in
+ * we must find these edges in advance and store them in a set on the Zone.
+ * If we run out of memory, we fall back to sweeping everything in one
+ * group.
+ */
+
+ for (GCCompartmentsIter comp(rt); !comp.done(); comp.next()) {
+ if (!WeakMapBase::findZoneEdgesForCompartment(comp))
+ return false;
+ }
+
+ return true;
}
void
GCRuntime::findZoneGroups()
{
ComponentFinder<Zone> finder(rt->mainThread.nativeStackLimit[StackForSystemCode]);
- if (!isIncremental)
+ if (!isIncremental || !findZoneEdgesForWeakMaps())
finder.useOneComponent();
for (GCZonesIter zone(rt); !zone.done(); zone.next()) {
JS_ASSERT(zone->isGCMarking());
finder.addNode(zone);
}
zoneGroups = finder.getResultsList();
currentZoneGroup = zoneGroups;
@@ -3782,16 +3810,18 @@ GCRuntime::beginSweepingZoneGroup()
/* Purge the ArenaLists before sweeping. */
zone->allocator.arenas.purge();
if (rt->isAtomsZone(zone))
sweepingAtoms = true;
if (rt->sweepZoneCallback)
rt->sweepZoneCallback(zone);
+
+ zone->gcLastZoneGroupIndex = zoneGroupIndex;
}
validateIncrementalMarking();
FreeOp fop(rt, sweepOnBackgroundThread);
{
gcstats::AutoPhase ap(stats, gcstats::PHASE_FINALIZE_START);
@@ -5014,16 +5044,19 @@ js::NewCompartment(JSContext *cx, Zone *
ScopedJSDeletePtr<Zone> zoneHolder;
if (!zone) {
zone = cx->new_<Zone>(rt);
if (!zone)
return nullptr;
zoneHolder.reset(zone);
+ if (!zone->init())
+ return nullptr;
+
zone->setGCLastBytes(8192, GC_NORMAL);
const JSPrincipals *trusted = rt->trustedPrincipals();
zone->isSystem = principals && principals == trusted;
}
ScopedJSDeletePtr<JSCompartment> compartment(cx->new_<JSCompartment>(zone, options));
if (!compartment || !compartment->init(cx))
--- a/js/src/jsweakmap.cpp
+++ b/js/src/jsweakmap.cpp
@@ -15,109 +15,182 @@
#include "jswrapper.h"
#include "vm/GlobalObject.h"
#include "vm/WeakMapObject.h"
#include "jsobjinlines.h"
using namespace js;
+using namespace js::gc;
WeakMapBase::WeakMapBase(JSObject *memOf, JSCompartment *c)
: memberOf(memOf),
compartment(c),
- next(WeakMapNotInList)
+ next(WeakMapNotInList),
+ marked(false)
{
JS_ASSERT_IF(memberOf, memberOf->compartment() == c);
}
WeakMapBase::~WeakMapBase()
{
- JS_ASSERT(next == WeakMapNotInList);
+ JS_ASSERT(!isInList());
+}
+
+void
+WeakMapBase::trace(JSTracer *tracer)
+{
+ JS_ASSERT(isInList());
+ if (IS_GC_MARKING_TRACER(tracer)) {
+ // We don't trace any of the WeakMap entries at this time, just record
+ // record the fact that the WeakMap has been marked. Enties are marked
+ // in the iterative marking phase by markAllIteratively(), which happens
+ // when many keys as possible have been marked already.
+ JS_ASSERT(tracer->eagerlyTraceWeakMaps() == DoNotTraceWeakMaps);
+ marked = true;
+ } else {
+ // If we're not actually doing garbage collection, the keys won't be marked
+ // nicely as needed by the true ephemeral marking algorithm --- custom tracers
+ // such as the cycle collector must use their own means for cycle detection.
+ // So here we do a conservative approximation: pretend all keys are live.
+ if (tracer->eagerlyTraceWeakMaps() == DoNotTraceWeakMaps)
+ return;
+
+ nonMarkingTraceValues(tracer);
+ if (tracer->eagerlyTraceWeakMaps() == TraceWeakMapKeysValues)
+ nonMarkingTraceKeys(tracer);
+ }
+}
+
+void
+WeakMapBase::unmarkCompartment(JSCompartment *c)
+{
+ for (WeakMapBase *m = c->gcWeakMapList; m; m = m->next)
+ m->marked = false;
}
bool
WeakMapBase::markCompartmentIteratively(JSCompartment *c, JSTracer *tracer)
{
bool markedAny = false;
for (WeakMapBase *m = c->gcWeakMapList; m; m = m->next) {
- if (m->markIteratively(tracer))
+ if (m->marked && m->markIteratively(tracer))
markedAny = true;
}
return markedAny;
}
+bool
+WeakMapBase::findZoneEdgesForCompartment(JSCompartment *c)
+{
+ for (WeakMapBase *m = c->gcWeakMapList; m; m = m->next) {
+ if (!m->findZoneEdges())
+ return false;
+ }
+ return true;
+}
+
void
WeakMapBase::sweepCompartment(JSCompartment *c)
{
+ WeakMapBase **tailPtr = &c->gcWeakMapList;
+ for (WeakMapBase *m = c->gcWeakMapList, *next; m; m = next) {
+ next = m->next;
+ if (m->marked) {
+ m->sweep();
+ *tailPtr = m;
+ tailPtr = &m->next;
+ } else {
+ /* Destroy the hash map now to catch any use after this point. */
+ m->finish();
+ m->next = WeakMapNotInList;
+ }
+ }
+ *tailPtr = nullptr;
+
+#ifdef DEBUG
for (WeakMapBase *m = c->gcWeakMapList; m; m = m->next)
- m->sweep();
+ JS_ASSERT(m->isInList() && m->marked);
+#endif
}
void
WeakMapBase::traceAllMappings(WeakMapTracer *tracer)
{
JSRuntime *rt = tracer->runtime;
for (CompartmentsIter c(rt, SkipAtoms); !c.done(); c.next()) {
for (WeakMapBase *m = c->gcWeakMapList; m; m = m->next)
m->traceMappings(tracer);
}
}
-void
-WeakMapBase::resetCompartmentWeakMapList(JSCompartment *c)
+bool
+WeakMapBase::saveCompartmentMarkedWeakMaps(JSCompartment *c, WeakMapSet &markedWeakMaps)
{
- JS_ASSERT(WeakMapNotInList != nullptr);
-
- WeakMapBase *m = c->gcWeakMapList;
- c->gcWeakMapList = nullptr;
- while (m) {
- WeakMapBase *n = m->next;
- m->next = WeakMapNotInList;
- m = n;
- }
-}
-
-bool
-WeakMapBase::saveCompartmentWeakMapList(JSCompartment *c, WeakMapVector &vector)
-{
- WeakMapBase *m = c->gcWeakMapList;
- while (m) {
- if (!vector.append(m))
+ for (WeakMapBase *m = c->gcWeakMapList; m; m = m->next) {
+ if (m->marked && !markedWeakMaps.put(m))
return false;
- m = m->next;
}
return true;
}
void
-WeakMapBase::restoreCompartmentWeakMapLists(WeakMapVector &vector)
+WeakMapBase::restoreCompartmentMarkedWeakMaps(WeakMapSet &markedWeakMaps)
{
- for (WeakMapBase **p = vector.begin(); p != vector.end(); p++) {
- WeakMapBase *m = *p;
- JS_ASSERT(m->next == WeakMapNotInList);
- JSCompartment *c = m->compartment;
- m->next = c->gcWeakMapList;
- c->gcWeakMapList = m;
+ for (WeakMapSet::Range r = markedWeakMaps.all(); !r.empty(); r.popFront()) {
+ WeakMapBase *map = r.front();
+ JS_ASSERT(map->compartment->zone()->isGCMarking());
+ JS_ASSERT(!map->marked);
+ map->marked = true;
}
}
void
WeakMapBase::removeWeakMapFromList(WeakMapBase *weakmap)
{
JSCompartment *c = weakmap->compartment;
for (WeakMapBase **p = &c->gcWeakMapList; *p; p = &(*p)->next) {
if (*p == weakmap) {
*p = (*p)->next;
weakmap->next = WeakMapNotInList;
break;
}
}
}
+bool
+ObjectValueMap::findZoneEdges()
+{
+ /*
+ * For unmarked weakmap keys with delegates in a different zone, add a zone
+ * edge to ensure that the delegate zone does finish marking after the key
+ * zone.
+ */
+ JS::AutoAssertNoGC nogc;
+ Zone *mapZone = compartment->zone();
+ for (Range r = all(); !r.empty(); r.popFront()) {
+ JSObject *key = r.front().key();
+ if (key->isMarked(BLACK) && !key->isMarked(GRAY))
+ continue;
+ JSWeakmapKeyDelegateOp op = key->getClass()->ext.weakmapKeyDelegateOp;
+ if (!op)
+ continue;
+ JSObject *delegate = op(key);
+ if (!delegate)
+ continue;
+ Zone *delegateZone = delegate->zone();
+ if (delegateZone == mapZone)
+ continue;
+ if (!delegateZone->gcZoneGroupEdges.put(key->zone()))
+ return false;
+ }
+ return true;
+}
+
static JSObject *
GetKeyArg(JSContext *cx, CallArgs &args)
{
if (args[0].isPrimitive()) {
JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_NOT_NONNULL_OBJECT);
return nullptr;
}
return &args[0].toObject();
@@ -161,18 +234,18 @@ WeakMap_has(JSContext *cx, unsigned argc
return CallNonGenericMethod<IsWeakMap, WeakMap_has_impl>(cx, args);
}
MOZ_ALWAYS_INLINE bool
WeakMap_clear_impl(JSContext *cx, CallArgs args)
{
JS_ASSERT(IsWeakMap(args.thisv()));
- // We can't js_delete the weakmap because the data gathered during GC
- // is used by the Cycle Collector
+ // We can't js_delete the weakmap because the data gathered during GC is
+ // used by the Cycle Collector.
if (ObjectValueMap *map = args.thisv().toObject().as<WeakMapObject>().getMap())
map->clear();
args.rval().setUndefined();
return true;
}
static bool
@@ -281,17 +354,17 @@ WeakMapPostWriteBarrier(JSRuntime *rt, O
* WeakMap's multiple inheritace, We need to do this in two stages, first to
* the HashMap base class and then to the unbarriered version.
*/
ObjectValueMap::Base *baseHashMap = static_cast<ObjectValueMap::Base *>(weakMap);
typedef HashMap<JSObject *, Value> UnbarrieredMap;
UnbarrieredMap *unbarrieredMap = reinterpret_cast<UnbarrieredMap *>(baseHashMap);
- typedef gc::HashKeyRef<UnbarrieredMap, JSObject *> Ref;
+ typedef HashKeyRef<UnbarrieredMap, JSObject *> Ref;
if (key && IsInsideNursery(rt, key))
rt->gc.storeBuffer.putGeneric(Ref((unbarrieredMap), key));
#endif
}
MOZ_ALWAYS_INLINE bool
SetWeakMapEntryInternal(JSContext *cx, Handle<WeakMapObject*> mapObj,
HandleObject key, HandleValue value)
@@ -368,17 +441,17 @@ JS_NondeterministicGetWeakMapKeys(JSCont
return true;
}
RootedObject arr(cx, NewDenseEmptyArray(cx));
if (!arr)
return false;
ObjectValueMap *map = obj->as<WeakMapObject>().getMap();
if (map) {
// Prevent GC from mutating the weakmap while iterating.
- gc::AutoSuppressGC suppress(cx);
+ AutoSuppressGC suppress(cx);
for (ObjectValueMap::Base::Range r = map->all(); !r.empty(); r.popFront()) {
RootedObject key(cx, r.front().key());
if (!cx->compartment()->wrap(cx, &key))
return false;
if (!NewbornArrayPush(cx, arr, ObjectValue(*key)))
return false;
}
}
@@ -392,17 +465,16 @@ WeakMap_mark(JSTracer *trc, JSObject *ob
if (ObjectValueMap *map = obj->as<WeakMapObject>().getMap())
map->trace(trc);
}
static void
WeakMap_finalize(FreeOp *fop, JSObject *obj)
{
if (ObjectValueMap *map = obj->as<WeakMapObject>().getMap()) {
- map->check();
#ifdef DEBUG
map->~ObjectValueMap();
memset(static_cast<void *>(map), 0xdc, sizeof(*map));
fop->free_(map);
#else
fop->delete_(map);
#endif
}
--- a/js/src/jsweakmap.h
+++ b/js/src/jsweakmap.h
@@ -28,121 +28,107 @@ namespace js {
// You must call this table's 'trace' method when the object of which it is a part is
// reached by the garbage collection tracer. Once a table is known to be live, the
// implementation takes care of the iterative marking needed for weak tables and removing
// table entries when collection is complete.
// The value for the next pointer for maps not in the map list.
static WeakMapBase * const WeakMapNotInList = reinterpret_cast<WeakMapBase *>(1);
-typedef Vector<WeakMapBase *, 0, SystemAllocPolicy> WeakMapVector;
+typedef HashSet<WeakMapBase *, DefaultHasher<WeakMapBase *>, SystemAllocPolicy> WeakMapSet;
// Common base class for all WeakMap specializations. The collector uses this to call
// their markIteratively and sweep methods.
class WeakMapBase {
public:
WeakMapBase(JSObject *memOf, JSCompartment *c);
virtual ~WeakMapBase();
- void trace(JSTracer *tracer) {
- if (IS_GC_MARKING_TRACER(tracer)) {
- // We don't do anything with a WeakMap at trace time. Rather, we wait until as
- // many keys as possible have been marked, and add ourselves to the list of
- // known-live WeakMaps to be scanned in the iterative marking phase, by
- // markAllIteratively.
- JS_ASSERT(tracer->eagerlyTraceWeakMaps() == DoNotTraceWeakMaps);
-
- // Add ourselves to the list if we are not already in the list. We can already
- // be in the list if the weak map is marked more than once due delayed marking.
- if (next == WeakMapNotInList) {
- next = compartment->gcWeakMapList;
- compartment->gcWeakMapList = this;
- }
- } else {
- // If we're not actually doing garbage collection, the keys won't be marked
- // nicely as needed by the true ephemeral marking algorithm --- custom tracers
- // such as the cycle collector must use their own means for cycle detection.
- // So here we do a conservative approximation: pretend all keys are live.
- if (tracer->eagerlyTraceWeakMaps() == DoNotTraceWeakMaps)
- return;
-
- nonMarkingTraceValues(tracer);
- if (tracer->eagerlyTraceWeakMaps() == TraceWeakMapKeysValues)
- nonMarkingTraceKeys(tracer);
- }
- }
+ void trace(JSTracer *tracer);
// Garbage collector entry points.
+ // Unmark all weak maps in a compartment.
+ static void unmarkCompartment(JSCompartment *c);
+
// Check all weak maps in a compartment that have been marked as live in this garbage
// collection, and mark the values of all entries that have become strong references
// to them. Return true if we marked any new values, indicating that we need to make
// another pass. In other words, mark my marked maps' marked members' mid-collection.
static bool markCompartmentIteratively(JSCompartment *c, JSTracer *tracer);
- // Remove entries whose keys are dead from all weak maps in a compartment marked as
- // live in this garbage collection.
+ // Add zone edges for weakmaps with key delegates in a different zone.
+ static bool findZoneEdgesForCompartment(JSCompartment *c);
+
+ // Sweep the weak maps in a compartment, removing dead weak maps and removing
+ // entries of live weak maps whose keys are dead.
static void sweepCompartment(JSCompartment *c);
// Trace all delayed weak map bindings. Used by the cycle collector.
static void traceAllMappings(WeakMapTracer *tracer);
bool isInList() { return next != WeakMapNotInList; }
- void check() { JS_ASSERT(!isInList()); }
- // Remove everything from the weak map list for a compartment.
- static void resetCompartmentWeakMapList(JSCompartment *c);
+ // Save information about which weak maps are marked for a compartment.
+ static bool saveCompartmentMarkedWeakMaps(JSCompartment *c, WeakMapSet &markedWeakMaps);
- // Save the live weak map list for a compartment, appending the data to a vector.
- static bool saveCompartmentWeakMapList(JSCompartment *c, WeakMapVector &vector);
+ // Restore information about which weak maps are marked for many compartments.
+ static void restoreCompartmentMarkedWeakMaps(WeakMapSet &markedWeakMaps);
- // Restore live weak map lists for multiple compartments from a vector.
- static void restoreCompartmentWeakMapLists(WeakMapVector &vector);
-
- // Remove a weakmap from the live weakmaps list
+ // Remove a weakmap from its compartment's weakmaps list.
static void removeWeakMapFromList(WeakMapBase *weakmap);
protected:
// Instance member functions called by the above. Instantiations of WeakMap override
// these with definitions appropriate for their Key and Value types.
virtual void nonMarkingTraceKeys(JSTracer *tracer) = 0;
virtual void nonMarkingTraceValues(JSTracer *tracer) = 0;
virtual bool markIteratively(JSTracer *tracer) = 0;
+ virtual bool findZoneEdges() = 0;
virtual void sweep() = 0;
virtual void traceMappings(WeakMapTracer *tracer) = 0;
+ virtual void finish() = 0;
// Object that this weak map is part of, if any.
JSObject *memberOf;
// Compartment that this weak map is part of.
JSCompartment *compartment;
- private:
- // Link in a list of WeakMaps to mark iteratively and sweep in this garbage
- // collection, headed by JSCompartment::gcWeakMapList. The last element of
- // the list has nullptr as its next. Maps not in the list have
- // WeakMapNotInList as their next. We must distinguish these cases to
- // avoid creating infinite lists when a weak map gets traced twice due to
- // delayed marking.
+ // Link in a list of all WeakMaps in a compartment, headed by
+ // JSCompartment::gcWeakMapList. The last element of the list has nullptr as
+ // its next. Maps not in the list have WeakMapNotInList as their next.
WeakMapBase *next;
+
+ // Whether this object has been traced during garbage collection.
+ bool marked;
};
template <class Key, class Value,
class HashPolicy = DefaultHasher<Key> >
class WeakMap : public HashMap<Key, Value, HashPolicy, RuntimeAllocPolicy>, public WeakMapBase
{
public:
typedef HashMap<Key, Value, HashPolicy, RuntimeAllocPolicy> Base;
typedef typename Base::Enum Enum;
typedef typename Base::Lookup Lookup;
typedef typename Base::Range Range;
explicit WeakMap(JSContext *cx, JSObject *memOf = nullptr)
: Base(cx->runtime()), WeakMapBase(memOf, cx->compartment()) { }
+ bool init(uint32_t len = 16) {
+ if (!Base::init(len))
+ return false;
+ next = compartment->gcWeakMapList;
+ compartment->gcWeakMapList = this;
+ marked = JS::IsIncrementalGCInProgress(compartment->runtimeFromMainThread());
+ return true;
+ }
+
private:
bool markValue(JSTracer *trc, Value *x) {
if (gc::IsMarked(x))
return false;
gc::Mark(trc, x, "WeakMap entry value");
JS_ASSERT(gc::IsMarked(x));
return true;
}
@@ -195,32 +181,41 @@ class WeakMap : public HashMap<Key, Valu
entryMoved(e, key);
markedAny = true;
}
key.unsafeSet(nullptr);
}
return markedAny;
}
+ bool findZoneEdges() {
+ // This is overridden by ObjectValueMap.
+ return true;
+ }
+
void sweep() {
/* Remove all entries whose keys remain unmarked. */
for (Enum e(*this); !e.empty(); e.popFront()) {
Key k(e.front().key());
if (gc::IsAboutToBeFinalized(&k))
e.removeFront();
else if (k != e.front().key())
entryMoved(e, k);
}
/*
* Once we've swept, all remaining edges should stay within the
* known-live part of the graph.
*/
assertEntriesNotAboutToBeFinalized();
}
+ void finish() {
+ Base::finish();
+ }
+
/* memberOf can be nullptr, which means that the map is not part of a JSObject. */
void traceMappings(WeakMapTracer *tracer) {
for (Range r = Base::all(); !r.empty(); r.popFront()) {
gc::Cell *key = gc::ToMarkable(r.front().key());
gc::Cell *value = gc::ToMarkable(r.front().value());
if (key && value) {
tracer->callback(tracer, memberOf,
key, gc::TraceKind(r.front().key()),
--- a/js/src/vm/ObjectImpl.h
+++ b/js/src/vm/ObjectImpl.h
@@ -566,16 +566,20 @@ class ObjectImpl : public gc::BarrieredC
types::TypeObject *typeRaw() const {
return type_;
}
uint32_t numFixedSlots() const {
return reinterpret_cast<const shadow::Object *>(this)->numFixedSlots();
}
+ uint32_t numUsedFixedSlots() const {
+ uint32_t nslots = lastProperty()->slotSpan(getClass());
+ return Min(nslots, numFixedSlots());
+ }
/*
* Whether this is the only object which has its specified type. This
* object will have its type constructed lazily as needed by analysis.
*/
bool hasSingletonType() const {
return !!type_->singleton();
}
--- a/js/src/vm/Runtime.cpp
+++ b/js/src/vm/Runtime.cpp
@@ -292,17 +292,17 @@ JSRuntime::init(uint32_t maxbytes)
if (!gc.init(maxbytes))
return false;
const char *size = getenv("JSGC_MARK_STACK_LIMIT");
if (size)
SetMarkStackLimit(this, atoi(size));
ScopedJSDeletePtr<Zone> atomsZone(new_<Zone>(this));
- if (!atomsZone)
+ if (!atomsZone || !atomsZone->init())
return false;
JS::CompartmentOptions options;
ScopedJSDeletePtr<JSCompartment> atomsCompartment(new_<JSCompartment>(atomsZone.get(), options));
if (!atomsCompartment || !atomsCompartment->init(nullptr))
return false;
gc.zones.append(atomsZone.get());
--- a/js/src/vm/String-inl.h
+++ b/js/src/vm/String-inl.h
@@ -20,23 +20,32 @@
namespace js {
template <AllowGC allowGC>
static MOZ_ALWAYS_INLINE JSInlineString *
NewFatInlineString(ThreadSafeContext *cx, JS::Latin1Chars chars)
{
size_t len = chars.length();
JS_ASSERT(JSFatInlineString::lengthFits(len));
- JSInlineString *str = JSInlineString::lengthFits(len)
- ? JSInlineString::new_<allowGC>(cx)
- : JSFatInlineString::new_<allowGC>(cx);
- if (!str)
- return nullptr;
- jschar *p = str->init(len);
+ JSInlineString *str;
+ jschar *p;
+ if (JSInlineString::lengthFits(len)) {
+ str = JSInlineString::new_<allowGC>(cx);
+ if (!str)
+ return nullptr;
+ p = str->init(len);
+ } else {
+ JSFatInlineString *fatstr = JSFatInlineString::new_<allowGC>(cx);
+ if (!fatstr)
+ return nullptr;
+ p = fatstr->init(len);
+ str = fatstr;
+ }
+
for (size_t i = 0; i < len; ++i)
p[i] = static_cast<jschar>(chars[i]);
p[len] = '\0';
return str;
}
template <AllowGC allowGC>
static MOZ_ALWAYS_INLINE JSInlineString *
@@ -44,23 +53,32 @@ NewFatInlineString(ExclusiveContext *cx,
{
size_t len = chars.length();
/*
* Don't bother trying to find a static atom; measurement shows that not
* many get here (for one, Atomize is catching them).
*/
JS_ASSERT(JSFatInlineString::lengthFits(len));
- JSInlineString *str = JSInlineString::lengthFits(len)
- ? JSInlineString::new_<allowGC>(cx)
- : JSFatInlineString::new_<allowGC>(cx);
- if (!str)
- return nullptr;
- jschar *storage = str->init(len);
+ JSInlineString *str;
+ jschar *storage;
+ if (JSInlineString::lengthFits(len)) {
+ str = JSInlineString::new_<allowGC>(cx);
+ if (!str)
+ return nullptr;
+ storage = str->init(len);
+ } else {
+ JSFatInlineString *fatstr = JSFatInlineString::new_<allowGC>(cx);
+ if (!fatstr)
+ return nullptr;
+ storage = fatstr->init(len);
+ str = fatstr;
+ }
+
mozilla::PodCopy(storage, chars.start().get(), len);
storage[len] = 0;
return str;
}
static inline void
StringWriteBarrierPost(js::ThreadSafeContext *maybecx, JSString **strp)
{
@@ -82,21 +100,22 @@ JSString::validateLength(js::ThreadSafeC
}
return true;
}
MOZ_ALWAYS_INLINE void
JSRope::init(js::ThreadSafeContext *cx, JSString *left, JSString *right, size_t length)
{
- d.u0.lengthAndFlags = buildLengthAndFlags(length, ROPE_FLAGS);
- d.u1.left = left;
- d.s.u2.right = right;
- js::StringWriteBarrierPost(cx, &d.u1.left);
- js::StringWriteBarrierPost(cx, &d.s.u2.right);
+ d.u1.length = length;
+ d.u1.flags = ROPE_FLAGS;
+ d.s.u2.left = left;
+ d.s.u3.right = right;
+ js::StringWriteBarrierPost(cx, &d.s.u2.left);
+ js::StringWriteBarrierPost(cx, &d.s.u3.right);
}
template <js::AllowGC allowGC>
MOZ_ALWAYS_INLINE JSRope *
JSRope::new_(js::ThreadSafeContext *cx,
typename js::MaybeRooted<JSString*, allowGC>::HandleType left,
typename js::MaybeRooted<JSString*, allowGC>::HandleType right,
size_t length)
@@ -108,29 +127,30 @@ JSRope::new_(js::ThreadSafeContext *cx,
return nullptr;
str->init(cx, left, right, length);
return str;
}
inline void
JSRope::markChildren(JSTracer *trc)
{
- js::gc::MarkStringUnbarriered(trc, &d.u1.left, "left child");
- js::gc::MarkStringUnbarriered(trc, &d.s.u2.right, "right child");
+ js::gc::MarkStringUnbarriered(trc, &d.s.u2.left, "left child");
+ js::gc::MarkStringUnbarriered(trc, &d.s.u3.right, "right child");
}
MOZ_ALWAYS_INLINE void
JSDependentString::init(js::ThreadSafeContext *cx, JSLinearString *base, const jschar *chars,
size_t length)
{
JS_ASSERT(!js::IsPoisonedPtr(base));
- d.u0.lengthAndFlags = buildLengthAndFlags(length, DEPENDENT_FLAGS);
- d.u1.chars = chars;
- d.s.u2.base = base;
- js::StringWriteBarrierPost(cx, reinterpret_cast<JSString **>(&d.s.u2.base));
+ d.u1.length = length;
+ d.u1.flags = DEPENDENT_FLAGS;
+ d.s.u2.nonInlineChars = chars;
+ d.s.u3.base = base;
+ js::StringWriteBarrierPost(cx, reinterpret_cast<JSString **>(&d.s.u3.base));
}
MOZ_ALWAYS_INLINE JSLinearString *
JSDependentString::new_(js::ExclusiveContext *cx,
JSLinearString *baseArg, const jschar *chars, size_t length)
{
/* Try to avoid long chains of dependent strings. */
while (baseArg->isDependent())
@@ -174,24 +194,25 @@ JSDependentString::new_(js::ExclusiveCon
str->init(cx, base, chars, length);
return str;
}
inline void
JSString::markBase(JSTracer *trc)
{
JS_ASSERT(hasBase());
- js::gc::MarkStringUnbarriered(trc, &d.s.u2.base, "base");
+ js::gc::MarkStringUnbarriered(trc, &d.s.u3.base, "base");
}
MOZ_ALWAYS_INLINE void
JSFlatString::init(const jschar *chars, size_t length)
{
- d.u0.lengthAndFlags = buildLengthAndFlags(length, FIXED_FLAGS);
- d.u1.chars = chars;
+ d.u1.length = length;
+ d.u1.flags = FLAT_BIT;
+ d.s.u2.nonInlineChars = chars;
}
template <js::AllowGC allowGC>
MOZ_ALWAYS_INLINE JSFlatString *
JSFlatString::new_(js::ThreadSafeContext *cx, const jschar *chars, size_t length)
{
JS_ASSERT(chars[length] == jschar(0));
@@ -224,44 +245,47 @@ MOZ_ALWAYS_INLINE JSInlineString *
JSInlineString::new_(js::ThreadSafeContext *cx)
{
return (JSInlineString *)js_NewGCString<allowGC>(cx);
}
MOZ_ALWAYS_INLINE jschar *
JSInlineString::init(size_t length)
{
- d.u0.lengthAndFlags = buildLengthAndFlags(length, FIXED_FLAGS);
- d.u1.chars = d.inlineStorage;
- JS_ASSERT(lengthFits(length) || (isFatInline() && JSFatInlineString::lengthFits(length)));
+ JS_ASSERT(lengthFits(length));
+ d.u1.length = length;
+ d.u1.flags = INIT_INLINE_FLAGS;
return d.inlineStorage;
}
-MOZ_ALWAYS_INLINE void
-JSInlineString::resetLength(size_t length)
+MOZ_ALWAYS_INLINE jschar *
+JSFatInlineString::init(size_t length)
{
- d.u0.lengthAndFlags = buildLengthAndFlags(length, FIXED_FLAGS);
- JS_ASSERT(lengthFits(length) || (isFatInline() && JSFatInlineString::lengthFits(length)));
+ JS_ASSERT(lengthFits(length));
+ d.u1.length = length;
+ d.u1.flags = INIT_FAT_INLINE_FLAGS;
+ return d.inlineStorage;
}
template <js::AllowGC allowGC>
MOZ_ALWAYS_INLINE JSFatInlineString *
JSFatInlineString::new_(js::ThreadSafeContext *cx)
{
return js_NewGCFatInlineString<allowGC>(cx);
}
MOZ_ALWAYS_INLINE void
JSExternalString::init(const jschar *chars, size_t length, const JSStringFinalizer *fin)
{
JS_ASSERT(fin);
JS_ASSERT(fin->finalize);
- d.u0.lengthAndFlags = buildLengthAndFlags(length, FIXED_FLAGS);
- d.u1.chars = chars;
- d.s.u2.externalFinalizer = fin;
+ d.u1.length = length;
+ d.u1.flags = EXTERNAL_FLAGS;
+ d.s.u2.nonInlineChars = chars;
+ d.s.u3.externalFinalizer = fin;
}
MOZ_ALWAYS_INLINE JSExternalString *
JSExternalString::new_(JSContext *cx, const jschar *chars, size_t length,
const JSStringFinalizer *fin)
{
JS_ASSERT(chars[length] == 0);
@@ -309,39 +333,39 @@ JSString::finalize(js::FreeOp *fop)
JS_ASSERT(isDependent() || isRope());
}
inline void
JSFlatString::finalize(js::FreeOp *fop)
{
JS_ASSERT(getAllocKind() != js::gc::FINALIZE_FAT_INLINE_STRING);
- if (chars() != d.inlineStorage)
- fop->free_(const_cast<jschar *>(chars()));
+ if (!isInline())
+ fop->free_(const_cast<jschar *>(nonInlineChars()));
}
inline void
JSFatInlineString::finalize(js::FreeOp *fop)
{
JS_ASSERT(getAllocKind() == js::gc::FINALIZE_FAT_INLINE_STRING);
- if (chars() != d.inlineStorage)
- fop->free_(const_cast<jschar *>(chars()));
+ if (!isInline())
+ fop->free_(const_cast<jschar *>(nonInlineChars()));
}
inline void
JSAtom::finalize(js::FreeOp *fop)
{
JS_ASSERT(JSString::isAtom());
JS_ASSERT(JSString::isFlat());
- if (chars() != d.inlineStorage)
- fop->free_(const_cast<jschar *>(chars()));
+ if (!isInline())
+ fop->free_(const_cast<jschar *>(nonInlineChars()));
}
inline void
JSExternalString::finalize(js::FreeOp *fop)
{
const JSStringFinalizer *fin = externalFinalizer();
- fin->finalize(fin, const_cast<jschar *>(chars()));
+ fin->finalize(fin, const_cast<jschar *>(nonInlineChars()));
}
#endif /* vm_String_inl_h */
--- a/js/src/vm/String.cpp
+++ b/js/src/vm/String.cpp
@@ -17,35 +17,16 @@
#include "jscompartmentinlines.h"
using namespace js;
using mozilla::PodCopy;
using mozilla::RangedPtr;
using mozilla::RoundUpPow2;
-bool
-JSString::isFatInline() const
-{
- // It's possible for fat-inline strings to be converted to flat strings;
- // as a result, checking just for the arena isn't enough to determine if a
- // string is fat-inline. Hence the isInline() check.
- bool is_FatInline = (getAllocKind() == gc::FINALIZE_FAT_INLINE_STRING) && isInline();
- JS_ASSERT_IF(is_FatInline, isFlat());
- return is_FatInline;
-}
-
-bool
-JSString::isExternal() const
-{
- bool is_external = (getAllocKind() == gc::FINALIZE_EXTERNAL_STRING);
- JS_ASSERT_IF(is_external, isFlat());
- return is_external;
-}
-
size_t
JSString::sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf)
{
// JSRope: do nothing, we'll count all children chars when we hit the leaf strings.
if (isRope())
return 0;
JS_ASSERT(isLinear());
@@ -54,17 +35,17 @@ JSString::sizeOfExcludingThis(mozilla::M
if (isDependent())
return 0;
JS_ASSERT(isFlat());
// JSExtensibleString: count the full capacity, not just the used space.
if (isExtensible()) {
JSExtensibleString &extensible = asExtensible();
- return mallocSizeOf(extensible.chars());
+ return mallocSizeOf(extensible.nonInlineChars());
}
// JSExternalString: don't count, the chars could be stored anywhere.
if (isExternal())
return 0;
// JSInlineString, JSFatInlineString [JSInlineAtom, JSFatInlineAtom]: the chars are inline.
if (isInline())
@@ -278,93 +259,94 @@ JSRope::flattenInternal(ExclusiveContext
if (capacity >= wholeLength) {
/*
* Simulate a left-most traversal from the root to leftMost->leftChild()
* via first_visit_node
*/
JS_ASSERT(str->isRope());
while (str != leftMostRope) {
if (b == WithIncrementalBarrier) {
- JSString::writeBarrierPre(str->d.u1.left);
- JSString::writeBarrierPre(str->d.s.u2.right);
+ JSString::writeBarrierPre(str->d.s.u2.left);
+ JSString::writeBarrierPre(str->d.s.u3.right);
}
- JSString *child = str->d.u1.left;
+ JSString *child = str->d.s.u2.left;
JS_ASSERT(child->isRope());
- str->d.u1.chars = left.chars();
- child->d.u0.flattenData = uintptr_t(str) | Tag_VisitRightChild;
+ str->d.s.u2.nonInlineChars = left.nonInlineChars();
+ child->d.u1.flattenData = uintptr_t(str) | Tag_VisitRightChild;
str = child;
}
if (b == WithIncrementalBarrier) {
- JSString::writeBarrierPre(str->d.u1.left);
- JSString::writeBarrierPre(str->d.s.u2.right);
+ JSString::writeBarrierPre(str->d.s.u2.left);
+ JSString::writeBarrierPre(str->d.s.u3.right);
}
- str->d.u1.chars = left.chars();
+ str->d.s.u2.nonInlineChars = left.nonInlineChars();
wholeCapacity = capacity;
- wholeChars = const_cast<jschar *>(left.chars());
- size_t bits = left.d.u0.lengthAndFlags;
- pos = wholeChars + (bits >> LENGTH_SHIFT);
+ wholeChars = const_cast<jschar *>(left.nonInlineChars());
+ pos = wholeChars + left.d.u1.length;
JS_STATIC_ASSERT(!(EXTENSIBLE_FLAGS & DEPENDENT_FLAGS));
- left.d.u0.lengthAndFlags = bits ^ (EXTENSIBLE_FLAGS | DEPENDENT_FLAGS);
- left.d.s.u2.base = (JSLinearString *)this; /* will be true on exit */
- StringWriteBarrierPostRemove(maybecx, &left.d.u1.left);
- StringWriteBarrierPost(maybecx, (JSString **)&left.d.s.u2.base);
+ left.d.u1.flags ^= (EXTENSIBLE_FLAGS | DEPENDENT_FLAGS);
+ left.d.s.u3.base = (JSLinearString *)this; /* will be true on exit */
+ StringWriteBarrierPostRemove(maybecx, &left.d.s.u2.left);
+ StringWriteBarrierPost(maybecx, (JSString **)&left.d.s.u3.base);
goto visit_right_child;
}
}
if (!AllocChars(maybecx, wholeLength, &wholeChars, &wholeCapacity))
return nullptr;
pos = wholeChars;
first_visit_node: {
if (b == WithIncrementalBarrier) {
- JSString::writeBarrierPre(str->d.u1.left);
- JSString::writeBarrierPre(str->d.s.u2.right);
+ JSString::writeBarrierPre(str->d.s.u2.left);
+ JSString::writeBarrierPre(str->d.s.u3.right);
}
- JSString &left = *str->d.u1.left;
- str->d.u1.chars = pos;
- StringWriteBarrierPostRemove(maybecx, &str->d.u1.left);
+ JSString &left = *str->d.s.u2.left;
+ str->d.s.u2.nonInlineChars = pos;
+ StringWriteBarrierPostRemove(maybecx, &str->d.s.u2.left);
if (left.isRope()) {
/* Return to this node when 'left' done, then goto visit_right_child. */
- left.d.u0.flattenData = uintptr_t(str) | Tag_VisitRightChild;
+ left.d.u1.flattenData = uintptr_t(str) | Tag_VisitRightChild;
str = &left;
goto first_visit_node;
}
size_t len = left.length();
- PodCopy(pos, left.d.u1.chars, len);
+ PodCopy(pos, left.asLinear().chars(), len);
pos += len;
}
visit_right_child: {
- JSString &right = *str->d.s.u2.right;
+ JSString &right = *str->d.s.u3.right;
if (right.isRope()) {
/* Return to this node when 'right' done, then goto finish_node. */
- right.d.u0.flattenData = uintptr_t(str) | Tag_FinishNode;
+ right.d.u1.flattenData = uintptr_t(str) | Tag_FinishNode;
str = &right;
goto first_visit_node;
}
size_t len = right.length();
- PodCopy(pos, right.d.u1.chars, len);
+ PodCopy(pos, right.asLinear().chars(), len);
pos += len;
}
finish_node: {
if (str == this) {
JS_ASSERT(pos == wholeChars + wholeLength);
*pos = '\0';
- str->d.u0.lengthAndFlags = buildLengthAndFlags(wholeLength, EXTENSIBLE_FLAGS);
- str->d.u1.chars = wholeChars;
- str->d.s.u2.capacity = wholeCapacity;
- StringWriteBarrierPostRemove(maybecx, &str->d.u1.left);
- StringWriteBarrierPostRemove(maybecx, &str->d.s.u2.right);
+ str->d.u1.length = wholeLength;
+ str->d.u1.flags = EXTENSIBLE_FLAGS;
+ str->d.s.u2.nonInlineChars = wholeChars;
+ str->d.s.u3.capacity = wholeCapacity;
+ StringWriteBarrierPostRemove(maybecx, &str->d.s.u2.left);
+ StringWriteBarrierPostRemove(maybecx, &str->d.s.u3.right);
return &this->asFlat();
}
- uintptr_t flattenData = str->d.u0.flattenData;
- str->d.u0.lengthAndFlags = buildLengthAndFlags(pos - str->d.u1.chars, DEPENDENT_FLAGS);
- str->d.s.u2.base = (JSLinearString *)this; /* will be true on exit */
- StringWriteBarrierPost(maybecx, (JSString **)&str->d.s.u2.base);
+ uintptr_t flattenData = str->d.u1.flattenData;
+ str->d.u1.flags = DEPENDENT_FLAGS;
+ str->d.u1.length = pos - str->d.s.u2.nonInlineChars;
+ str->d.s.u3.base = (JSLinearString *)this; /* will be true on exit */
+ StringWriteBarrierPost(maybecx, (JSString **)&str->d.s.u3.base);
str = (JSString *)(flattenData & ~Tag_Mask);
if ((flattenData & Tag_Mask) == Tag_VisitRightChild)
goto visit_right_child;
JS_ASSERT((flattenData & Tag_Mask) == Tag_FinishNode);
goto finish_node;
}
}
@@ -434,17 +416,17 @@ JSDependentString::copyNonPureCharsZ(Thr
{
JS_ASSERT(JSString::isDependent());
size_t n = length();
jschar *s = cx->pod_malloc<jschar>(n + 1);
if (!s)
return false;
- PodCopy(s, chars(), n);
+ PodCopy(s, nonInlineChars(), n);
s[n] = 0;
out.reset(s);
return true;
}
JSFlatString *
JSDependentString::undepend(ExclusiveContext *cx)
@@ -459,25 +441,25 @@ JSDependentString::undepend(ExclusiveCon
JSString::writeBarrierPre(base());
size_t n = length();
size_t size = (n + 1) * sizeof(jschar);
jschar *s = (jschar *) cx->malloc_(size);
if (!s)
return nullptr;
- PodCopy(s, chars(), n);
+ PodCopy(s, nonInlineChars(), n);
s[n] = 0;
- d.u1.chars = s;
+ d.s.u2.nonInlineChars = s;
/*
* Transform *this into an undepended string so 'base' will remain rooted
* for the benefit of any other dependent string that depends on *this.
*/
- d.u0.lengthAndFlags = buildLengthAndFlags(n, UNDEPENDED_FLAGS);
+ d.u1.flags = UNDEPENDED_FLAGS;
return &this->asFlat();
}
bool
JSFlatString::isIndexSlow(uint32_t *indexp) const
{
const jschar *s = charsZ();
--- a/js/src/vm/String.h
+++ b/js/src/vm/String.h
@@ -132,136 +132,150 @@ class JSString : public js::gc::Barriere
{
protected:
static const size_t NUM_INLINE_CHARS = 2 * sizeof(void *) / sizeof(jschar);
/* Fields only apply to string types commented on the right. */
struct Data
{
union {
- size_t lengthAndFlags; /* JSString */
+ struct {
+ uint32_t flags; /* JSString */
+ uint32_t length; /* JSString */
+ };
uintptr_t flattenData; /* JSRope (temporary while flattening) */
- } u0;
- union {
- const jschar *chars; /* JSLinearString */
- JSString *left; /* JSRope */
} u1;
union {
jschar inlineStorage[NUM_INLINE_CHARS]; /* JS(Inline|FatInline)String */
struct {
union {
+ const jschar *nonInlineChars; /* JSLinearString, except JS(Inline|FatInline)String */
+ JSString *left; /* JSRope */
+ } u2;
+ union {
JSLinearString *base; /* JS(Dependent|Undepended)String */
JSString *right; /* JSRope */
size_t capacity; /* JSFlatString (extensible) */
const JSStringFinalizer *externalFinalizer;/* JSExternalString */
- } u2;
+ } u3;
} s;
};
} d;
public:
/* Flags exposed only for jits */
/*
- * The low LENGTH_SHIFT bits of lengthAndFlags are used to encode the type
- * of the string. The remaining bits store the string length (which must be
- * less or equal than MAX_LENGTH).
- *
* Instead of using a dense index to represent the most-derived type, string
* types are encoded to allow single-op tests for hot queries (isRope,
* isDependent, isFlat, isAtom) which, in view of subtyping, would require
* slower (isX() || isY() || isZ()).
*
* The string type encoding can be summarized as follows. The "instance
* encoding" entry for a type specifies the flag bits used to create a
* string instance of that type. Abstract types have no instances and thus
* have no such entry. The "subtype predicate" entry for a type specifies
* the predicate used to query whether a JSString instance is subtype
* (reflexively) of that type.
*
- * Rope 0000 0000
- * Linear - !0000
- * HasBase - xxx1
- * Dependent 0001 0001
- * Flat - isLinear && !isDependent
- * Undepended 0011 0011
- * Extensible 0010 0010
- * Inline 0100 isFlat && !isExtensible && (u1.chars == inlineStorage)
- * FatInline 0100 isInline && header in FINALIZE_FAT_INLINE_STRING arena
- * External 0100 header in FINALIZE_EXTERNAL_STRING arena
- * Atom - 1xxx
- * PermanentAtom 1100 1100
- * InlineAtom - isAtom && isInline
- * FatInlineAtom - isAtom && isFatInline
+ * String Instance Subtype
+ * type encoding predicate
+ * ------------------------------------
+ * Rope 000000 000000
+ * Linear - !000000
+ * HasBase - xxxx1x
+ * Dependent 000010 000010
+ * Flat - xxxxx1
+ * Undepended 000011 000011
+ * Extensible 010001 010001
+ * Inline 000101 xxx1xx
+ * FatInline 010101 x1x1xx
+ * External 100001 100001
+ * Atom 001001 xx1xxx
+ * PermanentAtom 101001 1x1xxx
+ * InlineAtom - xx11xx
+ * FatInlineAtom - x111xx
+ *
+ * Note that the first 4 flag bits (from right to left in the previous table)
+ * have the following meaning and can be used for some hot queries:
+ *
+ * Bit 0: IsFlat
+ * Bit 1: HasBase (Dependent, Undepended)
+ * Bit 2: IsInline (Inline, FatInline)
+ * Bit 3: IsAtom (Atom, PermanentAtom)
*
* "HasBase" here refers to the two string types that have a 'base' field:
* JSDependentString and JSUndependedString.
* A JSUndependedString is a JSDependentString which has been 'fixed' (by ensureFixed)
* to be null-terminated. In such cases, the string must keep marking its base since
* there may be any number of *other* JSDependentStrings transitively depending on it.
*
*/
- static const size_t LENGTH_SHIFT = 4;
- static const size_t FLAGS_MASK = JS_BITMASK(LENGTH_SHIFT);
-
- static const size_t ROPE_FLAGS = 0;
- static const size_t DEPENDENT_FLAGS = JS_BIT(0);
- static const size_t UNDEPENDED_FLAGS = JS_BIT(0) | JS_BIT(1);
- static const size_t EXTENSIBLE_FLAGS = JS_BIT(1);
- static const size_t FIXED_FLAGS = JS_BIT(2);
-
- static const size_t INT32_MASK = JS_BITMASK(3);
- static const size_t INT32_FLAGS = JS_BIT(1) | JS_BIT(2);
+ static const uint32_t FLAT_BIT = JS_BIT(0);
+ static const uint32_t HAS_BASE_BIT = JS_BIT(1);
+ static const uint32_t INLINE_CHARS_BIT = JS_BIT(2);
+ static const uint32_t ATOM_BIT = JS_BIT(3);
- static const size_t HAS_BASE_BIT = JS_BIT(0);
- static const size_t PERMANENT_BIT = JS_BIT(2);
- static const size_t ATOM_BIT = JS_BIT(3);
-
- static const size_t PERMANENT_ATOM_FLAGS = JS_BIT(2) | JS_BIT(3);
+ static const uint32_t ROPE_FLAGS = 0;
+ static const uint32_t DEPENDENT_FLAGS = HAS_BASE_BIT;
+ static const uint32_t UNDEPENDED_FLAGS = FLAT_BIT | HAS_BASE_BIT;
+ static const uint32_t EXTENSIBLE_FLAGS = FLAT_BIT | JS_BIT(4);
+ static const uint32_t EXTERNAL_FLAGS = FLAT_BIT | JS_BIT(5);
- static const size_t MAX_LENGTH = JS_BIT(32 - LENGTH_SHIFT) - 1;
+ static const uint32_t FAT_INLINE_MASK = INLINE_CHARS_BIT | JS_BIT(4);
+ static const uint32_t PERMANENT_ATOM_MASK = ATOM_BIT | JS_BIT(5);
- size_t buildLengthAndFlags(size_t length, size_t flags) {
- JS_ASSERT(length <= MAX_LENGTH);
- JS_ASSERT(flags <= FLAGS_MASK);
- return (length << LENGTH_SHIFT) | flags;
- }
+ /* Initial flags for inline and fat inline strings. */
+ static const uint32_t INIT_INLINE_FLAGS = FLAT_BIT | INLINE_CHARS_BIT;
+ static const uint32_t INIT_FAT_INLINE_FLAGS = FLAT_BIT | FAT_INLINE_MASK;
+
+ static const uint32_t MAX_LENGTH = JS_BIT(28) - 1;
/*
* Helper function to validate that a string of a given length is
* representable by a JSString. An allocation overflow is reported if false
* is returned.
*/
static inline bool validateLength(js::ThreadSafeContext *maybecx, size_t length);
static void staticAsserts() {
- JS_STATIC_ASSERT(JS_BITS_PER_WORD >= 32);
- JS_STATIC_ASSERT(((JSString::MAX_LENGTH << JSString::LENGTH_SHIFT) >>
- JSString::LENGTH_SHIFT) == JSString::MAX_LENGTH);
- JS_STATIC_ASSERT(sizeof(JSString) ==
- offsetof(JSString, d.inlineStorage) + NUM_INLINE_CHARS * sizeof(jschar));
- JS_STATIC_ASSERT(offsetof(JSString, d.u1.chars) ==
- offsetof(js::shadow::Atom, chars));
+ static_assert(JSString::MAX_LENGTH < UINT32_MAX, "Length must fit in 32 bits");
+ static_assert(sizeof(JSString) ==
+ offsetof(JSString, d.inlineStorage) + NUM_INLINE_CHARS * sizeof(jschar),
+ "NUM_INLINE_CHARS inline chars must fit in a JSString");
+
+ /* Ensure js::shadow::Atom has the same layout. */
+ using js::shadow::Atom;
+ static_assert(offsetof(JSString, d.u1.length) == offsetof(Atom, length),
+ "shadow::Atom length offset must match JSString");
+ static_assert(offsetof(JSString, d.u1.flags) == offsetof(Atom, flags),
+ "shadow::Atom flags offset must match JSString");
+ static_assert(offsetof(JSString, d.s.u2.nonInlineChars) == offsetof(Atom, nonInlineChars),
+ "shadow::Atom nonInlineChars offset must match JSString");
+ static_assert(offsetof(JSString, d.inlineStorage) == offsetof(Atom, inlineStorage),
+ "shadow::Atom inlineStorage offset must match JSString");
+ static_assert(INLINE_CHARS_BIT == Atom::INLINE_CHARS_BIT,
+ "shadow::Atom::INLINE_CHARS_BIT must match JSString::INLINE_CHARS_BIT");
}
/* Avoid lame compile errors in JSRope::flatten */
friend class JSRope;
public:
/* All strings have length. */
MOZ_ALWAYS_INLINE
size_t length() const {
- return d.u0.lengthAndFlags >> LENGTH_SHIFT;
+ return d.u1.length;
}
MOZ_ALWAYS_INLINE
bool empty() const {
- return d.u0.lengthAndFlags <= FLAGS_MASK;
+ return d.u1.length == 0;
}
/*
* All strings have a fallible operation to get an array of chars.
* getCharsZ additionally ensures the array is null terminated.
*/
inline const jschar *getChars(js::ExclusiveContext *cx);
@@ -293,17 +307,17 @@ class JSString : public js::gc::Barriere
static bool ensureLinear(js::ExclusiveContext *cx, JSString *str) {
return str->ensureLinear(cx) != nullptr;
}
/* Type query and debug-checked casts */
MOZ_ALWAYS_INLINE
bool isRope() const {
- return (d.u0.lengthAndFlags & FLAGS_MASK) == ROPE_FLAGS;
+ return d.u1.flags == ROPE_FLAGS;
}
MOZ_ALWAYS_INLINE
JSRope &asRope() const {
JS_ASSERT(isRope());
return *(JSRope *)this;
}
@@ -315,117 +329,124 @@ class JSString : public js::gc::Barriere
MOZ_ALWAYS_INLINE
JSLinearString &asLinear() const {
JS_ASSERT(JSString::isLinear());
return *(JSLinearString *)this;
}
MOZ_ALWAYS_INLINE
bool isDependent() const {
- return (d.u0.lengthAndFlags & FLAGS_MASK) == DEPENDENT_FLAGS;
+ return d.u1.flags == DEPENDENT_FLAGS;
}
MOZ_ALWAYS_INLINE
JSDependentString &asDependent() const {
JS_ASSERT(isDependent());
return *(JSDependentString *)this;
}
MOZ_ALWAYS_INLINE
bool isFlat() const {
- return isLinear() && !isDependent();
+ return d.u1.flags & FLAT_BIT;
}
MOZ_ALWAYS_INLINE
JSFlatString &asFlat() const {
JS_ASSERT(isFlat());
return *(JSFlatString *)this;
}
MOZ_ALWAYS_INLINE
bool isExtensible() const {
- return (d.u0.lengthAndFlags & FLAGS_MASK) == EXTENSIBLE_FLAGS;
+ return d.u1.flags == EXTENSIBLE_FLAGS;
}
MOZ_ALWAYS_INLINE
JSExtensibleString &asExtensible() const {
JS_ASSERT(isExtensible());
return *(JSExtensibleString *)this;
}
MOZ_ALWAYS_INLINE
bool isInline() const {
- return isFlat() && !isExtensible() && (d.u1.chars == d.inlineStorage);
+ return d.u1.flags & INLINE_CHARS_BIT;
}
MOZ_ALWAYS_INLINE
JSInlineString &asInline() const {
JS_ASSERT(isInline());
return *(JSInlineString *)this;
}
- bool isFatInline() const;
+ MOZ_ALWAYS_INLINE
+ bool isFatInline() const {
+ return (d.u1.flags & FAT_INLINE_MASK) == FAT_INLINE_MASK;
+ }
/* For hot code, prefer other type queries. */
- bool isExternal() const;
+ bool isExternal() const {
+ return d.u1.flags == EXTERNAL_FLAGS;
+ }
MOZ_ALWAYS_INLINE
JSExternalString &asExternal() const {
JS_ASSERT(isExternal());
return *(JSExternalString *)this;
}
MOZ_ALWAYS_INLINE
bool isUndepended() const {
- return (d.u0.lengthAndFlags & FLAGS_MASK) == UNDEPENDED_FLAGS;
+ return d.u1.flags == UNDEPENDED_FLAGS;
}
MOZ_ALWAYS_INLINE
bool isAtom() const {
- return d.u0.lengthAndFlags & ATOM_BIT;
+ return d.u1.flags & ATOM_BIT;
}
MOZ_ALWAYS_INLINE
bool isPermanentAtom() const {
- return (d.u0.lengthAndFlags & FLAGS_MASK) == PERMANENT_ATOM_FLAGS;
+ return (d.u1.flags & PERMANENT_ATOM_MASK) == PERMANENT_ATOM_MASK;
}
MOZ_ALWAYS_INLINE
JSAtom &asAtom() const {
JS_ASSERT(isAtom());
return *(JSAtom *)this;
}
/* Only called by the GC for dependent or undepended strings. */
inline bool hasBase() const {
- JS_STATIC_ASSERT((DEPENDENT_FLAGS | JS_BIT(1)) == UNDEPENDED_FLAGS);
- return d.u0.lengthAndFlags & HAS_BASE_BIT;
+ return d.u1.flags & HAS_BASE_BIT;
}
inline JSLinearString *base() const;
inline void markBase(JSTracer *trc);
/* Only called by the GC for strings with the FINALIZE_STRING kind. */
inline void finalize(js::FreeOp *fop);
/* Gets the number of bytes that the chars take on the heap. */
size_t sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf);
/* Offsets for direct field from jit code. */
- static size_t offsetOfLengthAndFlags() {
- return offsetof(JSString, d.u0.lengthAndFlags);
+ static size_t offsetOfLength() {
+ return offsetof(JSString, d.u1.length);
+ }
+ static size_t offsetOfFlags() {
+ return offsetof(JSString, d.u1.flags);
}
- static size_t offsetOfChars() {
- return offsetof(JSString, d.u1.chars);
+ static size_t offsetOfNonInlineChars() {
+ return offsetof(JSString, d.s.u2.nonInlineChars);
}
js::gc::AllocKind getAllocKind() const { return tenuredGetAllocKind(); }
static inline js::ThingRootKind rootKind() { return js::THING_ROOT_STRING; }
#ifdef DEBUG
void dump();
@@ -478,55 +499,58 @@ class JSRope : public JSString
template <js::AllowGC allowGC>
static inline JSRope *new_(js::ThreadSafeContext *cx,
typename js::MaybeRooted<JSString*, allowGC>::HandleType left,
typename js::MaybeRooted<JSString*, allowGC>::HandleType right,
size_t length);
inline JSString *leftChild() const {
JS_ASSERT(isRope());
- return d.u1.left;
+ return d.s.u2.left;
}
inline JSString *rightChild() const {
JS_ASSERT(isRope());
- return d.s.u2.right;
+ return d.s.u3.right;
}
inline void markChildren(JSTracer *trc);
inline static size_t offsetOfLeft() {
- return offsetof(JSRope, d.u1.left);
+ return offsetof(JSRope, d.s.u2.left);
}
inline static size_t offsetOfRight() {
- return offsetof(JSRope, d.s.u2.right);
+ return offsetof(JSRope, d.s.u3.right);
}
};
JS_STATIC_ASSERT(sizeof(JSRope) == sizeof(JSString));
class JSLinearString : public JSString
{
friend class JSString;
/* Vacuous and therefore unimplemented. */
JSLinearString *ensureLinear(JSContext *cx) MOZ_DELETE;
bool isLinear() const MOZ_DELETE;
JSLinearString &asLinear() const MOZ_DELETE;
public:
MOZ_ALWAYS_INLINE
- const jschar *chars() const {
- JS_ASSERT(JSString::isLinear());
- return d.u1.chars;
+ const jschar *nonInlineChars() const {
+ JS_ASSERT(!isInline());
+ return d.s.u2.nonInlineChars;
}
+ MOZ_ALWAYS_INLINE
+ const jschar *chars() const;
+
JS::TwoByteChars range() const {
JS_ASSERT(JSString::isLinear());
- return JS::TwoByteChars(d.u1.chars, length());
+ return JS::TwoByteChars(chars(), length());
}
};
JS_STATIC_ASSERT(sizeof(JSLinearString) == sizeof(JSString));
class JSDependentString : public JSLinearString
{
bool copyNonPureCharsZ(js::ThreadSafeContext *cx, js::ScopedJSFreePtr<jschar> &out) const;
@@ -536,16 +560,19 @@ class JSDependentString : public JSLinea
void init(js::ThreadSafeContext *cx, JSLinearString *base, const jschar *chars,
size_t length);
/* Vacuous and therefore unimplemented. */
bool isDependent() const MOZ_DELETE;
JSDependentString &asDependent() const MOZ_DELETE;
+ /* Hide chars(), nonInlineChars() is more efficient. */
+ const jschar *chars() const MOZ_DELETE;
+
public:
static inline JSLinearString *new_(js::ExclusiveContext *cx, JSLinearString *base,
const jschar *chars, size_t length);
};
JS_STATIC_ASSERT(sizeof(JSDependentString) == sizeof(JSString));
class JSFlatString : public JSLinearString
@@ -588,58 +615,70 @@ class JSFlatString : public JSLinearStri
*/
inline js::PropertyName *toPropertyName(JSContext *cx);
/*
* Once a JSFlatString sub-class has been added to the atom state, this
* operation changes the string to the JSAtom type, in place.
*/
MOZ_ALWAYS_INLINE JSAtom *morphAtomizedStringIntoAtom() {
- d.u0.lengthAndFlags = buildLengthAndFlags(length(), ATOM_BIT);
+ d.u1.flags |= ATOM_BIT;
return &asAtom();
}
MOZ_ALWAYS_INLINE JSAtom *morphAtomizedStringIntoPermanentAtom() {
- d.u0.lengthAndFlags = buildLengthAndFlags(length(), PERMANENT_ATOM_FLAGS);
+ d.u1.flags |= PERMANENT_ATOM_MASK;
return &asAtom();
}
inline void finalize(js::FreeOp *fop);
};
JS_STATIC_ASSERT(sizeof(JSFlatString) == sizeof(JSString));
class JSExtensibleString : public JSFlatString
{
/* Vacuous and therefore unimplemented. */
bool isExtensible() const MOZ_DELETE;
JSExtensibleString &asExtensible() const MOZ_DELETE;
+ /* Hide chars(), nonInlineChars() is more efficient. */
+ const jschar *chars() const MOZ_DELETE;
+
public:
MOZ_ALWAYS_INLINE
size_t capacity() const {
JS_ASSERT(JSString::isExtensible());
- return d.s.u2.capacity;
+ return d.s.u3.capacity;
}
};
JS_STATIC_ASSERT(sizeof(JSExtensibleString) == sizeof(JSString));
/* On 32-bit platforms, MAX_INLINE_LENGTH is 4. On 64-bit platforms it is 8. */
class JSInlineString : public JSFlatString
{
static const size_t MAX_INLINE_LENGTH = NUM_INLINE_CHARS - 1;
+ /* Hide chars(), inlineChars() is more efficient. */
+ const jschar *chars() const MOZ_DELETE;
+
public:
template <js::AllowGC allowGC>
static inline JSInlineString *new_(js::ThreadSafeContext *cx);
inline jschar *init(size_t length);
inline void resetLength(size_t length);
+ MOZ_ALWAYS_INLINE
+ const jschar *inlineChars() const {
+ const char *p = reinterpret_cast<const char *>(this);
+ return reinterpret_cast<const jschar *>(p + offsetOfInlineStorage());
+ }
+
static bool lengthFits(size_t length) {
return length <= MAX_INLINE_LENGTH;
}
static size_t offsetOfInlineStorage() {
return offsetof(JSInlineString, d.inlineStorage);
}
};
@@ -663,27 +702,32 @@ class JSFatInlineString : public JSInlin
static void staticAsserts() {
JS_STATIC_ASSERT((INLINE_EXTENSION_CHARS * sizeof(jschar)) % js::gc::CellSize == 0);
JS_STATIC_ASSERT(MAX_FAT_INLINE_LENGTH + 1 ==
(sizeof(JSFatInlineString) -
offsetof(JSFatInlineString, d.inlineStorage)) / sizeof(jschar));
}
+ /* Hide chars(), inlineChars() is more efficient. */
+ const jschar *chars() const MOZ_DELETE;
+
protected: /* to fool clang into not warning this is unused */
jschar inlineStorageExtension[INLINE_EXTENSION_CHARS];
public:
template <js::AllowGC allowGC>
static inline JSFatInlineString *new_(js::ThreadSafeContext *cx);
static const size_t MAX_FAT_INLINE_LENGTH = JSString::NUM_INLINE_CHARS +
INLINE_EXTENSION_CHARS
-1 /* null terminator */;
+ inline jschar *init(size_t length);
+
static bool lengthFits(size_t length) {
return length <= MAX_FAT_INLINE_LENGTH;
}
/* Only called by the GC for strings with the FINALIZE_FAT_INLINE_STRING kind. */
MOZ_ALWAYS_INLINE void finalize(js::FreeOp *fop);
};
@@ -693,23 +737,26 @@ JS_STATIC_ASSERT(sizeof(JSFatInlineStrin
class JSExternalString : public JSFlatString
{
void init(const jschar *chars, size_t length, const JSStringFinalizer *fin);
/* Vacuous and therefore unimplemented. */
bool isExternal() const MOZ_DELETE;
JSExternalString &asExternal() const MOZ_DELETE;
+ /* Hide chars(), nonInlineChars() is more efficient. */
+ const jschar *chars() const MOZ_DELETE;
+
public:
static inline JSExternalString *new_(JSContext *cx, const jschar *chars, size_t length,
const JSStringFinalizer *fin);
const JSStringFinalizer *externalFinalizer() const {
JS_ASSERT(JSString::isExternal());
- return d.s.u2.externalFinalizer;
+ return d.s.u3.externalFinalizer;
}
/* Only called by the GC for strings with the FINALIZE_EXTERNAL_STRING kind. */
inline void finalize(js::FreeOp *fop);
};
JS_STATIC_ASSERT(sizeof(JSExternalString) == sizeof(JSString));
@@ -734,23 +781,23 @@ class JSAtom : public JSFlatString
public:
/* Returns the PropertyName for this. isIndex() must be false. */
inline js::PropertyName *asPropertyName();
inline void finalize(js::FreeOp *fop);
MOZ_ALWAYS_INLINE
bool isPermanent() const {
- return d.u0.lengthAndFlags & PERMANENT_BIT;
+ return JSString::isPermanentAtom();
}
// Transform this atom into a permanent atom. This is only done during
// initialization of the runtime.
MOZ_ALWAYS_INLINE void morphIntoPermanentAtom() {
- d.u0.lengthAndFlags = buildLengthAndFlags(length(), PERMANENT_ATOM_FLAGS);
+ d.u1.flags |= PERMANENT_ATOM_MASK;
}
#ifdef DEBUG
void dump();
#endif
};
JS_STATIC_ASSERT(sizeof(JSAtom) == sizeof(JSString));
@@ -1043,18 +1090,25 @@ JSString::ensureFlat(js::ExclusiveContex
? asDependent().undepend(cx)
: asRope().flatten(cx);
}
inline JSLinearString *
JSString::base() const
{
JS_ASSERT(hasBase());
- JS_ASSERT(!d.s.u2.base->isInline());
- return d.s.u2.base;
+ JS_ASSERT(!d.s.u3.base->isInline());
+ return d.s.u3.base;
+}
+
+MOZ_ALWAYS_INLINE const jschar *
+JSLinearString::chars() const
+{
+ JS_ASSERT(JSString::isLinear());
+ return isInline() ? asInline().inlineChars() : nonInlineChars();
}
inline js::PropertyName *
JSAtom::asPropertyName()
{
#ifdef DEBUG
uint32_t dummy;
JS_ASSERT(!isIndex(&dummy));
--- a/js/src/vm/TraceLogging.h
+++ b/js/src/vm/TraceLogging.h
@@ -132,16 +132,17 @@ namespace jit {
_(IrregexpExecute) \
_(VM) \
\
/* Specific passes during ion compilation */ \
_(SplitCriticalEdges) \
_(RenumberBlocks) \
_(DominatorTree) \
_(PhiAnalysis) \
+ _(MakeLoopsContiguous) \
_(ApplyTypes) \
_(ParallelSafetyAnalysis) \
_(AliasAnalysis) \
_(GVN) \
_(UCE) \
_(LICM) \
_(RangeAnalysis) \
_(EffectiveAddressAnalysis) \
--- a/js/src/vm/WeakMapObject.h
+++ b/js/src/vm/WeakMapObject.h
@@ -7,17 +7,24 @@
#ifndef vm_WeakMapObject_h
#define vm_WeakMapObject_h
#include "jsobj.h"
#include "jsweakmap.h"
namespace js {
-typedef WeakMap<PreBarrieredObject, RelocatableValue> ObjectValueMap;
+class ObjectValueMap : public WeakMap<PreBarrieredObject, RelocatableValue>
+{
+ public:
+ ObjectValueMap(JSContext *cx, JSObject *obj)
+ : WeakMap<PreBarrieredObject, RelocatableValue>(cx, obj) {}
+
+ virtual bool findZoneEdges();
+};
class WeakMapObject : public JSObject
{
public:
static const Class class_;
ObjectValueMap *getMap() { return static_cast<ObjectValueMap*>(getPrivate()); }
};
--- a/js/src/vm/WeakMapPtr.cpp
+++ b/js/src/vm/WeakMapPtr.cpp
@@ -53,17 +53,16 @@ void
JS::WeakMapPtr<K, V>::destroy()
{
MOZ_ASSERT(initialized());
auto map = Utils<K, V>::cast(ptr);
// If this destruction happens mid-GC, we might be in the compartment's list
// of known live weakmaps. If we are, remove ourselves before deleting.
if (map->isInList())
WeakMapBase::removeWeakMapFromList(map);
- map->check();
js_delete(map);
ptr = nullptr;
}
template <typename K, typename V>
bool
JS::WeakMapPtr<K, V>::init(JSContext *cx)
{
--- a/layout/forms/nsListControlFrame.cpp
+++ b/layout/forms/nsListControlFrame.cpp
@@ -2126,22 +2126,21 @@ nsListControlFrame::KeyDown(nsIDOMEvent*
case NS_VK_DOWN:
case NS_VK_RIGHT:
AdjustIndexForDisabledOpt(mEndSelectionIndex, newIndex,
static_cast<int32_t>(numOptions),
1, 1);
break;
case NS_VK_RETURN:
if (IsInDropDownMode()) {
- // If the select element is a dropdown style, Enter key should be
- // consumed everytime since Enter key may be pressed accidentally after
- // the dropdown is closed by Enter key press.
- aKeyEvent->PreventDefault();
+ if (mComboboxFrame->IsDroppedDown()) {
+ // If the select element is a dropdown style, Enter key should be
+ // consumed while the dropdown is open for security.
+ aKeyEvent->PreventDefault();
- if (mComboboxFrame->IsDroppedDown()) {
nsWeakFrame weakFrame(this);
ComboboxFinish(mEndSelectionIndex);
if (!weakFrame.IsAlive()) {
return NS_OK;
}
}
// XXX This is strange. On other browsers, "change" event is fired
// immediately after the selected item is changed rather than
--- a/layout/forms/test/test_bug935876.html
+++ b/layout/forms/test/test_bug935876.html
@@ -316,17 +316,17 @@ function runTests()
for (var i = 0; i < 2; i++) {
reset()
synthesizeKey("VK_HOME", {});
check(true, "Home key on combobox #" + i);
}
reset()
synthesizeKey("VK_RETURN", {});
- check(true, "Enter key on combobox");
+ check(false, "Enter key on combobox");
reset()
synthesizeKey("VK_ESCAPE", {});
check(true, "Esc key on combobox");
if (!kIsWin) {
reset()
synthesizeKey("VK_F4", {});
--- a/media/webrtc/signaling/src/media-conduit/WebrtcOMXH264VideoCodec.cpp
+++ b/media/webrtc/signaling/src/media-conduit/WebrtcOMXH264VideoCodec.cpp
@@ -128,16 +128,18 @@ public:
}
void Stop() {
MonitorAutoLock lock(mMonitor);
mEnding = true;
lock.NotifyAll(); // In case Run() is waiting.
if (mThread != nullptr) {
+ MonitorAutoUnlock unlock(mMonitor);
+ CODEC_LOGD("OMXOutputDrain thread shutdown");
mThread->Shutdown();
mThread = nullptr;
}
CODEC_LOGD("OMXOutputDrain stopped");
}
void QueueInput(const EncodedFrame& aFrame)
{
@@ -686,16 +688,21 @@ WebrtcOMXH264VideoEncoder::Encode(const
if (!mOMXConfigured) {
mOMX->Configure(mWidth, mHeight, mFrameRate,
OMXVideoEncoder::BlobFormat::AVC_NAL);
mOMXConfigured = true;
CODEC_LOGD("WebrtcOMXH264VideoEncoder:%p start OMX with image size:%ux%u",
this, mWidth, mHeight);
}
+ if (aFrameTypes && aFrameTypes->size() &&
+ ((*aFrameTypes)[0] == webrtc::kKeyFrame)) {
+ mOMX->RequestIDRFrame();
+ }
+
// Wrap I420VideoFrame input with PlanarYCbCrImage for OMXVideoEncoder.
layers::PlanarYCbCrData yuvData;
yuvData.mYChannel = const_cast<uint8_t*>(aInputImage.buffer(webrtc::kYPlane));
yuvData.mYSize = gfx::IntSize(aInputImage.width(), aInputImage.height());
yuvData.mYStride = aInputImage.stride(webrtc::kYPlane);
MOZ_ASSERT(aInputImage.stride(webrtc::kUPlane) == aInputImage.stride(webrtc::kVPlane));
yuvData.mCbCrStride = aInputImage.stride(webrtc::kUPlane);
yuvData.mCbChannel = const_cast<uint8_t*>(aInputImage.buffer(webrtc::kUPlane));
--- a/mobile/android/base/preferences/PanelsPreference.java
+++ b/mobile/android/base/preferences/PanelsPreference.java
@@ -25,43 +25,40 @@ public class PanelsPreference extends Cu
protected String LOGTAG = "PanelsPreference";
// Position state of this Preference in enclosing category.
private static final int STATE_IS_FIRST = 0;
private static final int STATE_IS_LAST = 1;
/**
* Index of the context menu button for controlling display options.
- * For (removable) Dynamic panels, this button removes the panel.
- * For built-in panels, this button toggles showing or hiding the panel.
+ * This button toggles showing or hiding the panel.
*/
private static final int INDEX_DISPLAY_BUTTON = 1;
private static final int INDEX_REORDER_BUTTON = 2;
// Indices of buttons in context menu for reordering.
private static final int INDEX_MOVE_UP_BUTTON = 0;
private static final int INDEX_MOVE_DOWN_BUTTON = 1;
private String LABEL_HIDE;
private String LABEL_SHOW;
private View preferenceView;
protected boolean mIsHidden = false;
- private boolean mIsRemovable;
private boolean mAnimate;
private static final int ANIMATION_DURATION_MS = 400;
// State for reordering.
private int mPositionState = -1;
private final int mIndex;
- public PanelsPreference(Context context, CustomListCategory parentCategory, boolean isRemovable, int index, boolean animate) {
+ public PanelsPreference(Context context, CustomListCategory parentCategory, int index, boolean animate) {
super(context, parentCategory);
- mIsRemovable = isRemovable;
mIndex = index;
mAnimate = animate;
}
@Override
protected int getPreferenceLayoutResource() {
return R.layout.preference_panels;
}
@@ -93,21 +90,16 @@ public class PanelsPreference extends Cu
}
}
@Override
protected String[] createDialogItems() {
final Resources res = getContext().getResources();
final String labelReorder = res.getString(R.string.pref_panels_reorder);
- if (mIsRemovable) {
- return new String[] { LABEL_SET_AS_DEFAULT, LABEL_REMOVE, labelReorder };
- }
-
- // Built-in panels can't be removed, so use show/hide options.
LABEL_HIDE = res.getString(R.string.pref_panels_hide);
LABEL_SHOW = res.getString(R.string.pref_panels_show);
return new String[] { LABEL_SET_AS_DEFAULT, LABEL_HIDE, labelReorder };
}
@Override
public void setIsDefault(boolean isDefault) {
@@ -126,24 +118,18 @@ public class PanelsPreference extends Cu
@Override
protected void onDialogIndexClicked(int index) {
switch(index) {
case INDEX_SET_DEFAULT_BUTTON:
mParentCategory.setDefault(this);
break;
case INDEX_DISPLAY_BUTTON:
- // Handle display options for the panel.
- if (mIsRemovable) {
- // For removable panels, the button displays text for removing the panel.
- mParentCategory.uninstall(this);
- } else {
- // Otherwise, the button toggles between text for showing or hiding the panel.
- ((PanelsPreferenceCategory) mParentCategory).setHidden(this, !mIsHidden);
- }
+ // The button toggles between text for showing or hiding the panel.
+ ((PanelsPreferenceCategory) mParentCategory).setHidden(this, !mIsHidden);
break;
case INDEX_REORDER_BUTTON:
// Display dialog for changing preference order.
final Dialog orderDialog = makeReorderDialog();
orderDialog.show();
break;
@@ -152,20 +138,18 @@ public class PanelsPreference extends Cu
}
}
@Override
protected void configureShownDialog() {
super.configureShownDialog();
// Handle Show/Hide buttons.
- if (!mIsRemovable) {
- final TextView hideButton = (TextView) mDialog.getListView().getChildAt(INDEX_DISPLAY_BUTTON);
- hideButton.setText(mIsHidden ? LABEL_SHOW : LABEL_HIDE);
- }
+ final TextView hideButton = (TextView) mDialog.getListView().getChildAt(INDEX_DISPLAY_BUTTON);
+ hideButton.setText(mIsHidden ? LABEL_SHOW : LABEL_HIDE);
}
private Dialog makeReorderDialog() {
final AlertDialog.Builder builder = new AlertDialog.Builder(getContext());
final Resources res = getContext().getResources();
final String labelUp = res.getString(R.string.pref_panels_move_up);
--- a/mobile/android/base/preferences/PanelsPreferenceCategory.java
+++ b/mobile/android/base/preferences/PanelsPreferenceCategory.java
@@ -17,19 +17,16 @@ import android.text.TextUtils;
import android.util.AttributeSet;
public class PanelsPreferenceCategory extends CustomListCategory {
public static final String LOGTAG = "PanelsPrefCategory";
protected HomeConfig mHomeConfig;
protected HomeConfig.Editor mConfigEditor;
- // Account for the fake "Add Panel" preference in preference counting.
- private static final int PANEL_PREFS_OFFSET = 1;
-
protected UiAsyncTask<Void, Void, HomeConfig.State> mLoadTask;
public PanelsPreferenceCategory(Context context) {
super(context);
initConfig(context);
}
public PanelsPreferenceCategory(Context context, AttributeSet attrs) {
@@ -82,41 +79,34 @@ public class PanelsPreferenceCategory ex
/**
* Refresh the Home Panels list and animate a panel, if specified.
* If null, load from HomeConfig.
*
* @param State HomeConfig.State to rebuild Home Panels list from.
* @param String panelId of panel to be animated.
*/
public void refresh(State state, String animatePanelId) {
- // Clear all the existing home panels, but leave the
- // first item (Add panels).
- int prefCount = getPreferenceCount();
- while (prefCount > 1) {
- removePreference(getPreference(1));
- prefCount--;
- }
+ // Clear all the existing home panels.
+ removeAll();
if (state == null) {
loadHomeConfig(animatePanelId);
} else {
displayHomeConfig(state, animatePanelId);
}
}
private void displayHomeConfig(HomeConfig.State configState, String animatePanelId) {
int index = 0;
for (PanelConfig panelConfig : configState) {
- final boolean isRemovable = panelConfig.isDynamic();
-
// Create and add the pref.
final String panelId = panelConfig.getId();
final boolean animate = TextUtils.equals(animatePanelId, panelId);
- final PanelsPreference pref = new PanelsPreference(getContext(), PanelsPreferenceCategory.this, isRemovable, index, animate);
+ final PanelsPreference pref = new PanelsPreference(getContext(), PanelsPreferenceCategory.this, index, animate);
pref.setTitle(panelConfig.getTitle());
pref.setKey(panelConfig.getId());
// XXX: Pull icon from PanelInfo.
addPreference(pref);
if (panelConfig.isDisabled()) {
pref.setHidden(true);
}
@@ -127,34 +117,33 @@ public class PanelsPreferenceCategory ex
setPositionState();
setDefaultFromConfig();
}
private void setPositionState() {
final int prefCount = getPreferenceCount();
// Pass in position state to first and last preference.
- final PanelsPreference firstPref = (PanelsPreference) getPreference(PANEL_PREFS_OFFSET);
+ final PanelsPreference firstPref = (PanelsPreference) getPreference(0);
firstPref.setIsFirst();
final PanelsPreference lastPref = (PanelsPreference) getPreference(prefCount - 1);
lastPref.setIsLast();
}
private void setDefaultFromConfig() {
final String defaultPanelId = mConfigEditor.getDefaultPanelId();
if (defaultPanelId == null) {
mDefaultReference = null;
return;
}
final int prefCount = getPreferenceCount();
- // First preference (index 0) is Preference to add panels.
- for (int i = 1; i < prefCount; i++) {
+ for (int i = 0; i < prefCount; i++) {
final PanelsPreference pref = (PanelsPreference) getPreference(i);
if (defaultPanelId.equals(pref.getKey())) {
super.setDefault(pref);
break;
}
}
}
--- a/mobile/android/base/resources/xml/preferences_home.xml
+++ b/mobile/android/base/resources/xml/preferences_home.xml
@@ -4,23 +4,17 @@
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:gecko="http://schemas.android.com/apk/res-auto"
android:title="@string/pref_category_home"
android:enabled="false">
<org.mozilla.gecko.preferences.PanelsPreferenceCategory
- android:title="@string/pref_category_home_panels">
-
- <Preference android:key="android.not_a_preference.home.add_panel"
- android:title="@string/pref_home_add_panel"
- android:icon="@drawable/icon_new_home_panel" />
-
- </org.mozilla.gecko.preferences.PanelsPreferenceCategory>
+ android:title="@string/pref_category_home_panels"/>
<PreferenceCategory android:title="@string/pref_category_home_content_settings">
<ListPreference android:key="home.sync.updateMode"
android:title="@string/pref_home_updates"
android:entries="@array/pref_home_updates_entries"
android:entryValues="@array/pref_home_updates_values"
android:persistent="false" />
--- a/mobile/android/chrome/content/aboutDownloads.js
+++ b/mobile/android/chrome/content/aboutDownloads.js
@@ -1,354 +1,617 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
-"use strict";
-
-const Cu = Components.utils;
+let Ci = Components.interfaces, Cc = Components.classes, Cu = Components.utils;
-Cu.import("resource://gre/modules/XPCOMUtils.jsm", this);
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/DownloadUtils.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/PluralForm.jsm");
+Cu.import("resource://gre/modules/PrivateBrowsingUtils.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "Downloads", "resource://gre/modules/Downloads.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "DownloadUtils", "resource://gre/modules/DownloadUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "OS", "resource://gre/modules/osfile.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "PluralForm", "resource://gre/modules/PluralForm.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "Services", "resource://gre/modules/Services.jsm");
-XPCOMUtils.defineLazyGetter(this, "strings",
- () => Services.strings.createBundle("chrome://browser/locale/aboutDownloads.properties"));
+let gStrings = Services.strings.createBundle("chrome://browser/locale/aboutDownloads.properties");
-function deleteDownload(download) {
- download.finalize(true).then(null, Cu.reportError);
- OS.File.remove(download.target.path).then(null, ex => {
- if (!(ex instanceof OS.File.Error && ex.becauseNoSuchFile)) {
- Cu.reportError(ex);
- }
- });
-}
-
-let contextMenu = {
- _items: [],
- _targetDownload: null,
+let downloadTemplate =
+"<li downloadGUID='{guid}' class='list-item' role='button' state='{state}' contextmenu='downloadmenu'>" +
+ "<img class='icon' src='{icon}'/>" +
+ "<div class='details'>" +
+ "<div class='row'>" +
+ // This is a hack so that we can crop this label in its center
+ "<xul:label class='title' crop='center' value='{target}'/>" +
+ "<div class='date'>{date}</div>" +
+ "</div>" +
+ "<div class='size'>{size}</div>" +
+ "<div class='domain'>{domain}</div>" +
+ "<div class='displayState'>{displayState}</div>" +
+ "</div>" +
+"</li>";
- init: function () {
- let element = document.getElementById("downloadmenu");
- element.addEventListener("click",
- event => event.download = this._targetDownload,
- true);
+XPCOMUtils.defineLazyGetter(window, "gChromeWin", function ()
+ window.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIWebNavigation)
+ .QueryInterface(Ci.nsIDocShellTreeItem)
+ .rootTreeItem
+ .QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIDOMWindow)
+ .QueryInterface(Ci.nsIDOMChromeWindow));
+
+
+var ContextMenus = {
+ target: null,
- this._items = [
- new ContextMenuItem("open",
- download => download.succeeded,
- download => download.launch().then(null, Cu.reportError)),
- new ContextMenuItem("retry",
- download => download.error ||
- (download.canceled && !download.hasPartialData),
- download => download.start().then(null, Cu.reportError)),
- new ContextMenuItem("remove",
- download => download.stopped,
- download => {
- Downloads.getList(Downloads.ALL)
- .then(list => list.remove(download))
- .then(null, Cu.reportError);
- deleteDownload(download);
- }),
- new ContextMenuItem("pause",
- download => !download.stopped,
- download => download.cancel().then(null, Cu.reportError)),
- new ContextMenuItem("resume",
- download => download.canceled && download.hasPartialData,
- download => download.start().then(null, Cu.reportError)),
- new ContextMenuItem("cancel",
- download => !download.stopped ||
- (download.canceled && download.hasPartialData),
- download => {
- download.cancel().then(null, Cu.reportError);
- download.removePartialData().then(null, Cu.reportError);
- }),
- // following menu item is a global action
- new ContextMenuItem("removeall",
- () => downloadLists.finished.length > 0,
- () => downloadLists.removeFinished())
+ init: function() {
+ document.addEventListener("contextmenu", this, false);
+ document.getElementById("contextmenu-open").addEventListener("click", this.open.bind(this), false);
+ document.getElementById("contextmenu-retry").addEventListener("click", this.retry.bind(this), false);
+ document.getElementById("contextmenu-remove").addEventListener("click", this.remove.bind(this), false);
+ document.getElementById("contextmenu-pause").addEventListener("click", this.pause.bind(this), false);
+ document.getElementById("contextmenu-resume").addEventListener("click", this.resume.bind(this), false);
+ document.getElementById("contextmenu-cancel").addEventListener("click", this.cancel.bind(this), false);
+ document.getElementById("contextmenu-removeall").addEventListener("click", this.removeAll.bind(this), false);
+ this.items = [
+ { name: "open", states: [Downloads._dlmgr.DOWNLOAD_FINISHED] },
+ { name: "retry", states: [Downloads._dlmgr.DOWNLOAD_FAILED, Downloads._dlmgr.DOWNLOAD_CANCELED] },
+ { name: "remove", states: [Downloads._dlmgr.DOWNLOAD_FINISHED,Downloads._dlmgr.DOWNLOAD_FAILED, Downloads._dlmgr.DOWNLOAD_CANCELED] },
+ { name: "removeall", states: [Downloads._dlmgr.DOWNLOAD_FINISHED,Downloads._dlmgr.DOWNLOAD_FAILED, Downloads._dlmgr.DOWNLOAD_CANCELED] },
+ { name: "pause", states: [Downloads._dlmgr.DOWNLOAD_DOWNLOADING] },
+ { name: "resume", states: [Downloads._dlmgr.DOWNLOAD_PAUSED] },
+ { name: "cancel", states: [Downloads._dlmgr.DOWNLOAD_DOWNLOADING, Downloads._dlmgr.DOWNLOAD_NOTSTARTED, Downloads._dlmgr.DOWNLOAD_QUEUED, Downloads._dlmgr.DOWNLOAD_PAUSED] },
];
},
- addContextMenuEventListener: function (element) {
- element.addEventListener("contextmenu", this.onContextMenu.bind(this));
+ handleEvent: function(event) {
+ // store the target of context menu events so that we know which app to act on
+ this.target = event.target;
+ while (!this.target.hasAttribute("contextmenu")) {
+ this.target = this.target.parentNode;
+ }
+ if (!this.target)
+ return;
+
+ let state = parseInt(this.target.getAttribute("state"));
+ for (let i = 0; i < this.items.length; i++) {
+ var item = this.items[i];
+ let enabled = (item.states.indexOf(state) > -1);
+ if (enabled)
+ document.getElementById("contextmenu-" + item.name).removeAttribute("hidden");
+ else
+ document.getElementById("contextmenu-" + item.name).setAttribute("hidden", "true");
+ }
+ },
+
+ // Open shown only for downloads that completed successfully
+ open: function(event) {
+ Downloads.openDownload(this.target);
+ this.target = null;
},
- onContextMenu: function (event) {
- let target = event.target;
- while (target && !target.download) {
- target = target.parentNode;
- }
- if (!target) {
- Cu.reportError("No download found for context menu target");
- event.preventDefault();
- return;
- }
+ // Retry shown when its failed, canceled, blocked(covered in failed, see _getState())
+ retry: function (event) {
+ Downloads.retryDownload(this.target);
+ this.target = null;
+ },
+
+ // Remove shown when its canceled, finished, failed(failed includes blocked and dirty, see _getState())
+ remove: function (event) {
+ Downloads.removeDownload(this.target);
+ this.target = null;
+ },
- // capture the target download for menu items to use in a click event
- this._targetDownload = target.download;
- for (let item of this._items) {
- item.updateVisibility(target.download);
- }
+ // Pause shown when item is currently downloading
+ pause: function (event) {
+ Downloads.pauseDownload(this.target);
+ this.target = null;
+ },
+
+ // Resume shown for paused items only
+ resume: function (event) {
+ Downloads.resumeDownload(this.target);
+ this.target = null;
+ },
+
+ // Cancel shown when its downloading, notstarted, queued or paused
+ cancel: function (event) {
+ Downloads.cancelDownload(this.target);
+ this.target = null;
+ },
+
+ removeAll: function(event) {
+ Downloads.removeAll();
+ this.target = null;
}
-};
-
-function ContextMenuItem(name, isVisible, action) {
- this.element = document.getElementById("contextmenu-" + name);
- this.isVisible = isVisible;
-
- this.element.addEventListener("click", event => action(event.download));
}
-ContextMenuItem.prototype = {
- updateVisibility: function (download) {
- this.element.hidden = !this.isVisible(download);
- }
-};
+
+let Downloads = {
+ init: function dl_init() {
+ function onClick(evt) {
+ let target = evt.target;
+ while (target.nodeName != "li") {
+ target = target.parentNode;
+ if (!target)
+ return;
+ }
+
+ Downloads.openDownload(target);
+ }
+
+ this._normalList = document.getElementById("normal-downloads-list");
+ this._privateList = document.getElementById("private-downloads-list");
-function DownloadListView(type, listElementId) {
- this.listElement = document.getElementById(listElementId);
- contextMenu.addContextMenuEventListener(this.listElement);
+ this._normalList.addEventListener("click", onClick, false);
+ this._privateList.addEventListener("click", onClick, false);
+
+ this._dlmgr = Cc["@mozilla.org/download-manager;1"].getService(Ci.nsIDownloadManager);
+ this._dlmgr.addPrivacyAwareListener(this);
+
+ Services.obs.addObserver(this, "last-pb-context-exited", false);
+ Services.obs.addObserver(this, "download-manager-remove-download-guid", false);
- this.items = new Map();
+ // If we have private downloads, show them all immediately. If we were to
+ // add them asynchronously, there's a small chance we could get a
+ // "last-pb-context-exited" notification before downloads are added to the
+ // list, meaning we'd show private downloads without any private tabs open.
+ let privateEntries = this.getDownloads({ isPrivate: true });
+ this._stepAddEntries(privateEntries, this._privateList, privateEntries.length);
+
+ // Add non-private downloads
+ let normalEntries = this.getDownloads({ isPrivate: false });
+ this._stepAddEntries(normalEntries, this._normalList, 1, this._scrollToSelectedDownload.bind(this));
+ ContextMenus.init();
+ },
- Downloads.getList(type)
- .then(list => list.addView(this))
- .then(null, Cu.reportError);
+ uninit: function dl_uninit() {
+ let contextmenus = gChromeWin.NativeWindow.contextmenus;
+ contextmenus.remove(this.openMenuItem);
+ contextmenus.remove(this.removeMenuItem);
+ contextmenus.remove(this.pauseMenuItem);
+ contextmenus.remove(this.resumeMenuItem);
+ contextmenus.remove(this.retryMenuItem);
+ contextmenus.remove(this.cancelMenuItem);
+ contextmenus.remove(this.deleteAllMenuItem);
+
+ this._dlmgr.removeListener(this);
+ Services.obs.removeObserver(this, "last-pb-context-exited");
+ Services.obs.removeObserver(this, "download-manager-remove-download-guid");
+ },
- window.addEventListener("unload", event => {
- Downloads.getList(type)
- .then(list => list.removeView(this))
- .then(null, Cu.reportError);
- });
-}
+ onProgressChange: function(aWebProgress, aRequest, aCurSelfProgress, aMaxSelfProgress,
+ aCurTotalProgress, aMaxTotalProgress, aDownload) { },
+ onDownloadStateChange: function(aState, aDownload) {
+ switch (aDownload.state) {
+ case Ci.nsIDownloadManager.DOWNLOAD_FAILED:
+ case Ci.nsIDownloadManager.DOWNLOAD_CANCELED:
+ case Ci.nsIDownloadManager.DOWNLOAD_BLOCKED_PARENTAL:
+ case Ci.nsIDownloadManager.DOWNLOAD_DIRTY:
+ case Ci.nsIDownloadManager.DOWNLOAD_FINISHED:
+ // For all "completed" states, move them after active downloads
+ this._moveDownloadAfterActive(this._getElementForDownload(aDownload.guid));
-DownloadListView.prototype = {
- get finished() {
- let finished = [];
- for (let download of this.items.keys()) {
- if (download.stopped && (!download.hasPartialData || download.error)) {
- finished.push(download);
+ // Fall-through the rest
+ case Ci.nsIDownloadManager.DOWNLOAD_SCANNING:
+ case Ci.nsIDownloadManager.DOWNLOAD_QUEUED:
+ case Ci.nsIDownloadManager.DOWNLOAD_DOWNLOADING:
+ let item = this._getElementForDownload(aDownload.guid);
+ if (item)
+ this._updateDownloadRow(item, aDownload);
+ else
+ this._insertDownloadRow(aDownload);
+ break;
+ }
+ },
+ onStateChange: function(aWebProgress, aRequest, aState, aStatus, aDownload) { },
+ onSecurityChange: function(aWebProgress, aRequest, aState, aDownload) { },
+
+ observe: function (aSubject, aTopic, aData) {
+ switch (aTopic) {
+ case "last-pb-context-exited":
+ this._privateList.innerHTML = "";
+ break;
+ case "download-manager-remove-download-guid": {
+ let guid = aSubject.QueryInterface(Ci.nsISupportsCString).data;
+ this._removeItem(this._getElementForDownload(guid));
+ break;
}
}
+ },
- return finished;
+ _moveDownloadAfterActive: function dl_moveDownloadAfterActive(aItem) {
+ // Move downloads that just reached a "completed" state below any active
+ try {
+ // Iterate down until we find a non-active download
+ let next = aItem.nextElementSibling;
+ while (next && this._inProgress(next.getAttribute("state")))
+ next = next.nextElementSibling;
+ // Move the item
+ aItem.parentNode.insertBefore(aItem, next);
+ } catch (ex) {
+ this.logError("_moveDownloadAfterActive() " + ex);
+ }
+ },
+
+ _inProgress: function dl_inProgress(aState) {
+ return [
+ this._dlmgr.DOWNLOAD_NOTSTARTED,
+ this._dlmgr.DOWNLOAD_QUEUED,
+ this._dlmgr.DOWNLOAD_DOWNLOADING,
+ this._dlmgr.DOWNLOAD_PAUSED,
+ this._dlmgr.DOWNLOAD_SCANNING,
+ ].indexOf(parseInt(aState)) != -1;
+ },
+
+ _insertDownloadRow: function dl_insertDownloadRow(aDownload) {
+ let updatedState = this._getState(aDownload.state);
+ let item = this._createItem(downloadTemplate, {
+ guid: aDownload.guid,
+ target: aDownload.displayName,
+ icon: "moz-icon://" + aDownload.displayName + "?size=64",
+ date: DownloadUtils.getReadableDates(new Date())[0],
+ domain: DownloadUtils.getURIHost(aDownload.source.spec)[0],
+ size: this._getDownloadSize(aDownload.size),
+ displayState: this._getStateString(updatedState),
+ state: updatedState
+ });
+ list = aDownload.isPrivate ? this._privateList : this._normalList;
+ list.insertAdjacentHTML("afterbegin", item);
+ },
+
+ _getDownloadSize: function dl_getDownloadSize(aSize) {
+ if (aSize > 0) {
+ let displaySize = DownloadUtils.convertByteUnits(aSize);
+ return displaySize.join(""); // [0] is size, [1] is units
+ }
+ return gStrings.GetStringFromName("downloadState.unknownSize");
+ },
+
+ // Not all states are displayed as-is on mobile, some are translated to a generic state
+ _getState: function dl_getState(aState) {
+ let str;
+ switch (aState) {
+ // Downloading and Scanning states show up as "Downloading"
+ case this._dlmgr.DOWNLOAD_DOWNLOADING:
+ case this._dlmgr.DOWNLOAD_SCANNING:
+ str = this._dlmgr.DOWNLOAD_DOWNLOADING;
+ break;
+
+ // Failed, Dirty and Blocked states show up as "Failed"
+ case this._dlmgr.DOWNLOAD_FAILED:
+ case this._dlmgr.DOWNLOAD_DIRTY:
+ case this._dlmgr.DOWNLOAD_BLOCKED_POLICY:
+ case this._dlmgr.DOWNLOAD_BLOCKED_PARENTAL:
+ str = this._dlmgr.DOWNLOAD_FAILED;
+ break;
+
+ /* QUEUED and NOTSTARTED are not translated as they
+ dont fall under a common state but we still need
+ to display a common "status" on the UI */
+
+ default:
+ str = aState;
+ }
+ return str;
},
- insertOrMoveItem: function (item) {
- var compare = (a, b) => {
- // active downloads always before stopped downloads
- if (a.stopped != b.stopped) {
- return b.stopped ? -1 : 1
- }
- // most recent downloads first
- return b.startTime - a.startTime;
- };
+ // Note: This doesn't cover all states as some of the states are translated in _getState()
+ _getStateString: function dl_getStateString(aState) {
+ let str;
+ switch (aState) {
+ case this._dlmgr.DOWNLOAD_DOWNLOADING:
+ str = "downloadState.downloading";
+ break;
+ case this._dlmgr.DOWNLOAD_CANCELED:
+ str = "downloadState.canceled";
+ break;
+ case this._dlmgr.DOWNLOAD_FAILED:
+ str = "downloadState.failed";
+ break;
+ case this._dlmgr.DOWNLOAD_PAUSED:
+ str = "downloadState.paused";
+ break;
- let insertLocation = this.listElement.firstChild;
- while (insertLocation && compare(item.download, insertLocation.download) > 0) {
- insertLocation = insertLocation.nextElementSibling;
+ // Queued and Notstarted show up as "Starting..."
+ case this._dlmgr.DOWNLOAD_QUEUED:
+ case this._dlmgr.DOWNLOAD_NOTSTARTED:
+ str = "downloadState.starting";
+ break;
+
+ default:
+ return "";
}
- this.listElement.insertBefore(item.element, insertLocation);
+ return gStrings.GetStringFromName(str);
+ },
+
+ _updateItem: function dl_updateItem(aItem, aValues) {
+ for (let i in aValues) {
+ aItem.querySelector("." + i).textContent = aValues[i];
+ }
+ },
+
+ _initStatement: function dv__initStatement(aIsPrivate) {
+ let dbConn = aIsPrivate ? this._dlmgr.privateDBConnection : this._dlmgr.DBConnection;
+ return dbConn.createStatement(
+ "SELECT guid, name, source, state, startTime, endTime, referrer, " +
+ "currBytes, maxBytes, state IN (?1, ?2, ?3, ?4, ?5) isActive " +
+ "FROM moz_downloads " +
+ "ORDER BY isActive DESC, endTime DESC, startTime DESC");
},
- onDownloadAdded: function (download) {
- let item = new DownloadItem(download);
- this.items.set(download, item);
- this.insertOrMoveItem(item);
+ _createItem: function _createItem(aTemplate, aValues) {
+ function htmlEscape(s) {
+ s = s.replace(/&/g, "&");
+ s = s.replace(/>/g, ">");
+ s = s.replace(/</g, "<");
+ s = s.replace(/"/g, """);
+ s = s.replace(/'/g, "'");
+ return s;
+ }
+
+ let t = aTemplate;
+ for (let key in aValues) {
+ if (aValues.hasOwnProperty(key)) {
+ let regEx = new RegExp("{" + key + "}", "g");
+ let value = htmlEscape(aValues[key].toString());
+ t = t.replace(regEx, value);
+ }
+ }
+ return t;
},
- onDownloadChanged: function (download) {
- let item = this.items.get(download);
- if (!item) {
- Cu.reportError("No DownloadItem found for download");
+ _getEntry: function dv__getEntry(aStmt) {
+ try {
+ if (!aStmt.executeStep()) {
+ return null;
+ }
+
+ let updatedState = this._getState(aStmt.row.state);
+ // Try to get the attribute values from the statement
+
+ return {
+ guid: aStmt.row.guid,
+ target: aStmt.row.name,
+ icon: "moz-icon://" + aStmt.row.name + "?size=64",
+ date: DownloadUtils.getReadableDates(new Date(aStmt.row.endTime / 1000))[0],
+ domain: DownloadUtils.getURIHost(aStmt.row.source)[0],
+ size: this._getDownloadSize(aStmt.row.maxBytes),
+ displayState: this._getStateString(updatedState),
+ state: updatedState
+ };
+
+ } catch (e) {
+ // Something went wrong when stepping or getting values, so clear and quit
+ this.logError("_getEntry() " + e);
+ aStmt.reset();
+ return null;
+ }
+ },
+
+ _stepAddEntries: function dv__stepAddEntries(aEntries, aList, aNumItems, aCallback) {
+
+ if (aEntries.length == 0){
+ if (aCallback)
+ aCallback();
+
return;
}
- if (item.stateChanged) {
- this.insertOrMoveItem(item);
+ let attrs = aEntries.shift();
+ let item = this._createItem(downloadTemplate, attrs);
+ aList.insertAdjacentHTML("beforeend", item);
+
+ // Add another item to the list if we should; otherwise, let the UI update
+ // and continue later
+ if (aNumItems > 1) {
+ this._stepAddEntries(aEntries, aList, aNumItems - 1, aCallback);
+ } else {
+ // Use a shorter delay for earlier downloads to display them faster
+ let delay = Math.min(aList.itemCount * 10, 300);
+ setTimeout(function () {
+ this._stepAddEntries(aEntries, aList, 5, aCallback);
+ }.bind(this), delay);
}
-
- item.onDownloadChanged();
},
- onDownloadRemoved: function (download) {
- let item = this.items.get(download);
- if (!item) {
- Cu.reportError("No DownloadItem found for download");
- return;
+ getDownloads: function dl_getDownloads(aParams) {
+ aParams = aParams || {};
+ let stmt = this._initStatement(aParams.isPrivate);
+
+ stmt.reset();
+ stmt.bindInt32Parameter(0, Ci.nsIDownloadManager.DOWNLOAD_NOTSTARTED);
+ stmt.bindInt32Parameter(1, Ci.nsIDownloadManager.DOWNLOAD_DOWNLOADING);
+ stmt.bindInt32Parameter(2, Ci.nsIDownloadManager.DOWNLOAD_PAUSED);
+ stmt.bindInt32Parameter(3, Ci.nsIDownloadManager.DOWNLOAD_QUEUED);
+ stmt.bindInt32Parameter(4, Ci.nsIDownloadManager.DOWNLOAD_SCANNING);
+
+ let entries = [];
+ while (entry = this._getEntry(stmt)) {
+ entries.push(entry);
}
- this.items.delete(download);
- this.listElement.removeChild(item.element);
- }
-};
+ stmt.finalize();
+
+ return entries;
+ },
+
+ _getElementForDownload: function dl_getElementForDownload(aKey) {
+ return document.body.querySelector("li[downloadGUID='" + aKey + "']");
+ },
+
+ _getDownloadForElement: function dl_getDownloadForElement(aElement, aCallback) {
+ let guid = aElement.getAttribute("downloadGUID");
+ this._dlmgr.getDownloadByGUID(guid, function(status, download) {
+ if (!Components.isSuccessCode(status)) {
+ return;
+ }
+ aCallback(download);
+ });
+ },
+
+ _removeItem: function dl_removeItem(aItem) {
+ // Make sure we have an item to remove
+ if (!aItem)
+ return;
+
+ aItem.parentNode.removeChild(aItem);
+ },
-let downloadLists = {
- init: function () {
- this.publicDownloads = new DownloadListView(Downloads.PUBLIC, "public-downloads-list");
- this.privateDownloads = new DownloadListView(Downloads.PRIVATE, "private-downloads-list");
+ openDownload: function dl_openDownload(aItem) {
+ this._getDownloadForElement(aItem, function(aDownload) {
+ if (aDownload.state !== Ci.nsIDownloadManager.DOWNLOAD_FINISHED) {
+ // Do not open unfinished downloads.
+ return;
+ }
+ try {
+ let f = aDownload.targetFile;
+ if (f) f.launch();
+ } catch (ex) {
+ this.logError("openDownload() " + ex, aDownload);
+ }
+ }.bind(this));
+ },
+
+ removeDownload: function dl_removeDownload(aItem) {
+ this._getDownloadForElement(aItem, function(aDownload) {
+ if (aDownload.targetFile) {
+ OS.File.remove(aDownload.targetFile.path).then(null, function onError(reason) {
+ if (!(reason instanceof OS.File.Error && reason.becauseNoSuchFile)) {
+ this.logError("removeDownload() " + reason, aDownload);
+ }
+ }.bind(this));
+ }
+
+ aDownload.remove();
+ }.bind(this));
+ },
+
+ removeAll: function dl_removeAll() {
+ let title = gStrings.GetStringFromName("downloadAction.deleteAll");
+ let messageForm = gStrings.GetStringFromName("downloadMessage.deleteAll");
+ let elements = document.body.querySelectorAll("li[state='" + this._dlmgr.DOWNLOAD_FINISHED + "']," +
+ "li[state='" + this._dlmgr.DOWNLOAD_CANCELED + "']," +
+ "li[state='" + this._dlmgr.DOWNLOAD_FAILED + "']");
+ let message = PluralForm.get(elements.length, messageForm)
+ .replace("#1", elements.length);
+ let flags = Services.prompt.BUTTON_POS_0 * Services.prompt.BUTTON_TITLE_OK +
+ Services.prompt.BUTTON_POS_1 * Services.prompt.BUTTON_TITLE_CANCEL;
+ let choice = Services.prompt.confirmEx(null, title, message, flags,
+ null, null, null, null, {});
+ if (choice == 0) {
+ for (let i = 0; i < elements.length; i++) {
+ this.removeDownload(elements[i]);
+ }
+ }
},
- get finished() {
- return this.publicDownloads.finished.concat(this.privateDownloads.finished);
+ pauseDownload: function dl_pauseDownload(aItem) {
+ this._getDownloadForElement(aItem, function(aDownload) {
+ try {
+ aDownload.pause();
+ this._updateDownloadRow(aItem, aDownload);
+ } catch (ex) {
+ this.logError("Error: pauseDownload() " + ex, aDownload);
+ }
+ }.bind(this));
+ },
+
+ resumeDownload: function dl_resumeDownload(aItem) {
+ this._getDownloadForElement(aItem, function(aDownload) {
+ try {
+ aDownload.resume();
+ this._updateDownloadRow(aItem, aDownload);
+ } catch (ex) {
+ this.logError("resumeDownload() " + ex, aDownload);
+ }
+ }.bind(this));
+ },
+
+ retryDownload: function dl_retryDownload(aItem) {
+ this._getDownloadForElement(aItem, function(aDownload) {
+ try {
+ this._removeItem(aItem);
+ aDownload.retry();
+ } catch (ex) {
+ this.logError("retryDownload() " + ex, aDownload);
+ }
+ }.bind(this));
},
- removeFinished: function () {
- let finished = this.finished;
- if (finished.length == 0) {
+ cancelDownload: function dl_cancelDownload(aItem) {
+ this._getDownloadForElement(aItem, function(aDownload) {
+ OS.File.remove(aDownload.targetFile.path).then(null, function onError(reason) {
+ if (!(reason instanceof OS.File.Error && reason.becauseNoSuchFile)) {
+ this.logError("cancelDownload() " + reason, aDownload);
+ }
+ }.bind(this));
+
+ aDownload.cancel();
+
+ this._updateDownloadRow(aItem, aDownload);
+ }.bind(this));
+ },
+
+ _updateDownloadRow: function dl_updateDownloadRow(aItem, aDownload) {
+ try {
+ let updatedState = this._getState(aDownload.state);
+ aItem.setAttribute("state", updatedState);
+ this._updateItem(aItem, {
+ size: this._getDownloadSize(aDownload.size),
+ displayState: this._getStateString(updatedState),
+ date: DownloadUtils.getReadableDates(new Date())[0]
+ });
+ } catch (ex) {
+ this.logError("_updateDownloadRow() " + ex, aDownload);
+ }
+ },
+
+ /**
+ * In case a specific downloadId was passed while opening, scrolls the list to
+ * the given elemenet
+ */
+
+ _scrollToSelectedDownload : function dl_scrollToSelected() {
+ let spec = document.location.href;
+ let pos = spec.indexOf("?");
+ let query = "";
+ if (pos >= 0)
+ query = spec.substring(pos + 1);
+
+ // Just assume the query is "id=<id>"
+ let id = query.substring(3);
+ if (!id) {
+ return;
+ }
+ downloadElement = this._getElementForDownload(id);
+ if (!downloadElement) {
return;
}
- let title = strings.GetStringFromName("downloadAction.deleteAll");
- let messageForm = strings.GetStringFromName("downloadMessage.deleteAll");
- let message = PluralForm.get(finished.length, messageForm).replace("#1", finished.length);
-
- if (Services.prompt.confirm(null, title, message)) {
- Downloads.getList(Downloads.ALL)
- .then(list => {
- for (let download of finished) {
- list.remove(download).then(null, Cu.reportError);
- deleteDownload(download);
- }
- }, Cu.reportError);
- }
- }
-};
-
-function DownloadItem(download) {
- this._download = download;
- this._updateFromDownload();
-
- this._domain = DownloadUtils.getURIHost(download.source.url)[0];
- this._fileName = this._htmlEscape(OS.Path.basename(download.target.path));
- this._iconUrl = "moz-icon://" + this._fileName + "?size=64";
- this._startDate = this._htmlEscape(DownloadUtils.getReadableDates(download.startTime)[0]);
-
- this._element = this.createElement();
-}
-
-const kDownloadStatePropertyNames = [
- "stopped",
- "succeeded",
- "canceled",
- "error",
- "startTime"
-];
-
-DownloadItem.prototype = {
- _htmlEscape : function (s) {
- s = s.replace(/&/g, "&");
- s = s.replace(/>/g, ">");
- s = s.replace(/</g, "<");
- s = s.replace(/"/g, """);
- s = s.replace(/'/g, "'");
- return s;
+ downloadElement.scrollIntoView();
},
- _updateFromDownload: function () {
- this._state = {};
- kDownloadStatePropertyNames.forEach(
- name => this._state[name] = this._download[name],
- this);
- },
-
- get stateChanged() {
- return kDownloadStatePropertyNames.some(
- name => this._state[name] != this._download[name],
- this);
- },
-
- get download() this._download,
- get element() this._element,
-
- createElement: function() {
- let template = document.getElementById("download-item");
- // TODO: use this once <template> is working
- // let element = document.importNode(template.content, true);
-
- // simulate a <template> node...
- let element = template.cloneNode(true);
- element.removeAttribute("id");
- element.removeAttribute("style");
-
- // launch the download if clicked
- element.addEventListener("click", this.onClick.bind(this));
-
- // set download as an expando property for the context menu
- element.download = this.download;
-
- // fill in template placeholders
- this.updateElement(element);
-
- return element;
- },
-
- updateElement: function (element) {
- element.querySelector(".date").textContent = this.startDate;
- element.querySelector(".domain").textContent = this.domain;
- element.querySelector(".icon").src = this.iconUrl;
- element.querySelector(".size").textContent = this.size;
- element.querySelector(".state").textContent = this.stateDescription;
- element.querySelector(".title").setAttribute("value", this.fileName);
- },
-
- onClick: function (event) {
- if (this.download.succeeded) {
- this.download.launch().then(null, Cu.reportError);
+ /**
+ * Logs the error to the console.
+ *
+ * @param aMessage error message to log
+ * @param aDownload (optional) if given, and if the download is private, the
+ * log message is suppressed
+ */
+ logError: function dl_logError(aMessage, aDownload) {
+ if (!aDownload || !aDownload.isPrivate) {
+ console.log("Error: " + aMessage);
}
},
- onDownloadChanged: function () {
- this._updateFromDownload();
- this.updateElement(this.element);
- },
-
- // template properties below
- get domain() this._domain,
- get fileName() this._fileName,
- get id() this._id,
- get iconUrl() this._iconUrl,
-
- get size() {
- if (this.download.hasProgress) {
- return DownloadUtils.convertByteUnits(this.download.totalBytes).join("");
- }
- return strings.GetStringFromName("downloadState.unknownSize");
- },
-
- get startDate() {
- return this._startDate;
- },
+ QueryInterface: function (aIID) {
+ if (!aIID.equals(Ci.nsIDownloadProgressListener) &&
+ !aIID.equals(Ci.nsISupports))
+ throw Components.results.NS_ERROR_NO_INTERFACE;
+ return this;
+ }
+}
- get stateDescription() {
- let name;
- if (this.download.error) {
- name = "downloadState.failed";
- } else if (this.download.canceled) {
- if (this.download.hasPartialData) {
- name = "downloadState.paused";
- } else {
- name = "downloadState.canceled";
- }
- } else if (!this.download.stopped) {
- if (this.download.currentBytes > 0) {
- name = "downloadState.downloading";
- } else {
- name = "downloadState.starting";
- }
- }
+document.addEventListener("DOMContentLoaded", Downloads.init.bind(Downloads), true);
+window.addEventListener("unload", Downloads.uninit.bind(Downloads), false);
- if (name) {
- return strings.GetStringFromName(name);
- }
- return "";
- }
-};
-window.addEventListener("DOMContentLoaded", event => {
- contextMenu.init();
- downloadLists.init()
-});
--- a/mobile/android/chrome/content/aboutDownloads.xhtml
+++ b/mobile/android/chrome/content/aboutDownloads.xhtml
@@ -30,33 +30,17 @@
<menuitem id="contextmenu-retry" label="&aboutDownloads.retry;"></menuitem>
<menuitem id="contextmenu-remove" label="&aboutDownloads.remove;"></menuitem>
<menuitem id="contextmenu-pause" label="&aboutDownloads.pause;"></menuitem>
<menuitem id="contextmenu-resume" label="&aboutDownloads.resume;"></menuitem>
<menuitem id="contextmenu-cancel" label="&aboutDownloads.cancel;"></menuitem>
<menuitem id="contextmenu-removeall" label="&aboutDownloads.removeAll;"></menuitem>
</menu>
- <!--template id="download-item"-->
- <li id="download-item" class="list-item" role="button" contextmenu="downloadmenu" style="display: none">
- <img class="icon" src=""/>
- <div class="details">
- <div class="row">
- <!-- This is a hack so that we can crop this label in its center -->
- <xul:label class="title" crop="center" value=""/>
- <div class="date"></div>
- </div>
- <div class="size"></div>
- <div class="domain"></div>
- <div class="state"></div>
- </div>
- </li>
- <!--/template-->
-
<div class="header">
<div>&aboutDownloads.header;</div>
</div>
<ul id="private-downloads-list" class="list"></ul>
- <ul id="public-downloads-list" class="list"></ul>
+ <ul id="normal-downloads-list" class="list"></ul>
<span id="no-downloads-indicator">&aboutDownloads.empty;</span>
<script type="application/javascript;version=1.8" src="chrome://browser/content/aboutDownloads.js"/>
</body>
</html>
--- a/mobile/android/chrome/content/browser.js
+++ b/mobile/android/chrome/content/browser.js
@@ -8,17 +8,16 @@
let Cc = Components.classes;
let Ci = Components.interfaces;
let Cu = Components.utils;
let Cr = Components.results;
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/AddonManager.jsm");
-Cu.import("resource://gre/modules/DownloadNotifications.jsm");
Cu.import("resource://gre/modules/FileUtils.jsm");
Cu.import("resource://gre/modules/JNI.jsm");
Cu.import('resource://gre/modules/Payment.jsm');
Cu.import("resource://gre/modules/NotificationDB.jsm");
Cu.import("resource://gre/modules/SpatialNavigation.jsm");
Cu.import("resource://gre/modules/UITelemetry.jsm");
#ifdef ACCESSIBILITY
@@ -365,17 +364,17 @@ var BrowserApp = {
}, false);
// When a restricted key is pressed in DOM full-screen mode, we should display
// the "Press ESC to exit" warning message.
window.addEventListener("MozShowFullScreenWarning", showFullScreenWarning, true);
NativeWindow.init();
LightWeightThemeWebInstaller.init();
- DownloadNotifications.init();
+ Downloads.init();
FormAssistant.init();
IndexedDB.init();
HealthReportStatusListener.init();
XPInstallObserver.init();
CharacterEncoding.init();
ActivityObserver.init();
#ifdef MOZ_ANDROID_SYNTHAPKS
// TODO: replace with Android implementation of WebappOSUtils.isLaunchable.
@@ -653,17 +652,17 @@ var BrowserApp = {
aTarget.mozRequestFullScreen();
});
NativeWindow.contextmenus.add(Strings.browser.GetStringFromName("contextmenu.mute"),
NativeWindow.contextmenus.mediaContext("media-unmuted"),
function(aTarget) {
aTarget.muted = true;
});
-
+
NativeWindow.contextmenus.add(Strings.browser.GetStringFromName("contextmenu.unmute"),
NativeWindow.contextmenus.mediaContext("media-muted"),
function(aTarget) {
aTarget.muted = false;
});
NativeWindow.contextmenus.add(Strings.browser.GetStringFromName("contextmenu.copyImageLocation"),
NativeWindow.contextmenus.imageLocationCopyableContext,
@@ -746,17 +745,16 @@ var BrowserApp = {
if (Services.prefs.prefHasUserValue("plugins.click_to_play")) {
Services.prefs.setIntPref("plugin.default.state", Ci.nsIPluginTag.STATE_ENABLED);
Services.prefs.clearUserPref("plugins.click_to_play");
}
},
shutdown: function shutdown() {
NativeWindow.uninit();
- DownloadNotifications.uninit();
LightWeightThemeWebInstaller.uninit();
FormAssistant.uninit();
IndexedDB.uninit();
ViewportHandler.uninit();
XPInstallObserver.uninit();
HealthReportStatusListener.uninit();
CharacterEncoding.uninit();
SearchEngines.uninit();
@@ -1820,17 +1818,17 @@ var NativeWindow = {
sendMessageToJava({ type: "Menu:Remove", id: aId });
},
update: function(aId, aOptions) {
if (!aOptions)
return;
sendMessageToJava({
- type: "Menu:Update",
+ type: "Menu:Update",
id: aId,
options: aOptions
});
}
},
doorhanger: {
_callbacks: {},
@@ -1846,17 +1844,17 @@ var NativeWindow = {
* to -1, the doorhanger will never automatically dismiss.
* persistWhileVisible:
* A boolean. If true, a visible notification will always
* persist across location changes.
* timeout: A time in milliseconds. The notification will not
* automatically dismiss before this time.
* checkbox: A string to appear next to a checkbox under the notification
* message. The button callback functions will be called with
- * the checked state as an argument.
+ * the checked state as an argument.
*/
show: function(aMessage, aValue, aButtons, aTabID, aOptions) {
if (aButtons == null) {
aButtons = [];
}
aButtons.forEach((function(aButton) {
this._callbacks[this._callbacksId] = { cb: aButton.callback, prompt: this._promptId };
@@ -2248,17 +2246,17 @@ var NativeWindow = {
this.menus = null;
Services.obs.notifyObservers({target: target, x: x, y: y}, "context-menu-not-shown", "");
if (SelectionHandler.canSelect(target)) {
if (!SelectionHandler.startSelection(target, {
mode: SelectionHandler.SELECT_AT_POINT,
x: x,
y: y
- })) {
+ })) {
SelectionHandler.attachCaret(target);
}
}
}
},
// Returns a title for a context menu. If no title attribute exists, will fall back to looking for a url
_getTitle: function(node) {
@@ -3142,17 +3140,17 @@ Tab.prototype = {
// We add in a bit of fudge just so that the end characters
// don't accidentally get clipped. 15px is an arbitrary choice.
gReflowPending = setTimeout(doChangeMaxLineBoxWidth,
reflozTimeout,
viewportWidth - 15);
},
- /**
+ /**
* Reloads the tab with the desktop mode setting.
*/
reloadWithMode: function (aDesktopMode) {
// Set desktop mode for tab and send change to Java
if (this.desktopMode != aDesktopMode) {
this.desktopMode = aDesktopMode;
sendMessageToJava({
type: "DesktopMode:Changed",
@@ -3777,34 +3775,31 @@ Tab.prototype = {
// We use the sizes attribute if available
// see http://www.whatwg.org/specs/web-apps/current-work/multipage/links.html#rel-icon
if (target.hasAttribute("sizes")) {
let sizes = target.getAttribute("sizes").toLowerCase();
if (sizes == "any") {
// Since Java expects an integer, use -1 to represent icons with sizes="any"
- maxSize = -1;
+ maxSize = -1;
} else {
let tokens = sizes.split(" ");
tokens.forEach(function(token) {
// TODO: check for invalid tokens
let [w, h] = token.split("x");
maxSize = Math.max(maxSize, Math.max(w, h));
});
}
}
let json = {
type: "Link:Favicon",
tabID: this.id,
href: resolveGeckoURI(target.href),
- charset: target.ownerDocument.characterSet,
- title: target.title,
- rel: list.join(" "),
size: maxSize
};
sendMessageToJava(json);
} else if (list.indexOf("[alternate]") != -1) {
let type = target.type.toLowerCase().replace(/^\s+|\s*(?:;.*)?$/g, "");
let isFeed = (type == "application/rss+xml" || type == "application/atom+xml");
if (!isFeed)
@@ -6661,17 +6656,17 @@ var IdentityHandler = {
* Determine the identity of the page being displayed by examining its SSL cert
* (if available). Return the data needed to update the UI.
*/
checkIdentity: function checkIdentity(aState, aBrowser) {
this._lastStatus = aBrowser.securityUI
.QueryInterface(Components.interfaces.nsISSLStatusProvider)
.SSLStatus;
- // Don't pass in the actual location object, since it can cause us to
+ // Don't pass in the actual location object, since it can cause us to
// hold on to the window object too long. Just pass in the fields we
// care about. (bug 424829)
let locationObj = {};
try {
let location = aBrowser.contentWindow.location;
locationObj.host = location.host;
locationObj.hostname = location.hostname;
locationObj.port = location.port;
@@ -6711,17 +6706,17 @@ var IdentityHandler = {
else if (iData.state) // State only
supplemental += iData.state;
else if (iData.country) // Country only
supplemental += iData.country;
result.supplemental = supplemental;
return result;
}
-
+
// Otherwise, we don't know the cert owner
result.owner = Strings.browser.GetStringFromName("identity.ownerUnknown3");
// Cache the override service the first time we need to check it
if (!this._overrideService)
this._overrideService = Cc["@mozilla.org/security/certoverride;1"].getService(Ci.nsICertOverrideService);
// Check whether this site is a security exception. XPConnect does the right
@@ -7323,17 +7318,17 @@ var WebappsUI = {
Cu.reportError("CreateShortcut: favicon image load error");
// if the image failed to load, and it was not our default icon, attempt to
// use our default as a fallback
if (favicon.src != WebappsUI.DEFAULT_ICON) {
favicon.src = WebappsUI.DEFAULT_ICON;
}
};
-
+
favicon.src = aIconURL;
},
createShortcut: function createShortcut(aTitle, aURL, aIconURL, aType) {
this.makeBase64Icon(aIconURL, function _createShortcut(icon) {
try {
let shell = Cc["@mozilla.org/browser/shell-service;1"].createInstance(Ci.nsIShellService);
shell.createShortcut(aTitle, aURL, icon, aType);
@@ -8523,26 +8518,8 @@ HTMLContextMenuItem.prototype = Object.c
icon: elt.icon,
label: elt.label,
disabled: elt.disabled,
menu: elt instanceof Ci.nsIDOMHTMLMenuElement
};
}
},
});
-
-/**
- * CID of Downloads.jsm's implementation of nsITransfer.
- */
-const kTransferCid = Components.ID("{1b4c85df-cbdd-4bb6-b04e-613caece083c}");
-
-/**
- * Contract ID of the service implementing nsITransfer.
- */
-const kTransferContractId = "@mozilla.org/transfer;1";
-
-// Override Toolkit's nsITransfer implementation with the one from the
-// JavaScript API for downloads. This will eventually be removed when
-// nsIDownloadManager will not be available anymore (bug 851471). The
-// old code in this module will be removed in bug 899110.
-Components.manager.QueryInterface(Ci.nsIComponentRegistrar)
- .registerFactory(kTransferCid, "",
- kTransferContractId, null);
--- a/mobile/android/chrome/content/browser.xul
+++ b/mobile/android/chrome/content/browser.xul
@@ -6,12 +6,13 @@
<?xml-stylesheet href="chrome://browser/content/browser.css" type="text/css"?>
<window id="main-window"
onload="BrowserApp.startup();"
windowtype="navigator:browser"
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
<script type="application/javascript" src="chrome://browser/content/browser.js"/>
+ <script type="application/javascript" src="chrome://browser/content/downloads.js"/>
<deck id="browsers" flex="1"/>
</window>
new file mode 100644
--- /dev/null
+++ b/mobile/android/chrome/content/downloads.js
@@ -0,0 +1,298 @@
+// -*- Mode: js2; tab-width: 2; indent-tabs-mode: nil; js2-basic-offset: 2; js2-skip-preprocessor-directives: t; -*-
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+let Cu = Components.utils;
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+function dump(a) {
+ Cc["@mozilla.org/consoleservice;1"].getService(Ci.nsIConsoleService).logStringMessage(a);
+}
+
+XPCOMUtils.defineLazyModuleGetter(this, "Notifications",
+ "resource://gre/modules/Notifications.jsm");
+
+const URI_GENERIC_ICON_DOWNLOAD = "drawable://alert_download";
+const URI_PAUSE_ICON = "drawable://pause";
+const URI_CANCEL_ICON = "drawable://close";
+const URI_RESUME_ICON = "drawable://play";
+
+
+XPCOMUtils.defineLazyModuleGetter(this, "OS", "resource://gre/modules/osfile.jsm");
+
+var Downloads = {
+ _initialized: false,
+ _dlmgr: null,
+ _progressAlert: null,
+ _privateDownloads: [],
+ _showingPrompt: false,
+ _downloadsIdMap: {},
+
+ _getLocalFile: function dl__getLocalFile(aFileURI) {
+ // if this is a URL, get the file from that
+ // XXX it's possible that using a null char-set here is bad
+ const fileUrl = Services.io.newURI(aFileURI, null, null).QueryInterface(Ci.nsIFileURL);
+ return fileUrl.file.clone().QueryInterface(Ci.nsILocalFile);
+ },
+
+ init: function dl_init() {
+ if (this._initialized)
+ return;
+ this._initialized = true;
+
+ // Monitor downloads and display alerts
+ this._dlmgr = Cc["@mozilla.org/download-manager;1"].getService(Ci.nsIDownloadManager);
+ this._progressAlert = new AlertDownloadProgressListener();
+ this._dlmgr.addPrivacyAwareListener(this._progressAlert);
+ Services.obs.addObserver(this, "last-pb-context-exited", true);
+ },
+
+ openDownload: function dl_openDownload(aDownload) {
+ let fileUri = aDownload.target.spec;
+ let guid = aDownload.guid;
+ let f = this._getLocalFile(fileUri);
+ try {
+ f.launch();
+ } catch (ex) {
+ // in case we are not able to open the file (i.e. there is no app able to handle it)
+ // we just open the browser tab showing it
+ BrowserApp.addTab("about:downloads?id=" + guid);
+ }
+ },
+
+ cancelDownload: function dl_cancelDownload(aDownload) {
+ aDownload.cancel();
+ let fileURI = aDownload.target.spec;
+ let f = this._getLocalFile(fileURI);
+
+ OS.File.remove(f.path);
+ },
+
+ showCancelConfirmPrompt: function dl_showCancelConfirmPrompt(aDownload) {
+ if (this._showingPrompt)
+ return;
+ this._showingPrompt = true;
+ // Open a prompt that offers a choice to cancel the download
+ let title = Strings.browser.GetStringFromName("downloadCancelPromptTitle");
+ let message = Strings.browser.GetStringFromName("downloadCancelPromptMessage");
+ let flags = Services.prompt.BUTTON_POS_0 * Services.prompt.BUTTON_TITLE_YES +
+ Services.prompt.BUTTON_POS_1 * Services.prompt.BUTTON_TITLE_NO;
+ let choice = Services.prompt.confirmEx(null, title, message, flags,
+ null, null, null, null, {});
+ if (choice == 0)
+ this.cancelDownload(aDownload);
+ this._showingPrompt = false;
+ },
+
+ handleClickEvent: function dl_handleClickEvent(aDownload) {
+ // Only open the downloaded file if the download is complete
+ if (aDownload.state == Ci.nsIDownloadManager.DOWNLOAD_FINISHED)
+ this.openDownload(aDownload);
+ else if (aDownload.state == Ci.nsIDownloadManager.DOWNLOAD_DOWNLOADING ||
+ aDownload.state == Ci.nsIDownloadManager.DOWNLOAD_PAUSED)
+ this.showCancelConfirmPrompt(aDownload);
+ },
+
+ clickCallback: function dl_clickCallback(aDownloadId) {
+ this._dlmgr.getDownloadByGUID(aDownloadId, (function(status, download) {
+ if (Components.isSuccessCode(status))
+ this.handleClickEvent(download);
+ }).bind(this));
+ },
+
+ pauseClickCallback: function dl_buttonPauseCallback(aDownloadId) {
+ this._dlmgr.getDownloadByGUID(aDownloadId, (function(status, download) {
+ if (Components.isSuccessCode(status))
+ download.pause();
+ }).bind(this));
+ },
+
+ resumeClickCallback: function dl_buttonPauseCallback(aDownloadId) {
+ this._dlmgr.getDownloadByGUID(aDownloadId, (function(status, download) {
+ if (Components.isSuccessCode(status))
+ download.resume();
+ }).bind(this));
+ },
+
+ cancelClickCallback: function dl_buttonPauseCallback(aDownloadId) {
+ this._dlmgr.getDownloadByGUID(aDownloadId, (function(status, download) {
+ if (Components.isSuccessCode(status))
+ this.cancelDownload(download);
+ }).bind(this));
+ },
+
+ notificationCanceledCallback: function dl_notifCancelCallback(aId, aDownloadId) {
+ let notificationId = this._downloadsIdMap[aDownloadId];
+ if (notificationId && notificationId == aId)
+ delete this._downloadsIdMap[aDownloadId];
+ },
+
+ createNotification: function dl_createNotif(aDownload, aOptions) {
+ let notificationId = Notifications.create(aOptions);
+ this._downloadsIdMap[aDownload.guid]Â = notificationId;
+ },
+
+ updateNotification: function dl_updateNotif(aDownload, aOptions) {
+ let notificationId = this._downloadsIdMap[aDownload.guid];
+ if (notificationId)
+ Notifications.update(notificationId, aOptions);
+ },
+
+ cancelNotification: function dl_cleanNotif(aDownload) {
+ Notifications.cancel(this._downloadsIdMap[aDownload.guid]);
+ delete this._downloadsIdMap[aDownload.guid];
+ },
+
+ // observer for last-pb-context-exited
+ observe: function dl_observe(aSubject, aTopic, aData) {
+ let download;
+ while ((download = this._privateDownloads.pop())) {
+ try {
+ let notificationId = aDownload.guid;
+ Notifications.clear(notificationId);
+ Downloads.removeNotification(download);
+ } catch (e) {
+ dump("Error removing private download: " + e);
+ }
+ }
+ },
+
+ QueryInterface: function (aIID) {
+ if (!aIID.equals(Ci.nsISupports) &&
+ !aIID.equals(Ci.nsIObserver) &&
+ !aIID.equals(Ci.nsISupportsWeakReference))
+ throw Components.results.NS_ERROR_NO_INTERFACE;
+ return this;
+ }
+};
+
+const PAUSE_BUTTON = {
+ buttonId: "pause",
+ title : Strings.browser.GetStringFromName("alertDownloadsPause"),
+ icon : URI_PAUSE_ICON,
+ onClicked: function (aId, aCookie) {
+ Downloads.pauseClickCallback(aCookie);
+ }
+};
+
+const CANCEL_BUTTON = {
+ buttonId: "cancel",
+ title : Strings.browser.GetStringFromName("alertDownloadsCancel"),
+ icon : URI_CANCEL_ICON,
+ onClicked: function (aId, aCookie) {
+ Downloads.cancelClickCallback(aCookie);
+ }
+};
+
+const RESUME_BUTTON = {
+ buttonId: "resume",
+ title : Strings.browser.GetStringFromName("alertDownloadsResume"),
+ icon: URI_RESUME_ICON,
+ onClicked: function (aId, aCookie) {
+ Downloads.resumeClickCallback(aCookie);
+ }
+};
+
+function DownloadNotifOptions (aDownload, aTitle, aMessage) {
+ this.icon = URI_GENERIC_ICON_DOWNLOAD;
+ this.onCancel = function (aId, aCookie) {
+ Downloads.notificationCanceledCallback(aId, aCookie);
+ }
+ this.onClick = function (aId, aCookie) {
+ Downloads.clickCallback(aCookie);
+ }
+ this.title = aTitle;
+ this.message = aMessage;
+ this.buttons = null;
+ this.cookie = aDownload.guid;
+ this.persistent = true;
+}
+
+function DownloadProgressNotifOptions (aDownload, aButtons) {
+ DownloadNotifOptions.apply(this, [aDownload, aDownload.displayName, aDownload.percentComplete + "%"]);
+ this.ongoing = true;
+ this.progress = aDownload.percentComplete;
+ this.buttons = aButtons;
+}
+
+// AlertDownloadProgressListener is used to display progress in the alert notifications.
+function AlertDownloadProgressListener() { }
+
+AlertDownloadProgressListener.prototype = {
+ //////////////////////////////////////////////////////////////////////////////
+ //// nsIDownloadProgressListener
+ onProgressChange: function(aWebProgress, aRequest, aCurSelfProgress, aMaxSelfProgress, aCurTotalProgress, aMaxTotalProgress, aDownload) {
+ let strings = Strings.browser;
+ let availableSpace = -1;
+ try {
+ // diskSpaceAvailable is not implemented on all systems
+ let availableSpace = aDownload.targetFile.diskSpaceAvailable;
+ } catch(ex) { }
+ let contentLength = aDownload.size;
+ if (availableSpace > 0 && contentLength > 0 && contentLength > availableSpace) {
+ Downloads.updateNotification(aDownload, new DownloadNotifOptions(aDownload,
+ strings.GetStringFromName("alertDownloadsNoSpace"),
+ strings.GetStringFromName("alertDownloadsSize")));
+ aDownload.cancel();
+ }
+
+ if (aDownload.percentComplete == -1) {
+ // Undetermined progress is not supported yet
+ return;
+ }
+
+ Downloads.updateNotification(aDownload, new DownloadProgressNotifOptions(aDownload, [PAUSE_BUTTON, CANCEL_BUTTON]));
+ },
+
+ onDownloadStateChange: function(aState, aDownload) {
+ let state = aDownload.state;
+ switch (state) {
+ case Ci.nsIDownloadManager.DOWNLOAD_QUEUED: {
+ NativeWindow.toast.show(Strings.browser.GetStringFromName("alertDownloadsToast"), "long");
+ Downloads.createNotification(aDownload, new DownloadNotifOptions(aDownload,
+ Strings.browser.GetStringFromName("alertDownloadsStart2"),
+ aDownload.displayName));
+ break;
+ }
+ case Ci.nsIDownloadManager.DOWNLOAD_PAUSED: {
+ Downloads.updateNotification(aDownload, new DownloadProgressNotifOptions(aDownload, [RESUME_BUTTON, CANCEL_BUTTON]));
+ break;
+ }
+ case Ci.nsIDownloadManager.DOWNLOAD_FAILED:
+ case Ci.nsIDownloadManager.DOWNLOAD_CANCELED:
+ case Ci.nsIDownloadManager.DOWNLOAD_BLOCKED_PARENTAL:
+ case Ci.nsIDownloadManager.DOWNLOAD_DIRTY:
+ case Ci.nsIDownloadManager.DOWNLOAD_FINISHED: {
+ Downloads.cancelNotification(aDownload);
+ if (aDownload.isPrivate) {
+ let index = Downloads._privateDownloads.indexOf(aDownload);
+ if (index != -1) {
+ Downloads._privateDownloads.splice(index, 1);
+ }
+ }
+
+ if (state == Ci.nsIDownloadManager.DOWNLOAD_FINISHED) {
+ Downloads.createNotification(aDownload, new DownloadNotifOptions(aDownload,
+ Strings.browser.GetStringFromName("alertDownloadsDone2"),
+ aDownload.displayName));
+ }
+ break;
+ }
+ }
+ },
+
+ onStateChange: function(aWebProgress, aRequest, aState, aStatus, aDownload) { },
+ onSecurityChange: function(aWebProgress, aRequest, aState, aDownload) { },
+
+ //////////////////////////////////////////////////////////////////////////////
+ //// nsISupports
+ QueryInterface: function (aIID) {
+ if (!aIID.equals(Ci.nsIDownloadProgressListener) &&
+ !aIID.equals(Ci.nsISupports))
+ throw Components.results.NS_ERROR_NO_INTERFACE;
+ return this;
+ }
+};
--- a/mobile/android/chrome/jar.mn
+++ b/mobile/android/chrome/jar.mn
@@ -29,16 +29,17 @@ chrome.jar:
* content/aboutApps.xhtml (content/aboutApps.xhtml)
* content/aboutApps.js (content/aboutApps.js)
content/blockedSite.xhtml (content/blockedSite.xhtml)
content/languages.properties (content/languages.properties)
content/browser.xul (content/browser.xul)
* content/browser.js (content/browser.js)
content/bindings/checkbox.xml (content/bindings/checkbox.xml)
content/bindings/settings.xml (content/bindings/settings.xml)
+ content/downloads.js (content/downloads.js)
content/netError.xhtml (content/netError.xhtml)
content/SelectHelper.js (content/SelectHelper.js)
content/SelectionHandler.js (content/SelectionHandler.js)
content/dbg-browser-actors.js (content/dbg-browser-actors.js)
* content/WebappRT.js (content/WebappRT.js)
content/InputWidgetHelper.js (content/InputWidgetHelper.js)
content/WebrtcUI.js (content/WebrtcUI.js)
content/MemoryObserver.js (content/MemoryObserver.js)
--- a/mobile/android/installer/package-manifest.in
+++ b/mobile/android/installer/package-manifest.in
@@ -437,19 +437,16 @@
@BINPATH@/components/dom_webspeechsynth.xpt
#endif
#ifdef MOZ_DEBUG
@BINPATH@/components/TestInterfaceJS.js
@BINPATH@/components/TestInterfaceJS.manifest
#endif
-@BINPATH@/components/Downloads.manifest
-@BINPATH@/components/DownloadLegacy.js
-
; Modules
@BINPATH@/modules/*
#ifdef MOZ_SAFE_BROWSING
; Safe Browsing
@BINPATH@/components/nsURLClassifier.manifest
@BINPATH@/components/nsUrlClassifierHashCompleter.js
@BINPATH@/components/nsUrlClassifierListManager.js
deleted file mode 100644
--- a/mobile/android/modules/DownloadNotifications.jsm
+++ /dev/null
@@ -1,232 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-"use strict";
-
-this.EXPORTED_SYMBOLS = ["DownloadNotifications"];
-
-const Cu = Components.utils;
-
-Cu.import("resource://gre/modules/XPCOMUtils.jsm");
-
-XPCOMUtils.defineLazyModuleGetter(this, "Downloads", "resource://gre/modules/Downloads.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "Notifications", "resource://gre/modules/Notifications.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "OS", "resource://gre/modules/osfile.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "Services", "resource://gre/modules/Services.jsm");
-
-XPCOMUtils.defineLazyGetter(this, "strings",
- () => Services.strings.createBundle("chrome://browser/locale/browser.properties"));
-Object.defineProperty(this, "nativeWindow",
- { get: () => Services.wm.getMostRecentWindow("navigator:browser").NativeWindow });
-
-const kButtons = {
- PAUSE: new DownloadNotificationButton("pause",
- "drawable://pause",
- "alertDownloadsPause",
- notification => notification.pauseDownload()),
- RESUME: new DownloadNotificationButton("resume",
- "drawable://play",
- "alertDownloadsResume",
- notification => notification.resumeDownload()),
- CANCEL: new DownloadNotificationButton("cancel",
- "drawable://close",
- "alertDownloadsCancel",
- notification => notification.cancelDownload())
-};
-
-let notifications = new Map();
-
-var DownloadNotifications = {
- init: function () {
- if (!this._viewAdded) {
- Downloads.getList(Downloads.ALL)
- .then(list => list.addView(this))
- .then(null, Cu.reportError);
-
- this._viewAdded = true;
- }
- },
-
- uninit: function () {
- if (this._viewAdded) {
- Downloads.getList(Downloads.ALL)
- .then(list => list.removeView(this))
- .then(null, Cu.reportError);
-
- for (let notification of notifications.values()) {
- notification.hide();
- }
-
- this._viewAdded = false;
- }
- },
-
- onDownloadAdded: function (download) {
- let notification = new DownloadNotification(download);
- notifications.set(download, notification);
-
- notification.showOrUpdate();
- if (download.currentBytes == 0) {
- nativeWindow.toast.show(strings.GetStringFromName("alertDownloadsToast"), "long");
- }
- },
-
- onDownloadChanged: function (download) {
- let notification = notifications.get(download);
- if (!notification) {
- Cu.reportError("Download doesn't have a notification.");
- return;
- }
-
- notification.showOrUpdate();
- },
-
- onDownloadRemoved: function (download) {
- let notification = notifications.get(download);
- if (!notification) {
- Cu.reportError("Download doesn't have a notification.");
- return;
- }
-
- notification.hide();
- notifications.delete(download);
- }
-};
-
-function DownloadNotification(download) {
- this.download = download;
- this._fileName = OS.Path.basename(download.target.path);
-
- this.id = null;
-}
-
-DownloadNotification.prototype = {
- _updateFromDownload: function () {
- this._downloading = !this.download.stopped;
- this._paused = this.download.canceled && this.download.hasPartialData;
- this._succeeded = this.download.succeeded;
-
- this._show = this._downloading || this._paused || this._succeeded;
- },
-
- get options() {
- if (!this._show) {
- return null;
- }
-
- let options = {
- icon : "drawable://alert_download",
- onClick : (id, cookie) => this.onClick(),
- onCancel : (id, cookie) => this._notificationId = null,
- cookie : this.download
- };
-
- if (this._downloading) {
- if (this.download.currentBytes == 0) {
- this._updateOptionsForStatic(options, "alertDownloadsStart2");
- } else {
- this._updateOptionsForOngoing(options, [kButtons.PAUSE, kButtons.CANCEL]);
- }
- } else if (this._paused) {
- this._updateOptionsForOngoing(options, [kButtons.RESUME, kButtons.CANCEL]);
- } else if (this._succeeded) {
- options.persistent = false;
- this._updateOptionsForStatic(options, "alertDownloadsDone2");
- }
-
- return options;
- },
-
- _updateOptionsForStatic : function (options, titleName) {
- options.title = strings.GetStringFromName(titleName);
- options.message = this._fileName;
- },
-
- _updateOptionsForOngoing: function (options, buttons) {
- options.title = this._fileName;
- options.message = this.download.progress + "%";
- options.buttons = buttons;
- options.ongoing = true;
- options.progress = this.download.progress;
- options.persistent = true;
- },
-
- showOrUpdate: function () {
- this._updateFromDownload();
-
- if (this._show) {
- if (!this.id) {
- this.id = Notifications.create(this.options);
- } else {
- Notifications.update(this.id, this.options);
- }
- } else {
- this.hide();
- }
- },
-
- hide: function () {
- if (this.id) {
- Notifications.cancel(this.id);
- this.id = null;
- }
- },
-
- onClick: function () {
- if (this.download.succeeded) {
- this.download.launch().then(null, Cu.reportError);
- } else {
- ConfirmCancelPrompt.show(this);
- }
- },
-
- pauseDownload: function () {
- this.download.cancel().then(null, Cu.reportError);
- },
-
- resumeDownload: function () {
- this.download.start().then(null, Cu.reportError);
- },
-
- cancelDownload: function () {
- this.hide();
-
- this.download.cancel().then(null, Cu.reportError);
- this.download.removePartialData().then(null, Cu.reportError);
- }
-};
-
-var ConfirmCancelPrompt = {
- showing: false,
- show: function (downloadNotification) {
- if (this.showing) {
- return;
- }
-
- this.showing = true;
- // Open a prompt that offers a choice to cancel the download
- let title = strings.GetStringFromName("downloadCancelPromptTitle");
- let message = strings.GetStringFromName("downloadCancelPromptMessage");
-
- if (Services.prompt.confirm(null, title, message)) {
- downloadNotification.cancelDownload();
- }
- this.showing = false;
- }
-};
-
-function DownloadNotificationButton(buttonId, iconUrl, titleStringName, onClicked) {
- this.buttonId = buttonId;
- this.title = strings.GetStringFromName(titleStringName);
- this.icon = iconUrl;
- this.onClicked = (id, download) => {
- let notification = notifications.get(download);
- if (!notification) {
- Cu.reportError("No DownloadNotification for button");
- return;
- }
-
- onClicked(notification);
- }
-}
--- a/mobile/android/modules/moz.build
+++ b/mobile/android/modules/moz.build
@@ -3,17 +3,16 @@
# 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/.
EXTRA_JS_MODULES += [
'Accounts.jsm',
'AndroidLog.jsm',
'ContactService.jsm',
- 'DownloadNotifications.jsm',
'HelperApps.jsm',
'Home.jsm',
'HomeProvider.jsm',
'JNI.jsm',
'LightweightThemeConsumer.jsm',
'Messaging.jsm',
'Notifications.jsm',
'OrderedBroadcast.jsm',
--- a/mobile/android/themes/core/aboutDownloads.css
+++ b/mobile/android/themes/core/aboutDownloads.css
@@ -46,27 +46,27 @@ li:active div.details,
color: gray;
}
.domain,
.size {
display: inline;
}
-.state {
+.displayState {
color: gray;
margin-bottom: -3px; /* Prevent overflow that hides bottom border */
}
.size:after {
content: " - ";
white-space: pre;
}
#no-downloads-indicator {
display: none;
}
-#private-downloads-list:empty + #public-downloads-list:empty + #no-downloads-indicator {
+#private-downloads-list:empty + #normal-downloads-list:empty + #no-downloads-indicator {
display: block;
text-align: center;
padding-top: 3.9em;
}
--- a/security/apps/AppSignatureVerification.cpp
+++ b/security/apps/AppSignatureVerification.cpp
@@ -600,18 +600,19 @@ VerifySignature(AppTrustedRoot trustedRo
// Verify certificate.
AppTrustDomain trustDomain(nullptr); // TODO: null pinArg
if (trustDomain.SetTrustedRoot(trustedRoot) != SECSuccess) {
return MapSECStatus(SECFailure);
}
if (BuildCertChain(trustDomain, signerCert, PR_Now(),
EndEntityOrCA::MustBeEndEntity, KU_DIGITAL_SIGNATURE,
- SEC_OID_EXT_KEY_USAGE_CODE_SIGN,
- SEC_OID_X509_ANY_POLICY, nullptr, builtChain)
+ KeyPurposeId::id_kp_codeSigning,
+ CertPolicyId::anyPolicy,
+ nullptr, builtChain)
!= SECSuccess) {
return MapSECStatus(SECFailure);
}
// See NSS_CMSContentInfo_GetContentTypeOID, which isn't exported from NSS.
SECOidData* contentTypeOidData =
SECOID_FindOID(&signedData->contentInfo.contentType);
if (!contentTypeOidData) {
--- a/security/apps/AppTrustDomain.cpp
+++ b/security/apps/AppTrustDomain.cpp
@@ -98,25 +98,25 @@ AppTrustDomain::FindPotentialIssuers(con
results = CERT_CreateSubjectCertList(nullptr, CERT_GetDefaultCertDB(),
encodedIssuerName, time, true);
return SECSuccess;
}
SECStatus
AppTrustDomain::GetCertTrust(EndEntityOrCA endEntityOrCA,
- SECOidTag policy,
+ const CertPolicyId& policy,
const CERTCertificate* candidateCert,
/*out*/ TrustLevel* trustLevel)
{
- MOZ_ASSERT(policy == SEC_OID_X509_ANY_POLICY);
+ MOZ_ASSERT(policy.IsAnyPolicy());
MOZ_ASSERT(candidateCert);
MOZ_ASSERT(trustLevel);
MOZ_ASSERT(mTrustedRoot);
- if (!candidateCert || !trustLevel || policy != SEC_OID_X509_ANY_POLICY) {
+ if (!candidateCert || !trustLevel || !policy.IsAnyPolicy()) {
PR_SetError(SEC_ERROR_INVALID_ARGS, 0);
return SECFailure;
}
if (!mTrustedRoot) {
PR_SetError(PR_INVALID_STATE_ERROR, 0);
return SECFailure;
}
--- a/security/apps/AppTrustDomain.h
+++ b/security/apps/AppTrustDomain.h
@@ -16,17 +16,17 @@ namespace mozilla { namespace psm {
class AppTrustDomain MOZ_FINAL : public mozilla::pkix::TrustDomain
{
public:
AppTrustDomain(void* pinArg);
SECStatus SetTrustedRoot(AppTrustedRoot trustedRoot);
SECStatus GetCertTrust(mozilla::pkix::EndEntityOrCA endEntityOrCA,
- SECOidTag policy,
+ const mozilla::pkix::CertPolicyId& policy,
const CERTCertificate* candidateCert,
/*out*/ mozilla::pkix::TrustLevel* trustLevel) MOZ_OVERRIDE;
SECStatus FindPotentialIssuers(const SECItem* encodedIssuerName,
PRTime time,
/*out*/ mozilla::pkix::ScopedCERTCertList& results)
MOZ_OVERRIDE;
SECStatus VerifySignedData(const CERTSignedData* signedData,
const CERTCertificate* cert) MOZ_OVERRIDE;
--- a/security/certverifier/CertVerifier.cpp
+++ b/security/certverifier/CertVerifier.cpp
@@ -295,18 +295,18 @@ destroyCertListThatShouldNotExist(CERTCe
*certChain = nullptr;
}
}
#endif
static SECStatus
BuildCertChainForOneKeyUsage(TrustDomain& trustDomain, CERTCertificate* cert,
PRTime time, KeyUsages ku1, KeyUsages ku2,
- KeyUsages ku3, SECOidTag eku,
- SECOidTag requiredPolicy,
+ KeyUsages ku3, KeyPurposeId eku,
+ const CertPolicyId& requiredPolicy,
const SECItem* stapledOCSPResponse,
ScopedCERTCertList& builtChain)
{
PR_ASSERT(ku1);
PR_ASSERT(ku2);
SECStatus rv = BuildCertChain(trustDomain, cert, time,
EndEntityOrCA::MustBeEndEntity, ku1,
@@ -386,48 +386,49 @@ CertVerifier::MozillaPKIXVerifyCert(
switch (usage) {
case certificateUsageSSLClient: {
// XXX: We don't really have a trust bit for SSL client authentication so
// just use trustEmail as it is the closest alternative.
NSSCertDBTrustDomain trustDomain(trustEmail, ocspFetching, mOCSPCache,
pinArg);
rv = BuildCertChain(trustDomain, cert, time,
EndEntityOrCA::MustBeEndEntity, KU_DIGITAL_SIGNATURE,
- SEC_OID_EXT_KEY_USAGE_CLIENT_AUTH,
- SEC_OID_X509_ANY_POLICY,
- stapledOCSPResponse, builtChain);
+ KeyPurposeId::id_kp_clientAuth,
+ CertPolicyId::anyPolicy, stapledOCSPResponse,
+ builtChain);
break;
}
case certificateUsageSSLServer: {
// TODO: When verifying a certificate in an SSL handshake, we should
// restrict the acceptable key usage based on the key exchange method
// chosen by the server.
#ifndef MOZ_NO_EV_CERTS
// Try to validate for EV first.
- SECOidTag evPolicy = SEC_OID_UNKNOWN;
- rv = GetFirstEVPolicy(cert, evPolicy);
- if (rv == SECSuccess && evPolicy != SEC_OID_UNKNOWN) {
+ CertPolicyId evPolicy;
+ SECOidTag evPolicyOidTag;
+ rv = GetFirstEVPolicy(cert, evPolicy, evPolicyOidTag);
+ if (rv == SECSuccess) {
NSSCertDBTrustDomain
trustDomain(trustSSL,
ocspFetching == NSSCertDBTrustDomain::NeverFetchOCSP
? NSSCertDBTrustDomain::LocalOnlyOCSPForEV
: NSSCertDBTrustDomain::FetchOCSPForEV,
mOCSPCache, pinArg, &callbackContainer);
rv = BuildCertChainForOneKeyUsage(trustDomain, cert, time,
KU_DIGITAL_SIGNATURE, // ECDHE/DHE
KU_KEY_ENCIPHERMENT, // RSA
KU_KEY_AGREEMENT, // ECDH/DH
- SEC_OID_EXT_KEY_USAGE_SERVER_AUTH,
+ KeyPurposeId::id_kp_serverAuth,
evPolicy, stapledOCSPResponse,
builtChain);
if (rv == SECSuccess) {
if (evOidPolicy) {
- *evOidPolicy = evPolicy;
+ *evOidPolicy = evPolicyOidTag;
}
break;
}
builtChain = nullptr; // clear built chain, just in case.
}
#endif
if (flags & FLAG_MUST_BE_EV) {
@@ -438,107 +439,106 @@ CertVerifier::MozillaPKIXVerifyCert(
// Now try non-EV.
NSSCertDBTrustDomain trustDomain(trustSSL, ocspFetching, mOCSPCache,
pinArg, &callbackContainer);
rv = BuildCertChainForOneKeyUsage(trustDomain, cert, time,
KU_DIGITAL_SIGNATURE, // ECDHE/DHE
KU_KEY_ENCIPHERMENT, // RSA
KU_KEY_AGREEMENT, // ECDH/DH
- SEC_OID_EXT_KEY_USAGE_SERVER_AUTH,
- SEC_OID_X509_ANY_POLICY,
+ KeyPurposeId::id_kp_serverAuth,
+ CertPolicyId::anyPolicy,
stapledOCSPResponse, builtChain);
break;
}
case certificateUsageSSLCA: {
NSSCertDBTrustDomain trustDomain(trustSSL, ocspFetching, mOCSPCache,
pinArg);
rv = BuildCertChain(trustDomain, cert, time, EndEntityOrCA::MustBeCA,
- KU_KEY_CERT_SIGN,
- SEC_OID_EXT_KEY_USAGE_SERVER_AUTH,
- SEC_OID_X509_ANY_POLICY,
+ KU_KEY_CERT_SIGN, KeyPurposeId::id_kp_serverAuth,
+ CertPolicyId::anyPolicy,
stapledOCSPResponse, builtChain);
break;
}
case certificateUsageEmailSigner: {
NSSCertDBTrustDomain trustDomain(trustEmail, ocspFetching, mOCSPCache,
pinArg);
rv = BuildCertChain(trustDomain, cert, time,
EndEntityOrCA::MustBeEndEntity, KU_DIGITAL_SIGNATURE,
- SEC_OID_EXT_KEY_USAGE_EMAIL_PROTECT,
- SEC_OID_X509_ANY_POLICY,
+ KeyPurposeId::id_kp_emailProtection,
+ CertPolicyId::anyPolicy,
stapledOCSPResponse, builtChain);
break;
}
case certificateUsageEmailRecipient: {
// TODO: The higher level S/MIME processing should pass in which key
// usage it is trying to verify for, and base its algorithm choices
// based on the result of the verification(s).
NSSCertDBTrustDomain trustDomain(trustEmail, ocspFetching, mOCSPCache,
pinArg);
rv = BuildCertChainForOneKeyUsage(trustDomain, cert, time,
KU_KEY_ENCIPHERMENT, // RSA
KU_KEY_AGREEMENT, // ECDH/DH
0,
- SEC_OID_EXT_KEY_USAGE_EMAIL_PROTECT,
- SEC_OID_X509_ANY_POLICY,
+ KeyPurposeId::id_kp_emailProtection,
+ CertPolicyId::anyPolicy,
stapledOCSPResponse, builtChain);
break;
}
case certificateUsageObjectSigner: {
NSSCertDBTrustDomain trustDomain(trustObjectSigning, ocspFetching,
mOCSPCache, pinArg);
rv = BuildCertChain(trustDomain, cert, time,
EndEntityOrCA::MustBeEndEntity, KU_DIGITAL_SIGNATURE,
- SEC_OID_EXT_KEY_USAGE_CODE_SIGN,
- SEC_OID_X509_ANY_POLICY,
+ KeyPurposeId::id_kp_codeSigning,
+ CertPolicyId::anyPolicy,
stapledOCSPResponse, builtChain);
break;
}
case certificateUsageVerifyCA:
case certificateUsageStatusResponder: {
// XXX This is a pretty useless way to verify a certificate. It is used
// by the implementation of window.crypto.importCertificates and in the
// certificate viewer UI. Because we don't know what trust bit is
// interesting, we just try them all.
mozilla::pkix::EndEntityOrCA endEntityOrCA;
mozilla::pkix::KeyUsages keyUsage;
- SECOidTag eku;
+ KeyPurposeId eku;
if (usage == certificateUsageVerifyCA) {
endEntityOrCA = EndEntityOrCA::MustBeCA;
keyUsage = KU_KEY_CERT_SIGN;
- eku = SEC_OID_UNKNOWN;
+ eku = KeyPurposeId::anyExtendedKeyUsage;
} else {
endEntityOrCA = EndEntityOrCA::MustBeEndEntity;
keyUsage = KU_DIGITAL_SIGNATURE;
- eku = SEC_OID_OCSP_RESPONDER;
+ eku = KeyPurposeId::id_kp_OCSPSigning;
}
NSSCertDBTrustDomain sslTrust(trustSSL, ocspFetching, mOCSPCache,
pinArg);
rv = BuildCertChain(sslTrust, cert, time, endEntityOrCA,
- keyUsage, eku, SEC_OID_X509_ANY_POLICY,
+ keyUsage, eku, CertPolicyId::anyPolicy,
stapledOCSPResponse, builtChain);
if (rv == SECFailure && PR_GetError() == SEC_ERROR_UNKNOWN_ISSUER) {
NSSCertDBTrustDomain emailTrust(trustEmail, ocspFetching, mOCSPCache,
pinArg);
rv = BuildCertChain(emailTrust, cert, time, endEntityOrCA, keyUsage,
- eku, SEC_OID_X509_ANY_POLICY,
+ eku, CertPolicyId::anyPolicy,
stapledOCSPResponse, builtChain);
if (rv == SECFailure && SEC_ERROR_UNKNOWN_ISSUER) {
NSSCertDBTrustDomain objectSigningTrust(trustObjectSigning,
ocspFetching, mOCSPCache,
pinArg);
rv = BuildCertChain(objectSigningTrust, cert, time, endEntityOrCA,
- keyUsage, eku, SEC_OID_X509_ANY_POLICY,
+ keyUsage, eku, CertPolicyId::anyPolicy,
stapledOCSPResponse, builtChain);
}
}
break;
}
default:
@@ -611,17 +611,18 @@ CertVerifier::VerifyCert(CERTCertificate
#ifndef NSS_NO_LIBPKIX
ScopedCERTCertList trustAnchors;
SECStatus rv;
SECOidTag evPolicy = SEC_OID_UNKNOWN;
// Do EV checking only for sslserver usage
if (usage == certificateUsageSSLServer) {
- SECStatus srv = GetFirstEVPolicy(cert, evPolicy);
+ CertPolicyId unusedPolicyId;
+ SECStatus srv = GetFirstEVPolicy(cert, unusedPolicyId, evPolicy);
if (srv == SECSuccess) {
if (evPolicy != SEC_OID_UNKNOWN) {
trustAnchors = GetRootsForOid(evPolicy);
}
if (!trustAnchors) {
return SECFailure;
}
// pkix ignores an empty trustanchors list and
--- a/security/certverifier/ExtendedValidation.cpp
+++ b/security/certverifier/ExtendedValidation.cpp
@@ -5,16 +5,17 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "ExtendedValidation.h"
#include "cert.h"
#include "certdb.h"
#include "base64.h"
#include "pkix/nullptr.h"
+#include "pkix/pkixtypes.h"
#include "pk11pub.h"
#include "secerr.h"
#include "prerror.h"
#include "prinit.h"
#ifdef PR_LOGGING
extern PRLogModuleInfo* gPIPNSSLog;
#endif
@@ -875,29 +876,31 @@ GetRootsForOid(SECOidTag oid_tag)
}
return certList;
}
#endif
bool
CertIsAuthoritativeForEVPolicy(const CERTCertificate* cert,
- SECOidTag policyOidTag)
+ const mozilla::pkix::CertPolicyId& policy)
{
PR_ASSERT(cert);
- PR_ASSERT(policyOidTag != SEC_OID_UNKNOWN);
- if (!cert || !policyOidTag) {
+ if (!cert) {
return false;
}
for (size_t iEV = 0; iEV < PR_ARRAY_SIZE(myTrustedEVInfos); ++iEV) {
nsMyTrustedEVInfo& entry = myTrustedEVInfos[iEV];
- if (entry.oid_tag == policyOidTag && entry.cert &&
- CERT_CompareCerts(cert, entry.cert)) {
- return true;
+ if (entry.cert && CERT_CompareCerts(cert, entry.cert)) {
+ const SECOidData* oidData = SECOID_FindOIDByTag(entry.oid_tag);
+ if (oidData && oidData->oid.len == policy.numBytes &&
+ !memcmp(oidData->oid.data, policy.bytes, policy.numBytes)) {
+ return true;
+ }
}
}
return false;
}
static PRStatus
IdentityInfoInit()
@@ -940,17 +943,18 @@ IdentityInfoInit()
}
#endif
PR_NOT_REACHED("Could not find EV root in NSS storage");
continue;
}
unsigned char certFingerprint[20];
rv = PK11_HashBuf(SEC_OID_SHA1, certFingerprint,
- entry.cert->derCert.data, entry.cert->derCert.len);
+ entry.cert->derCert.data,
+ static_cast<int32_t>(entry.cert->derCert.len));
PR_ASSERT(rv == SECSuccess);
if (rv == SECSuccess) {
bool same = !memcmp(certFingerprint, entry.ev_root_sha1_fingerprint, 20);
PR_ASSERT(same);
if (same) {
SECItem ev_oid_item;
ev_oid_item.data = nullptr;
@@ -1000,20 +1004,24 @@ CleanupIdentityInfo()
}
}
memset(&sIdentityInfoCallOnce, 0, sizeof(PRCallOnceType));
}
// Find the first policy OID that is known to be an EV policy OID.
SECStatus
-GetFirstEVPolicy(CERTCertificate* cert, SECOidTag& outOidTag)
+GetFirstEVPolicy(CERTCertificate* cert,
+ /*out*/ mozilla::pkix::CertPolicyId& policy,
+ /*out*/ SECOidTag& policyOidTag)
{
- if (!cert)
+ if (!cert) {
+ PR_SetError(SEC_ERROR_INVALID_ARGS, 0);
return SECFailure;
+ }
if (cert->extensions) {
for (int i=0; cert->extensions[i]; i++) {
const SECItem* oid = &cert->extensions[i]->id;
SECOidTag oidTag = SECOID_FindOIDTag(oid);
if (oidTag != SEC_OID_X509_CERTIFICATE_POLICIES)
continue;
@@ -1030,24 +1038,35 @@ GetFirstEVPolicy(CERTCertificate* cert,
policyInfos = policies->policyInfos;
bool found = false;
while (*policyInfos) {
const CERTPolicyInfo* policyInfo = *policyInfos++;
SECOidTag oid_tag = policyInfo->oid;
if (oid_tag != SEC_OID_UNKNOWN && isEVPolicy(oid_tag)) {
- // in our list of OIDs accepted for EV
- outOidTag = oid_tag;
- found = true;
+ const SECOidData* oidData = SECOID_FindOIDByTag(oid_tag);
+ PR_ASSERT(oidData);
+ PR_ASSERT(oidData->oid.data);
+ PR_ASSERT(oidData->oid.len > 0);
+ PR_ASSERT(oidData->oid.len <= mozilla::pkix::CertPolicyId::MAX_BYTES);
+ if (oidData && oidData->oid.data && oidData->oid.len > 0 &&
+ oidData->oid.len <= mozilla::pkix::CertPolicyId::MAX_BYTES) {
+ policy.numBytes = static_cast<uint16_t>(oidData->oid.len);
+ memcpy(policy.bytes, oidData->oid.data, policy.numBytes);
+ policyOidTag = oid_tag;
+ found = true;
+ }
break;
}
}
CERT_DestroyCertificatePoliciesExtension(policies);
- if (found)
+ if (found) {
return SECSuccess;
+ }
}
}
+ PR_SetError(SEC_ERROR_POLICY_VALIDATION_FAILED, 0);
return SECFailure;
}
} } // namespace mozilla::psm
--- a/security/certverifier/ExtendedValidation.h
+++ b/security/certverifier/ExtendedValidation.h
@@ -4,27 +4,31 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef mozilla_psm_ExtendedValidation_h
#define mozilla_psm_ExtendedValidation_h
#include "certt.h"
#include "prtypes.h"
+namespace mozilla { namespace pkix { struct CertPolicyId; } }
+
namespace mozilla { namespace psm {
#ifndef MOZ_NO_EV_CERTS
void EnsureIdentityInfoLoaded();
void CleanupIdentityInfo();
-SECStatus GetFirstEVPolicy(CERTCertificate* cert, SECOidTag& outOidTag);
+SECStatus GetFirstEVPolicy(CERTCertificate* cert,
+ /*out*/ mozilla::pkix::CertPolicyId& policy,
+ /*out*/ SECOidTag& policyOidTag);
// CertIsAuthoritativeForEVPolicy does NOT evaluate whether the cert is trusted
// or distrusted.
bool CertIsAuthoritativeForEVPolicy(const CERTCertificate* cert,
- SECOidTag policyOidTag);
+ const mozilla::pkix::CertPolicyId& policy);
#endif
#ifndef NSS_NO_LIBPKIX
CERTCertList* GetRootsForOid(SECOidTag oid_tag);
#endif
} } // namespace mozilla::psm
--- a/security/certverifier/NSSCertDBTrustDomain.cpp
+++ b/security/certverifier/NSSCertDBTrustDomain.cpp
@@ -63,30 +63,30 @@ NSSCertDBTrustDomain::FindPotentialIssue
// "there was an error trying to retrieve the potential issuers."
results = CERT_CreateSubjectCertList(nullptr, CERT_GetDefaultCertDB(),
encodedIssuerName, time, true);
return SECSuccess;
}
SECStatus
NSSCertDBTrustDomain::GetCertTrust(EndEntityOrCA endEntityOrCA,
- SECOidTag policy,
+ const CertPolicyId& policy,
const CERTCertificate* candidateCert,
/*out*/ TrustLevel* trustLevel)
{
PR_ASSERT(candidateCert);
PR_ASSERT(trustLevel);
if (!candidateCert || !trustLevel) {
PR_SetError(SEC_ERROR_INVALID_ARGS, 0);
return SECFailure;
}
#ifdef MOZ_NO_EV_CERTS
- if (policy != SEC_OID_X509_ANY_POLICY) {
+ if (!policy.IsAnyPolicy()) {
PR_SetError(SEC_ERROR_POLICY_VALIDATION_FAILED, 0);
return SECFailure;
}
#endif
// XXX: CERT_GetCertTrust seems to be abusing SECStatus as a boolean, where
// SECSuccess means that there is a trust record and SECFailure means there
// is not a trust record. I looked at NSS's internal uses of
@@ -109,17 +109,17 @@ NSSCertDBTrustDomain::GetCertTrust(EndEn
*trustLevel = TrustLevel::ActivelyDistrusted;
return SECSuccess;
}
// For TRUST, we only use the CERTDB_TRUSTED_CA bit, because Gecko hasn't
// needed to consider end-entity certs to be their own trust anchors since
// Gecko implemented nsICertOverrideService.
if (flags & CERTDB_TRUSTED_CA) {
- if (policy == SEC_OID_X509_ANY_POLICY) {
+ if (policy.IsAnyPolicy()) {
*trustLevel = TrustLevel::TrustAnchor;
return SECSuccess;
}
#ifndef MOZ_NO_EV_CERTS
if (CertIsAuthoritativeForEVPolicy(candidateCert, policy)) {
*trustLevel = TrustLevel::TrustAnchor;
return SECSuccess;
}
@@ -463,28 +463,28 @@ NSSCertDBTrustDomain::IsChainValid(const
}
namespace {
static char*
nss_addEscape(const char* string, char quote)
{
char* newString = 0;
- int escapes = 0, size = 0;
+ size_t escapes = 0, size = 0;
const char* src;
char* dest;
for (src = string; *src; src++) {
if ((*src == quote) || (*src == '\\')) {
escapes++;
}
size++;
}
- newString = (char*) PORT_ZAlloc(escapes + size + 1);
+ newString = (char*) PORT_ZAlloc(escapes + size + 1u);
if (!newString) {
return nullptr;
}
for (src = string, dest = newString; *src; src++, dest++) {
if ((*src == quote) || (*src == '\\')) {
*dest++ = '\\';
}
@@ -594,19 +594,19 @@ SetClassicOCSPBehavior(CertVerifier::ocs
SEC_OcspFailureMode failureMode = strict == CertVerifier::ocsp_strict
? ocspMode_FailureIsVerificationFailure
: ocspMode_FailureIsNotAVerificationFailure;
(void) CERT_SetOCSPFailureMode(failureMode);
CERT_ForcePostMethodForOCSP(get != CertVerifier::ocsp_get_enabled);
- int OCSPTimeoutSeconds = 3;
+ uint32_t OCSPTimeoutSeconds = 3u;
if (strict == CertVerifier::ocsp_strict) {
- OCSPTimeoutSeconds = 10;
+ OCSPTimeoutSeconds = 10u;
}
CERT_SetOCSPTimeout(OCSPTimeoutSeconds);
}
char*
DefaultServerNicknameForCert(CERTCertificate* cert)
{
char* nickname = nullptr;
--- a/security/certverifier/NSSCertDBTrustDomain.h
+++ b/security/certverifier/NSSCertDBTrustDomain.h
@@ -61,17 +61,17 @@ public:
CERTChainVerifyCallback* checkChainCallback = nullptr);
virtual SECStatus FindPotentialIssuers(
const SECItem* encodedIssuerName,
PRTime time,
/*out*/ mozilla::pkix::ScopedCERTCertList& results);
virtual SECStatus GetCertTrust(mozilla::pkix::EndEntityOrCA endEntityOrCA,
- SECOidTag policy,
+ const mozilla::pkix::CertPolicyId& policy,
const CERTCertificate* candidateCert,
/*out*/ mozilla::pkix::TrustLevel* trustLevel);
virtual SECStatus VerifySignedData(const CERTSignedData* signedData,
const CERTCertificate* cert);
virtual SECStatus CheckRevocation(mozilla::pkix::EndEntityOrCA endEntityOrCA,
const CERTCertificate* cert,
--- a/security/certverifier/OCSPCache.cpp
+++ b/security/certverifier/OCSPCache.cpp
@@ -19,16 +19,18 @@
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "OCSPCache.h"
+#include <limits>
+
#include "NSSCertDBTrustDomain.h"
#include "pk11pub.h"
#include "secerr.h"
#ifdef PR_LOGGING
extern PRLogModuleInfo* gCertVerifierLog;
#endif
@@ -108,40 +110,44 @@ OCSPCache::OCSPCache()
{
}
OCSPCache::~OCSPCache()
{
Clear();
}
-// Returns -1 if no entry is found for the given (cert, issuer) pair.
-int32_t
+// Returns false with index in an undefined state if no matching entry was
+// found.
+bool
OCSPCache::FindInternal(const CERTCertificate* aCert,
const CERTCertificate* aIssuerCert,
+ /*out*/ size_t& index,
const MutexAutoLock& /* aProofOfLock */)
{
if (mEntries.length() == 0) {
- return -1;
+ return false;
}
SHA384Buffer idHash;
SECStatus rv = CertIDHash(idHash, aCert, aIssuerCert);
if (rv != SECSuccess) {
- return -1;
+ return false;
}
// mEntries is sorted with the most-recently-used entry at the end.
// Thus, searching from the end will often be fastest.
- for (int32_t i = mEntries.length() - 1; i >= 0; i--) {
- if (memcmp(mEntries[i]->mIDHash, idHash, SHA384_LENGTH) == 0) {
- return i;
+ index = mEntries.length();
+ while (index > 0) {
+ --index;
+ if (memcmp(mEntries[index]->mIDHash, idHash, SHA384_LENGTH) == 0) {
+ return true;
}
}
- return -1;
+ return false;
}
void
OCSPCache::LogWithCerts(const char* aMessage, const CERTCertificate* aCert,
const CERTCertificate* aIssuerCert)
{
#ifdef PR_LOGGING
if (PR_LOG_TEST(gCertVerifierLog, PR_LOG_DEBUG)) {
@@ -171,18 +177,18 @@ OCSPCache::Get(const CERTCertificate* aC
PRErrorCode& aErrorCode,
PRTime& aValidThrough)
{
PR_ASSERT(aCert);
PR_ASSERT(aIssuerCert);
MutexAutoLock lock(mMutex);
- int32_t index = FindInternal(aCert, aIssuerCert, lock);
- if (index < 0) {
+ size_t index;
+ if (!FindInternal(aCert, aIssuerCert, index, lock)) {
LogWithCerts("OCSPCache::Get(%s, %s) not in cache", aCert, aIssuerCert);
return false;
}
LogWithCerts("OCSPCache::Get(%s, %s) in cache", aCert, aIssuerCert);
aErrorCode = mEntries[index]->mErrorCode;
aValidThrough = mEntries[index]->mValidThrough;
MakeMostRecentlyUsed(index, lock);
return true;
@@ -195,19 +201,18 @@ OCSPCache::Put(const CERTCertificate* aC
PRTime aThisUpdate,
PRTime aValidThrough)
{
PR_ASSERT(aCert);
PR_ASSERT(aIssuerCert);
MutexAutoLock lock(mMutex);
- int32_t index = FindInternal(aCert, aIssuerCert, lock);
-
- if (index >= 0) {
+ size_t index;
+ if (FindInternal(aCert, aIssuerCert, index, lock)) {
// Never replace an entry indicating a revoked certificate.
if (mEntries[index]->mErrorCode == SEC_ERROR_REVOKED_CERTIFICATE) {
LogWithCerts("OCSPCache::Put(%s, %s) already in cache as revoked - "
"not replacing", aCert, aIssuerCert);
MakeMostRecentlyUsed(index, lock);
return SECSuccess;
}
--- a/security/certverifier/OCSPCache.h
+++ b/security/certverifier/OCSPCache.h
@@ -87,19 +87,20 @@ private:
PRTime mThisUpdate;
PRTime mValidThrough;
// The SHA-384 hash of the concatenation of the DER encodings of the
// issuer name and issuer key, followed by the serial number.
// See the documentation for CertIDHash in OCSPCache.cpp.
SHA384Buffer mIDHash;
};
- int32_t FindInternal(const CERTCertificate* aCert,
- const CERTCertificate* aIssuerCert,
- const MutexAutoLock& aProofOfLock);
+ bool FindInternal(const CERTCertificate* aCert,
+ const CERTCertificate* aIssuerCert,
+ /*out*/ size_t& index,
+ const MutexAutoLock& aProofOfLock);
void MakeMostRecentlyUsed(size_t aIndex, const MutexAutoLock& aProofOfLock);
void LogWithCerts(const char* aMessage, const CERTCertificate* aCert,
const CERTCertificate* aIssuerCert);
Mutex mMutex;
static const size_t MaxEntries = 1024;
// Sorted with the most-recently-used entry at the end.
// Using 256 here reserves as much possible inline storage as the vector
--- a/security/pkix/include/pkix/bind.h
+++ b/security/pkix/include/pkix/bind.h
@@ -19,36 +19,45 @@
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// Work around missing std::bind, std::ref, std::cref in older compilers. This
// implementation isn't intended to be complete; rather, it is the minimal
-// implementation needed to make our use of std::bind work.
+// implementation needed to make our use of std::bind work for compilers that
+// lack both C++11 and TR1 support for these features. We cannot even assume
+// that rvalue references work, which means we don't get perfect forwarding
+// and thus we basically have to define a new overload for every distinct call
+// signature.
+//
+// A positive side-effect of this code is improved debugging usability; it is
+// much more convenient to step through code that uses this polyfill than it is
+// to step through the many nested layers of a real std::bind implementation.
+//
+// Build with MOZILLA_PKIX_USE_REAL_FUNCTIONAL defined in order to use the
+// compiler's definitions of these functions. This is helpful in order to
+// ensure that the calling code is actually compatible with the real std::bind
+// and friends.
#ifndef mozilla_pkix__bind_h
#define mozilla_pkix__bind_h
-#ifdef _MSC_VER
-#pragma warning(disable:4275) //Suppress spurious MSVC warning
-#endif
+#ifdef MOZILLA_PKIX_USE_REAL_FUNCTIONAL
#include <functional>
-#ifdef _MSC_VER
-#pragma warning(default:4275)
#endif
namespace mozilla { namespace pkix {
-#ifdef _MSC_VER
+#ifdef MOZILLA_PKIX_USE_REAL_FUNCTIONAL
using std::bind;
+using std::cref;
using std::ref;
-using std::cref;
using std::placeholders::_1;
#else
class Placeholder1 { };
extern Placeholder1 _1;
template <typename V> V& ref(V& v) { return v; }
@@ -61,44 +70,95 @@ class Bind1
{
public:
typedef R (*F)(P1 &, B1 &);
Bind1(F f, B1 & b1) : f(f), b1(b1) { }
R operator()(P1 & p1) const { return f(p1, b1); }
private:
const F f;
B1& b1;
+ void operator=(const Bind1&) /*= delete*/;
};
template <typename R, typename P1, typename B1, typename B2>
class Bind2
{
public:
typedef R (*F)(P1&, B1&, B2&);
Bind2(F f, B1& b1, B2& b2) : f(f), b1(b1), b2(b2) { }
R operator()(P1& p1) const { return f(p1, b1, b2); }
private:
const F f;
B1& b1;
B2& b2;
+ void operator=(const Bind2&) /*= delete*/;
+};
+
+template <typename R, typename P1, typename B1, typename B2, typename B3>
+class Bind3
+{
+public:
+ typedef R (*F)(P1&, B1, B2&, B3&);
+ Bind3(F f, B1& b1, B2& b2, B3& b3) : f(f), b1(b1), b2(b2), b3(b3) { }
+ R operator()(P1& p1) const { return f(p1, b1, b2, b3); }
+private:
+ const F f;
+ B1& b1;
+ B2& b2;
+ B3& b3;
+ void operator=(const Bind3&) /*= delete*/;
+};
+
+template <typename R, typename P1, typename B1, typename B2, typename B3,
+ typename B4>
+class Bind4
+{
+public:
+ typedef R (*F)(P1&, B1, B2, B3&, B4&);
+ Bind4(F f, B1& b1, B2& b2, B3& b3, B4& b4)
+ : f(f), b1(b1), b2(b2), b3(b3), b4(b4) { }
+ R operator()(P1& p1) const { return f(p1, b1, b2, b3, b4); }
+private:
+ const F f;
+ B1& b1;
+ B2& b2;
+ B3& b3;
+ B4& b4;
+ void operator=(const Bind4&) /*= delete*/;
};
} // namespace internal
template <typename R, typename P1, typename B1>
inline internal::Bind1<R, P1, B1>
bind(R (*f)(P1&, B1&), Placeholder1&, B1& b1)
{
return internal::Bind1<R, P1, B1>(f, b1);
}
template <typename R, typename P1, typename B1, typename B2>
inline internal::Bind2<R, P1, B1, B2>
-bind(R (*f)(P1&, B1&, B2&), Placeholder1 &, B1 & b1, B2 & b2)
+bind(R (*f)(P1&, B1&, B2&), Placeholder1&, B1& b1, B2& b2)
{
return internal::Bind2<R, P1, B1, B2>(f, b1, b2);
}
-#endif // _MSC_VER
+template <typename R, typename P1, typename B1, typename B2, typename B3>
+inline internal::Bind3<R, P1, const B1, const B2, B3>
+bind(R (*f)(P1&, B1, const B2&, B3&), Placeholder1&, B1& b1, const B2& b2, B3& b3)
+{
+ return internal::Bind3<R, P1, const B1, const B2, B3>(f, b1, b2, b3);
+}
+
+template <typename R, typename P1, typename B1, typename B2, typename B3,
+ typename B4>
+inline internal::Bind4<R, P1, const B1, const B2, B3, B4>
+bind(R (*f)(P1&, B1, B2, B3&, B4&), Placeholder1&, const B1& b1, const B2& b2,
+ B3& b3, B4& b4)
+{
+ return internal::Bind4<R, P1, const B1, const B2, B3, B4>(f, b1, b2, b3, b4);
+}
+
+#endif
} } // namespace mozilla::pkix
#endif // mozilla_pkix__bind_h
--- a/security/pkix/include/pkix/pkix.h
+++ b/security/pkix/include/pkix/pkix.h
@@ -89,18 +89,18 @@ namespace mozilla { namespace pkix {
// distrust.
// TODO(bug 968451): Document more of these.
SECStatus BuildCertChain(TrustDomain& trustDomain,
CERTCertificate* cert,
PRTime time,
EndEntityOrCA endEntityOrCA,
/*optional*/ KeyUsages requiredKeyUsagesIfPresent,
- /*optional*/ SECOidTag requiredEKUIfPresent,
- /*optional*/ SECOidTag requiredPolicy,
+ KeyPurposeId requiredEKUIfPresent,
+ const CertPolicyId& requiredPolicy,
/*optional*/ const SECItem* stapledOCSPResponse,
/*out*/ ScopedCERTCertList& results);
// Verify the given signed data using the public key of the given certificate.
// (EC)DSA parameter inheritance is not supported.
SECStatus VerifySignedData(const CERTSignedData* sd,
const CERTCertificate* cert,
void* pkcs11PinArg);
--- a/security/pkix/include/pkix/pkixtypes.h
+++ b/security/pkix/include/pkix/pkixtypes.h
@@ -25,30 +25,50 @@
#ifndef mozilla_pkix__pkixtypes_h
#define mozilla_pkix__pkixtypes_h
#include "pkix/enumclass.h"
#include "pkix/ScopedPtr.h"
#include "plarena.h"
#include "cert.h"
#include "keyhi.h"
+#include "stdint.h"
namespace mozilla { namespace pkix {
typedef ScopedPtr<PLArenaPool, PL_FreeArenaPool> ScopedPLArenaPool;
typedef ScopedPtr<CERTCertificate, CERT_DestroyCertificate>
ScopedCERTCertificate;
typedef ScopedPtr<CERTCertList, CERT_DestroyCertList> ScopedCERTCertList;
typedef ScopedPtr<SECKEYPublicKey, SECKEY_DestroyPublicKey>
ScopedSECKEYPublicKey;
+MOZILLA_PKIX_ENUM_CLASS EndEntityOrCA { MustBeEndEntity = 0, MustBeCA = 1 };
+
typedef unsigned int KeyUsages;
-MOZILLA_PKIX_ENUM_CLASS EndEntityOrCA { MustBeEndEntity = 0, MustBeCA = 1 };
+MOZILLA_PKIX_ENUM_CLASS KeyPurposeId {
+ anyExtendedKeyUsage = 0,
+ id_kp_serverAuth = 1, // id-kp-serverAuth
+ id_kp_clientAuth = 2, // id-kp-clientAuth
+ id_kp_codeSigning = 3, // id-kp-codeSigning
+ id_kp_emailProtection = 4, // id-kp-emailProtection
+ id_kp_OCSPSigning = 9, // id-kp-OCSPSigning
+};
+
+struct CertPolicyId {
+ uint16_t numBytes;
+ static const uint16_t MAX_BYTES = 24;
+ uint8_t bytes[MAX_BYTES];
+
+ bool IsAnyPolicy() const;
+
+ static const CertPolicyId anyPolicy;
+};
MOZILLA_PKIX_ENUM_CLASS TrustLevel {
TrustAnchor = 1, // certificate is a trusted root CA certificate or
// equivalent *for the given policy*.
ActivelyDistrusted = 2, // certificate is known to be bad
InheritsTrust = 3 // certificate must chain to a trust anchor
};
@@ -60,26 +80,26 @@ class TrustDomain
{
public:
virtual ~TrustDomain() { }
// Determine the level of trust in the given certificate for the given role.
// This will be called for every certificate encountered during path
// building.
//
- // When policy == SEC_OID_X509_ANY_POLICY, then no policy-related checking
- // should be done. When policy != SEC_OID_X509_ANY_POLICY, then GetCertTrust
- // MUST NOT return with *trustLevel == TrustAnchor unless the given cert is
- // considered a trust anchor *for that policy*. In particular, if the user
- // has marked an intermediate certificate as trusted, but that intermediate
- // isn't in the list of EV roots, then GetCertTrust must result in
+ // When policy.IsAnyPolicy(), then no policy-related checking should be done.
+ // When !policy.IsAnyPolicy(), then GetCertTrust MUST NOT return with
+ // *trustLevel == TrustAnchor unless the given cert is considered a trust
+ // anchor *for that policy*. In particular, if the user has marked an
+ // intermediate certificate as trusted, but that intermediate isn't in the
+ // list of EV roots, then GetCertTrust must result in
// *trustLevel == InheritsTrust instead of *trustLevel == TrustAnchor
// (assuming the candidate cert is not actively distrusted).
virtual SECStatus GetCertTrust(EndEntityOrCA endEntityOrCA,
- SECOidTag policy,
+ const CertPolicyId& policy,
const CERTCertificate* candidateCert,
/*out*/ TrustLevel* trustLevel) = 0;
// Find all certificates (intermediate and/or root) in the certificate
// database that have a subject name matching |encodedIssuerName| at
// the given time. Certificates where the given time is not within the
// certificate's validity period may be excluded. On input, |results|
// will be null on input. If no potential issuers are found, then this
--- a/security/pkix/lib/pkixbind.cpp
+++ b/security/pkix/lib/pkixbind.cpp
@@ -17,19 +17,19 @@
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-#ifndef _MSC_VER
+#ifndef MOZILLA_PKIX_USE_REAL_FUNCTIONAL
#include "pkix/bind.h"
namespace mozilla { namespace pkix {
Placeholder1 _1;
} } // namespace mozilla::pkix
-#endif // _MSC_VER
+#endif
--- a/security/pkix/lib/pkixbuild.cpp
+++ b/security/pkix/lib/pkixbuild.cpp
@@ -109,30 +109,30 @@ BackCert::Init()
return Success;
}
static Result BuildForward(TrustDomain& trustDomain,
BackCert& subject,
PRTime time,
EndEntityOrCA endEntityOrCA,
KeyUsages requiredKeyUsagesIfPresent,
- SECOidTag requiredEKUIfPresent,
- SECOidTag requiredPolicy,
+ KeyPurposeId requiredEKUIfPresent,
+ const CertPolicyId& requiredPolicy,
/*optional*/ const SECItem* stapledOCSPResponse,
unsigned int subCACount,
/*out*/ ScopedCERTCertList& results);
// The code that executes in the inner loop of BuildForward
static Result
BuildForwardInner(TrustDomain& trustDomain,
BackCert& subject,
PRTime time,
EndEntityOrCA endEntityOrCA,
- SECOidTag requiredEKUIfPresent,
- SECOidTag requiredPolicy,
+ KeyPurposeId requiredEKUIfPresent,
+ const CertPolicyId& requiredPolicy,
CERTCertificate* potentialIssuerCertToDup,
unsigned int subCACount,
ScopedCERTCertList& results)
{
PORT_Assert(potentialIssuerCertToDup);
BackCert potentialIssuer(potentialIssuerCertToDup, &subject,
BackCert::IncludeCN::No);
@@ -191,18 +191,18 @@ BuildForwardInner(TrustDomain& trustDoma
// chain has multiple problems. See the error ranking documentation in
// pkix/pkix.h.
static Result
BuildForward(TrustDomain& trustDomain,
BackCert& subject,
PRTime time,
EndEntityOrCA endEntityOrCA,
KeyUsages requiredKeyUsagesIfPresent,
- SECOidTag requiredEKUIfPresent,
- SECOidTag requiredPolicy,
+ KeyPurposeId requiredEKUIfPresent,
+ const CertPolicyId& requiredPolicy,
/*optional*/ const SECItem* stapledOCSPResponse,
unsigned int subCACount,
/*out*/ ScopedCERTCertList& results)
{
// Avoid stack overflows and poor performance by limiting cert length.
// XXX: 6 is not enough for chains.sh anypolicywithlevel.cfg tests
static const size_t MAX_DEPTH = 8;
if (subCACount >= MAX_DEPTH - 1) {
@@ -331,18 +331,18 @@ BuildForward(TrustDomain& trustDomain,
}
SECStatus
BuildCertChain(TrustDomain& trustDomain,
CERTCertificate* certToDup,
PRTime time,
EndEntityOrCA endEntityOrCA,
/*optional*/ KeyUsages requiredKeyUsagesIfPresent,
- /*optional*/ SECOidTag requiredEKUIfPresent,
- /*optional*/ SECOidTag requiredPolicy,
+ /*optional*/ KeyPurposeId requiredEKUIfPresent,
+ const CertPolicyId& requiredPolicy,
/*optional*/ const SECItem* stapledOCSPResponse,
/*out*/ ScopedCERTCertList& results)
{
PORT_Assert(certToDup);
if (!certToDup) {
PR_SetError(SEC_ERROR_INVALID_ARGS, 0);
return SECFailure;
@@ -350,17 +350,17 @@ BuildCertChain(TrustDomain& trustDomain,
// The only non-const operation on the cert we are allowed to do is
// CERT_DupCertificate.
// XXX: Support the legacy use of the subject CN field for indicating the
// domain name the certificate is valid for.
BackCert::IncludeCN includeCN
= endEntityOrCA == EndEntityOrCA::MustBeEndEntity &&
- requiredEKUIfPresent == SEC_OID_EXT_KEY_USAGE_SERVER_AUTH
+ requiredEKUIfPresent == KeyPurposeId::id_kp_serverAuth
? BackCert::IncludeCN::Yes
: BackCert::IncludeCN::No;
BackCert cert(certToDup, nullptr, includeCN);
Result rv = cert.Init();
if (rv != Success) {
return SECFailure;
}
--- a/security/pkix/lib/pkixcheck.cpp
+++ b/security/pkix/lib/pkixcheck.cpp
@@ -19,21 +19,21 @@
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <limits>
+#include "pkix/bind.h"
#include "pkix/pkix.h"
#include "pkixcheck.h"
#include "pkixder.h"
#include "pkixutil.h"
-#include "secder.h"
namespace mozilla { namespace pkix {
Result
CheckTimes(const CERTCertificate* cert, PRTime time)
{
PR_ASSERT(cert);
@@ -102,180 +102,202 @@ CheckKeyUsage(EndEntityOrCA endEntityOrC
// return Fail(RecoverableError, SEC_ERROR_INADEQUATE_CERT_TYPE);
//}
}
return Success;
}
// RFC5820 4.2.1.4. Certificate Policies
-//
+
// "The user-initial-policy-set contains the special value any-policy if the
// user is not concerned about certificate policy."
-Result
-CheckCertificatePolicies(BackCert& cert, EndEntityOrCA endEntityOrCA,
- bool isTrustAnchor, SECOidTag requiredPolicy)
+//
+// id-ce OBJECT IDENTIFIER ::= {joint-iso-ccitt(2) ds(5) 29}
+// id-ce-certificatePolicies OBJECT IDENTIFIER ::= { id-ce 32 }
+// anyPolicy OBJECT IDENTIFIER ::= { id-ce-certificatePolicies 0 }
+
+/*static*/ const CertPolicyId CertPolicyId::anyPolicy = {
+ 4, { (40*2)+5, 29, 32, 0 }
+};
+
+bool CertPolicyId::IsAnyPolicy() const
{
- if (requiredPolicy == SEC_OID_X509_ANY_POLICY) {
- return Success;
+ return this == &anyPolicy ||
+ (numBytes == anyPolicy.numBytes &&
+ !memcmp(bytes, anyPolicy.bytes, anyPolicy.numBytes));
+}
+
+// PolicyInformation ::= SEQUENCE {
+// policyIdentifier CertPolicyId,
+// policyQualifiers SEQUENCE SIZE (1..MAX) OF
+// PolicyQualifierInfo OPTIONAL }
+inline der::Result
+CheckPolicyInformation(der::Input& input, EndEntityOrCA endEntityOrCA,
+ const CertPolicyId& requiredPolicy,
+ /*in/out*/ bool& found)
+{
+ if (input.MatchTLV(der::OIDTag, requiredPolicy.numBytes,
+ requiredPolicy.bytes)) {
+ found = true;
+ } else if (endEntityOrCA == EndEntityOrCA::MustBeCA &&
+ input.MatchTLV(der::OIDTag, CertPolicyId::anyPolicy.numBytes,
+ CertPolicyId::anyPolicy.bytes)) {
+ found = true;
}
- // It is likely some callers will pass SEC_OID_UNKNOWN when they don't care,
- // instead of passing SEC_OID_X509_ANY_POLICY. Help them out by failing hard.
- if (requiredPolicy == SEC_OID_UNKNOWN) {
- PR_SetError(SEC_ERROR_INVALID_ARGS, 0);
- return FatalError;
+ // RFC 5280 Section 4.2.1.4 says "Optional qualifiers, which MAY be present,
+ // are not expected to change the definition of the policy." Also, it seems
+ // that Section 6, which defines validation, does not require any matching of
+ // qualifiers. Thus, doing anything with the policy qualifiers would be a
+ // waste of time and a source of potential incompatibilities, so we just
+ // ignore them.
+
+ // Skip unmatched OID and/or policyQualifiers
+ input.SkipToEnd();
+
+ return der::Success;
+}
+
+// certificatePolicies ::= SEQUENCE SIZE (1..MAX) OF PolicyInformation
+Result
+CheckCertificatePolicies(EndEntityOrCA endEntityOrCA,
+ const SECItem* encodedCertificatePolicies,
+ const SECItem* encodedInhibitAnyPolicy,
+ TrustLevel trustLevel,
+ const CertPolicyId& requiredPolicy)
+{
+ if (requiredPolicy.numBytes == 0 ||
+ requiredPolicy.numBytes > sizeof requiredPolicy.bytes) {
+ return Fail(FatalError, SEC_ERROR_INVALID_ARGS);
+ }
+
+ // Ignore all policy information if the caller indicates any policy is
+ // acceptable. See TrustDomain::GetCertTrust and the policy part of
+ // BuildCertChain's documentation.
+ if (requiredPolicy.IsAnyPolicy()) {
+ return Success;
}
// Bug 989051. Until we handle inhibitAnyPolicy we will fail close when
// inhibitAnyPolicy extension is present and we need to evaluate certificate
// policies.
- if (cert.encodedInhibitAnyPolicy) {
+ if (encodedInhibitAnyPolicy) {
return Fail(RecoverableError, SEC_ERROR_POLICY_VALIDATION_FAILED);
}
// The root CA certificate may omit the policies that it has been
// trusted for, so we cannot require the policies to be present in those
// certificates. Instead, the determination of which roots are trusted for
// which policies is made by the TrustDomain's GetCertTrust method.
- if (isTrustAnchor && endEntityOrCA == EndEntityOrCA::MustBeCA) {
+ if (trustLevel == TrustLevel::TrustAnchor &&
+ endEntityOrCA == EndEntityOrCA::MustBeCA) {
return Success;
}
- if (!cert.encodedCertificatePolicies) {
+ if (!encodedCertificatePolicies) {
return Fail(RecoverableError, SEC_ERROR_POLICY_VALIDATION_FAILED);
}
- ScopedPtr<CERTCertificatePolicies, CERT_DestroyCertificatePoliciesExtension>
- policies(CERT_DecodeCertificatePoliciesExtension(
- cert.encodedCertificatePolicies));
- if (!policies) {
- return MapSECStatus(SECFailure);
+ bool found = false;
+
+ der::Input input;
+ if (input.Init(encodedCertificatePolicies->data,
+ encodedCertificatePolicies->len) != der::Success) {
+ return Fail(RecoverableError, SEC_ERROR_POLICY_VALIDATION_FAILED);
+ }
+ if (der::NestedOf(input, der::SEQUENCE, der::SEQUENCE, der::EmptyAllowed::No,
+ bind(CheckPolicyInformation, _1, endEntityOrCA,
+ requiredPolicy, ref(found))) != der::Success) {
+ return Fail(RecoverableError, SEC_ERROR_POLICY_VALIDATION_FAILED);
+ }
+ if (der::End(input) != der::Success) {
+ return Fail(RecoverableError, SEC_ERROR_POLICY_VALIDATION_FAILED);
+ }
+ if (!found) {
+ return Fail(RecoverableError, SEC_ERROR_POLICY_VALIDATION_FAILED);
}
- for (const CERTPolicyInfo* const* policyInfos = policies->policyInfos;
- *policyInfos; ++policyInfos) {
- if ((*policyInfos)->oid == requiredPolicy) {
- return Success;
- }
- // Intermediate certs are allowed to have the anyPolicy OID
- if (endEntityOrCA == EndEntityOrCA::MustBeCA &&
- (*policyInfos)->oid == SEC_OID_X509_ANY_POLICY) {
- return Success;
- }
- }
+ return Success;
+}
- return Fail(RecoverableError, SEC_ERROR_POLICY_VALIDATION_FAILED);
-}
+static const long UNLIMITED_PATH_LEN = -1; // must be less than zero
// BasicConstraints ::= SEQUENCE {
// cA BOOLEAN DEFAULT FALSE,
// pathLenConstraint INTEGER (0..MAX) OPTIONAL }
-der::Result
-DecodeBasicConstraints(const SECItem* encodedBasicConstraints,
- CERTBasicConstraints& basicConstraints)
+static der::Result
+DecodeBasicConstraints(der::Input& input, /*out*/ bool& isCA,
+ /*out*/ long& pathLenConstraint)
{
- PR_ASSERT(encodedBasicConstraints);
- if (!encodedBasicConstraints) {
- return der::Fail(SEC_ERROR_INVALID_ARGS);
- }
-
- basicConstraints.isCA = false;
- basicConstraints.pathLenConstraint = 0;
-
- der::Input input;
- if (input.Init(encodedBasicConstraints->data, encodedBasicConstraints->len)
- != der::Success) {
- return der::Fail(SEC_ERROR_EXTENSION_VALUE_INVALID);
- }
-
- if (der::ExpectTagAndIgnoreLength(input, der::SEQUENCE) != der::Success) {
- return der::Fail(SEC_ERROR_EXTENSION_VALUE_INVALID);
- }
-
- bool isCA = false;
// TODO(bug 989518): cA is by default false. According to DER, default
// values must not be explicitly encoded in a SEQUENCE. So, if this
// value is present and false, it is an encoding error. However, Go Daddy
// has issued many certificates with this improper encoding, so we can't
// enforce this yet (hence passing true for allowInvalidExplicitEncoding
// to der::OptionalBoolean).
if (der::OptionalBoolean(input, true, isCA) != der::Success) {
return der::Fail(SEC_ERROR_EXTENSION_VALUE_INVALID);
}
- basicConstraints.isCA = isCA;
- if (input.Peek(der::INTEGER)) {
- SECItem pathLenConstraintEncoded;
- if (der::Integer(input, pathLenConstraintEncoded) != der::Success) {
- return der::Fail(SEC_ERROR_EXTENSION_VALUE_INVALID);
- }
- long pathLenConstraint = DER_GetInteger(&pathLenConstraintEncoded);
- if (pathLenConstraint >= std::numeric_limits<int>::max() ||
- pathLenConstraint < 0) {
- return der::Fail(SEC_ERROR_EXTENSION_VALUE_INVALID);
- }
- basicConstraints.pathLenConstraint = static_cast<int>(pathLenConstraint);
- // TODO(bug 985025): If isCA is false, pathLenConstraint MUST NOT
- // be included (as per RFC 5280 section 4.2.1.9), but for compatibility
- // reasons, we don't check this for now.
- } else if (basicConstraints.isCA) {
- // If this is a CA but the path length is omitted, it is unlimited.
- basicConstraints.pathLenConstraint = CERT_UNLIMITED_PATH_CONSTRAINT;
+ // TODO(bug 985025): If isCA is false, pathLenConstraint MUST NOT
+ // be included (as per RFC 5280 section 4.2.1.9), but for compatibility
+ // reasons, we don't check this for now.
+ if (OptionalInteger(input, UNLIMITED_PATH_LEN, pathLenConstraint)
+ != der::Success) {
+ return der::Fail(SEC_ERROR_EXTENSION_VALUE_INVALID);
}
- if (der::End(input) != der::Success) {
- return der::Fail(SEC_ERROR_EXTENSION_VALUE_INVALID);
- }
return der::Success;
}
// RFC5280 4.2.1.9. Basic Constraints (id-ce-basicConstraints)
Result
-CheckBasicConstraints(const BackCert& cert,
- EndEntityOrCA endEntityOrCA,
- bool isTrustAnchor,
+CheckBasicConstraints(EndEntityOrCA endEntityOrCA,
+ const SECItem* encodedBasicConstraints,
+ const der::Version version, TrustLevel trustLevel,
unsigned int subCACount)
{
- CERTBasicConstraints basicConstraints;
- if (cert.encodedBasicConstraints) {
- if (DecodeBasicConstraints(cert.encodedBasicConstraints,
- basicConstraints) != der::Success) {
- return RecoverableError;
+ bool isCA = false;
+ long pathLenConstraint = UNLIMITED_PATH_LEN;
+
+ if (encodedBasicConstraints) {
+ der::Input input;
+ if (input.Init(encodedBasicConstraints->data,
+ encodedBasicConstraints->len) != der::Success) {
+ return Fail(RecoverableError, SEC_ERROR_EXTENSION_VALUE_INVALID);
+ }
+ if (der::Nested(input, der::SEQUENCE,
+ bind(DecodeBasicConstraints, _1, ref(isCA),
+ ref(pathLenConstraint))) != der::Success) {
+ return Fail(RecoverableError, SEC_ERROR_EXTENSION_VALUE_INVALID);
+ }
+ if (der::End(input) != der::Success) {
+ return Fail(RecoverableError, SEC_ERROR_EXTENSION_VALUE_INVALID);
}
} else {
- // Synthesize a non-CA basic constraints by default
- basicConstraints.isCA = false;
- basicConstraints.pathLenConstraint = 0;
-
// "If the basic constraints extension is not present in a version 3
// certificate, or the extension is present but the cA boolean is not
// asserted, then the certified public key MUST NOT be used to verify
// certificate signatures."
//
// For compatibility, we must accept v1 trust anchors without basic
// constraints as CAs.
//
// TODO: add check for self-signedness?
- if (endEntityOrCA == EndEntityOrCA::MustBeCA && isTrustAnchor) {
- const CERTCertificate* nssCert = cert.GetNSSCert();
- // We only allow trust anchor CA certs to omit the
- // basicConstraints extension if they are v1. v1 is encoded
- // implicitly.
- if (!nssCert->version.data && !nssCert->version.len) {
- basicConstraints.isCA = true;
- basicConstraints.pathLenConstraint = CERT_UNLIMITED_PATH_CONSTRAINT;
- }
+ if (endEntityOrCA == EndEntityOrCA::MustBeCA &&
+ trustLevel == TrustLevel::TrustAnchor && version == der::Version::v1) {
+ isCA = true;
}
}
if (endEntityOrCA == EndEntityOrCA::MustBeEndEntity) {
// CA certificates are not trusted as EE certs.
- if (basicConstraints.isCA) {
+ if (isCA) {
// XXX: We use SEC_ERROR_CA_CERT_INVALID here so we can distinguish
// this error from other errors, given that NSS does not have a "CA cert
// used as end-entity" error code since it doesn't have such a
// prohibition. We should add such an error code and stop abusing
// SEC_ERROR_CA_CERT_INVALID this way.
//
// Note, in particular, that this check prevents a delegated OCSP
// response signing certificate with the CA bit from successfully
@@ -285,25 +307,23 @@ CheckBasicConstraints(const BackCert& ce
}
return Success;
}
PORT_Assert(endEntityOrCA == EndEntityOrCA::MustBeCA);
// End-entity certificates are not allowed to act as CA certs.
- if (!basicConstraints.isCA) {
+ if (!isCA) {
return Fail(RecoverableError, SEC_ERROR_CA_CERT_INVALID);
}
- if (basicConstraints.pathLenConstraint >= 0) {
- if (subCACount >
- static_cast<unsigned int>(basicConstraints.pathLenConstraint)) {
- return Fail(RecoverableError, SEC_ERROR_PATH_LEN_CONSTRAINT_INVALID);
- }
+ if (pathLenConstraint >= 0 &&
+ static_cast<long>(subCACount) > pathLenConstraint) {
+ return Fail(RecoverableError, SEC_ERROR_PATH_LEN_CONSTRAINT_INVALID);
}
return Success;
}
Result
BackCert::GetConstrainedNames(/*out*/ const CERTGeneralName** result)
{
@@ -363,61 +383,130 @@ CheckNameConstraints(BackCert& cert)
currentName = CERT_GetNextGeneralName(currentName);
} while (currentName != names);
}
return Success;
}
// 4.2.1.12. Extended Key Usage (id-ce-extKeyUsage)
-// 4.2.1.12. Extended Key Usage (id-ce-extKeyUsage)
-Result
-CheckExtendedKeyUsage(EndEntityOrCA endEntityOrCA, const SECItem* encodedEKUs,
- SECOidTag requiredEKU)
+
+static der::Result
+MatchEKU(der::Input& value, KeyPurposeId requiredEKU,
+ EndEntityOrCA endEntityOrCA, /*in/out*/ bool& found,
+ /*in/out*/ bool& foundOCSPSigning)
{
- // TODO: Either do not allow anyExtendedKeyUsage to be passed as requiredEKU,
- // or require that callers pass anyExtendedKeyUsage instead of
- // SEC_OID_UNKNWON and disallow SEC_OID_UNKNWON.
+ // See Section 5.9 of "A Layman's Guide to a Subset of ASN.1, BER, and DER"
+ // for a description of ASN.1 DER encoding of OIDs.
+
+ // id-pkix OBJECT IDENTIFIER ::=
+ // { iso(1) identified-organization(3) dod(6) internet(1)
+ // security(5) mechanisms(5) pkix(7) }
+ // id-kp OBJECT IDENTIFIER ::= { id-pkix 3 }
+ // id-kp-serverAuth OBJECT IDENTIFIER ::= { id-kp 1 }
+ // id-kp-clientAuth OBJECT IDENTIFIER ::= { id-kp 2 }
+ // id-kp-codeSigning OBJECT IDENTIFIER ::= { id-kp 3 }
+ // id-kp-emailProtection OBJECT IDENTIFIER ::= { id-kp 4 }
+ // id-kp-OCSPSigning OBJECT IDENTIFIER ::= { id-kp 9 }
+ static const uint8_t server[] = { (40*1)+3, 6, 1, 5, 5, 7, 3, 1 };
+ static const uint8_t client[] = { (40*1)+3, 6, 1, 5, 5, 7, 3, 2 };
+ static const uint8_t code [] = { (40*1)+3, 6, 1, 5, 5, 7, 3, 3 };
+ static const uint8_t email [] = { (40*1)+3, 6, 1, 5, 5, 7, 3, 4 };
+ static const uint8_t ocsp [] = { (40*1)+3, 6, 1, 5, 5, 7, 3, 9 };
+
+ // id-Netscape OBJECT IDENTIFIER ::= { 2 16 840 1 113730 }
+ // id-Netscape-policy OBJECT IDENTIFIER ::= { id-Netscape 4 }
+ // id-Netscape-stepUp OBJECT IDENTIFIER ::= { id-Netscape-policy 1 }
+ static const uint8_t serverStepUp[] =
+ { (40*2)+16, 128+6,72, 1, 128+6,128+120,66, 4, 1 };
+
+ bool match = false;
+
+ if (!found) {
+ switch (requiredEKU) {
+ case KeyPurposeId::id_kp_serverAuth:
+ // Treat CA certs with step-up OID as also having SSL server type.
+ // Comodo has issued certificates that require this behavior that don't
+ // expire until June 2020! TODO(bug 982932): Limit this exception to
+ // old certificates.
+ match = value.MatchBytes(server) ||
+ (endEntityOrCA == EndEntityOrCA::MustBeCA &&
+ value.MatchBytes(serverStepUp));
+ break;
+
+ case KeyPurposeId::id_kp_clientAuth:
+ match = value.MatchBytes(client);
+ break;
+ case KeyPurposeId::id_kp_codeSigning:
+ match = value.MatchBytes(code);
+ break;
+
+ case KeyPurposeId::id_kp_emailProtection:
+ match = value.MatchBytes(email);
+ break;
+
+ case KeyPurposeId::id_kp_OCSPSigning:
+ match = value.MatchBytes(ocsp);
+ break;
+
+ case KeyPurposeId::anyExtendedKeyUsage:
+ PR_NOT_REACHED("anyExtendedKeyUsage should start with found==true");
+ return der::Fail(SEC_ERROR_LIBRARY_FAILURE);
+
+ default:
+ PR_NOT_REACHED("unrecognized EKU");
+ return der::Fail(SEC_ERROR_LIBRARY_FAILURE);
+ }
+ }
+
+ if (match) {
+ if (value.AtEnd()) {
+ found = true;
+ if (requiredEKU == KeyPurposeId::id_kp_OCSPSigning) {
+ foundOCSPSigning = true;
+ }
+ }
+ } else if (value.MatchBytes(ocsp) && value.AtEnd()) {
+ foundOCSPSigning = true;
+ }
+
+ value.SkipToEnd(); // ignore unmatched OIDs.
+
+ return der::Success;
+}
+
+Result
+CheckExtendedKeyUsage(EndEntityOrCA endEntityOrCA,
+ const SECItem* encodedExtendedKeyUsage,
+ KeyPurposeId requiredEKU)
+{
// XXX: We're using SEC_ERROR_INADEQUATE_CERT_TYPE here so that callers can
// distinguish EKU mismatch from KU mismatch from basic constraints mismatch.
// We should probably add a new error code that is more clear for this type
// of problem.
bool foundOCSPSigning = false;
- if (encodedEKUs) {
- ScopedPtr<CERTOidSequence, CERT_DestroyOidSequence>
- seq(CERT_DecodeOidSequence(encodedEKUs));
- if (!seq) {
- PR_SetError(SEC_ERROR_INADEQUATE_CERT_TYPE, 0);
- return RecoverableError;
- }
-
- bool found = false;
+ if (encodedExtendedKeyUsage) {
+ bool found = requiredEKU == KeyPurposeId::anyExtendedKeyUsage;
- // XXX: We allow duplicate entries.
- for (const SECItem* const* oids = seq->oids; oids && *oids; ++oids) {
- SECOidTag oidTag = SECOID_FindOIDTag(*oids);
- if (requiredEKU != SEC_OID_UNKNOWN && oidTag == requiredEKU) {
- found = true;
- } else {
- // Treat CA certs with step-up OID as also having SSL server type.
- // COMODO has issued certificates that require this behavior
- // that don't expire until June 2020!
- // TODO 982932: Limit this expection to old certificates
- if (endEntityOrCA == EndEntityOrCA::MustBeCA &&
- requiredEKU == SEC_OID_EXT_KEY_USAGE_SERVER_AUTH &&
- oidTag == SEC_OID_NS_KEY_USAGE_GOVT_APPROVED) {
- found = true;
- }
- }
- if (oidTag == SEC_OID_OCSP_RESPONDER) {
- foundOCSPSigning = true;
- }
+ der::Input input;
+ if (input.Init(encodedExtendedKeyUsage->data,
+ encodedExtendedKeyUsage->len) != der::Success) {
+ return Fail(RecoverableError, SEC_ERROR_INADEQUATE_CERT_TYPE);
+ }
+ if (der::NestedOf(input, der::SEQUENCE, der::OIDTag, der::EmptyAllowed::No,
+ bind(MatchEKU, _1, requiredEKU, endEntityOrCA,
+ ref(found), ref(foundOCSPSigning)))
+ != der::Success) {
+ return Fail(RecoverableError, SEC_ERROR_INADEQUATE_CERT_TYPE);
+ }
+ if (der::End(input) != der::Success) {
+ return Fail(RecoverableError, SEC_ERROR_INADEQUATE_CERT_TYPE);
}
// If the EKU extension was included, then the required EKU must be in the
// list.
if (!found) {
return Fail(RecoverableError, SEC_ERROR_INADEQUATE_CERT_TYPE);
}
}
@@ -432,46 +521,44 @@ CheckExtendedKeyUsage(EndEntityOrCA endE
// if not for this check.
// That said, we accept CA certificates with id-kp-OCSPSigning because
// some CAs in Mozilla's CA program have issued such intermediate
// certificates, and because some CAs have reported some Microsoft server
// software wrongly requires CA certificates to have id-kp-OCSPSigning.
// Allowing this exception does not cause any security issues because we
// require delegated OCSP response signing certificates to be end-entity
// certificates.
- if (foundOCSPSigning && requiredEKU != SEC_OID_OCSP_RESPONDER) {
- PR_SetError(SEC_ERROR_INADEQUATE_CERT_TYPE, 0);
- return RecoverableError;
+ if (foundOCSPSigning && requiredEKU != KeyPurposeId::id_kp_OCSPSigning) {
+ return Fail(RecoverableError, SEC_ERROR_INADEQUATE_CERT_TYPE);
}
// http://tools.ietf.org/html/rfc6960#section-4.2.2.2:
// "OCSP signing delegation SHALL be designated by the inclusion of
// id-kp-OCSPSigning in an extended key usage certificate extension
// included in the OCSP response signer's certificate."
//
// id-kp-OCSPSigning is the only EKU that isn't implicitly assumed when the
// EKU extension is missing from an end-entity certificate. However, any CA
// certificate can issue a delegated OCSP response signing certificate, so
// we can't require the EKU be explicitly included for CA certificates.
- if (!foundOCSPSigning && requiredEKU == SEC_OID_OCSP_RESPONDER) {
- PR_SetError(SEC_ERROR_INADEQUATE_CERT_TYPE, 0);
- return RecoverableError;
+ if (!foundOCSPSigning && requiredEKU == KeyPurposeId::id_kp_OCSPSigning) {
+ return Fail(RecoverableError, SEC_ERROR_INADEQUATE_CERT_TYPE);
}
}
return Success;
}
Result
CheckIssuerIndependentProperties(TrustDomain& trustDomain,
BackCert& cert,
PRTime time,
EndEntityOrCA endEntityOrCA,
KeyUsages requiredKeyUsagesIfPresent,
- SECOidTag requiredEKUIfPresent,
- SECOidTag requiredPolicy,
+ KeyPurposeId requiredEKUIfPresent,
+ const CertPolicyId& requiredPolicy,
unsigned int subCACount,
/*optional out*/ TrustLevel* trustLevelOut)
{
Result rv;
TrustLevel trustLevel;
rv = MapSECStatus(trustDomain.GetCertTrust(endEntityOrCA,
requiredPolicy,
@@ -488,18 +575,23 @@ CheckIssuerIndependentProperties(TrustDo
// The TrustDomain returned a trust level that we weren't expecting.
PORT_SetError(PR_INVALID_STATE_ERROR);
return FatalError;
}
if (trustLevelOut) {
*trustLevelOut = trustLevel;
}
- bool isTrustAnchor = endEntityOrCA == EndEntityOrCA::MustBeCA &&
- trustLevel == TrustLevel::TrustAnchor;
+ // XXX: Good enough for now. There could be an illegal explicit version
+ // number or one we don't support, but we can safely treat those all as v3
+ // for now since processing of v3 certificates is strictly more strict than
+ // processing of v1 certificates.
+ der::Version version = (!cert.GetNSSCert()->version.data &&
+ !cert.GetNSSCert()->version.len) ? der::Version::v1
+ : der::Version::v3;
PLArenaPool* arena = cert.GetArena();
if (!arena) {
return FatalError;
}
// 4.2.1.1. Authority Key Identifier is ignored (see bug 965136).
@@ -508,17 +600,18 @@ CheckIssuerIndependentProperties(TrustDo
// 4.2.1.3. Key Usage
rv = CheckKeyUsage(endEntityOrCA, cert.encodedKeyUsage,
requiredKeyUsagesIfPresent, arena);
if (rv != Success) {
return rv;
}
// 4.2.1.4. Certificate Policies
- rv = CheckCertificatePolicies(cert, endEntityOrCA, isTrustAnchor,
+ rv = CheckCertificatePolicies(endEntityOrCA, cert.encodedCertificatePolicies,
+ cert.encodedInhibitAnyPolicy, trustLevel,
requiredPolicy);
if (rv != Success) {
return rv;
}
// 4.2.1.5. Policy Mappings are not supported; see the documentation about
// policy enforcement in pkix.h.
@@ -526,17 +619,18 @@ CheckIssuerIndependentProperties(TrustDo
// checking and during name verification (CERT_VerifyCertName).
// 4.2.1.7. Issuer Alternative Name is not something that needs checking.
// 4.2.1.8. Subject Directory Attributes is not something that needs
// checking.
// 4.2.1.9. Basic Constraints.
- rv = CheckBasicConstraints(cert, endEntityOrCA, isTrustAnchor, subCACount);
+ rv = CheckBasicConstraints(endEntityOrCA, cert.encodedBasicConstraints,
+ version, trustLevel, subCACount);
if (rv != Success) {
return rv;
}
// 4.2.1.10. Name Constraints is dealt with in during path building.
// 4.2.1.11. Policy Constraints are implicitly supported; see the
// documentation about policy enforcement in pkix.h.
--- a/security/pkix/lib/pkixcheck.h
+++ b/security/pkix/lib/pkixcheck.h
@@ -20,29 +20,30 @@
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef mozilla_pkix__pkixcheck_h
#define mozilla_pkix__pkixcheck_h
+#include "pkix/pkixtypes.h"
#include "pkixutil.h"
#include "certt.h"
namespace mozilla { namespace pkix {
Result CheckIssuerIndependentProperties(
TrustDomain& trustDomain,
BackCert& cert,
PRTime time,
EndEntityOrCA endEntityOrCA,
KeyUsages requiredKeyUsagesIfPresent,
- SECOidTag requiredEKUIfPresent,
- SECOidTag requiredPolicy,
+ KeyPurposeId requiredEKUIfPresent,
+ const CertPolicyId& requiredPolicy,
unsigned int subCACount,
/*optional out*/ TrustLevel* trustLevel = nullptr);
Result CheckNameConstraints(BackCert& cert);
} } // namespace mozilla::pkix
#endif // mozilla_pkix__pkixcheck_h
--- a/security/pkix/lib/pkixder.cpp
+++ b/security/pkix/lib/pkixder.cpp
@@ -29,16 +29,18 @@ namespace mozilla { namespace pkix { nam
// not inline
Result
Fail(PRErrorCode errorCode)
{
PR_SetError(errorCode, 0);
return Failure;
}
+namespace internal {
+
// Too complicated to be inline
Result
ExpectTagAndGetLength(Input& input, uint8_t expectedTag, uint16_t& length)
{
PR_ASSERT((expectedTag & 0x1F) != 0x1F); // high tag number form not allowed
uint8_t tag;
if (input.Read(tag) != Success) {
@@ -81,9 +83,11 @@ ExpectTagAndGetLength(Input& input, uint
// We don't support lengths larger than 2^16 - 1.
return Fail(SEC_ERROR_BAD_DER);
}
// Ensure the input is long enough for the length it says it has.
return input.EnsureLength(length);
}
+} // namespace internal
+
} } } // namespace mozilla::pkix::der
--- a/security/pkix/lib/pkixder.h
+++ b/security/pkix/lib/pkixder.h
@@ -20,16 +20,28 @@
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef mozilla_pkix__pkixder_h
#define mozilla_pkix__pkixder_h
+// Expect* functions advance the input mark and return Success if the input
+// matches the given criteria; they return Failure with the input mark in an
+// undefined state if the input does not match the criteria.
+//
+// Match* functions advance the input mark and return true if the input matches
+// the given criteria; they return false without changing the input mark if the
+// input does not match the criteria.
+//
+// Skip* functions unconditionally advance the input mark and return Success if
+// they are able to do so; otherwise they return Failure with the input mark in
+// an undefined state.
+
#include "pkix/enumclass.h"
#include "pkix/nullptr.h"
#include "prerror.h"
#include "prlog.h"
#include "secder.h"
#include "secerr.h"
#include "secoidt.h"
@@ -133,16 +145,54 @@ public:
return Fail(SEC_ERROR_BAD_DER);
}
out = *input++;
out <<= 8u;
out |= *input++;
return Success;
}
+ template <uint16_t N>
+ bool MatchBytes(const uint8_t (&toMatch)[N])
+ {
+ if (EnsureLength(N) != Success) {
+ return false;
+ }
+ if (memcmp(input, toMatch, N)) {
+ return false;
+ }
+ input += N;
+ return true;
+ }
+
+ template <uint16_t N>
+ bool MatchTLV(uint8_t tag, uint16_t len, const uint8_t (&value)[N])
+ {
+ static_assert(N <= 127, "buffer larger than largest length supported");
+ if (len > N) {
+ PR_NOT_REACHED("overflow prevented dynamically instead of statically");
+ return false;
+ }
+ uint16_t totalLen = 2u + len;
+ if (EnsureLength(totalLen) != Success) {
+ return false;
+ }
+ if (*input != tag) {
+ return false;
+ }
+ if (*(input + 1) != len) {
+ return false;
+ }
+ if (memcmp(input + 2, value, len)) {
+ return false;
+ }
+ input += totalLen;
+ return true;
+ }
+
Result Skip(uint16_t len)
{
if (EnsureLength(len) != Success) {
return Fail(SEC_ERROR_BAD_DER);
}
input += len;
return Success;
}
@@ -234,76 +284,98 @@ ExpectTagAndLength(Input& input, uint8_t
if (tagAndLength != expectedTagAndLength) {
return Fail(SEC_ERROR_BAD_DER);
}
return Success;
}
+namespace internal {
+
Result
ExpectTagAndGetLength(Input& input, uint8_t expectedTag, uint16_t& length);
+} // namespace internal
+
inline Result
-ExpectTagAndIgnoreLength(Input& input, uint8_t expectedTag)
+ExpectTagAndSkipLength(Input& input, uint8_t expectedTag)
{
uint16_t ignored;
- return ExpectTagAndGetLength(input, expectedTag, ignored);
+ return internal::ExpectTagAndGetLength(input, expectedTag, ignored);
+}
+
+inline Result
+ExpectTagAndSkipValue(Input& input, uint8_t tag)
+{
+ uint16_t length;
+ if (internal::ExpectTagAndGetLength(input, tag, length) != Success) {
+ return Failure;
+ }
+ return input.Skip(length);
+}
+
+inline Result
+ExpectTagAndGetValue(Input& input, uint8_t tag, /*out*/ SECItem& value)
+{
+ uint16_t length;
+ if (internal::ExpectTagAndGetLength(input, tag, length) != Success) {
+ return Failure;
+ }
+ return input.Skip(length, value);
+}
+
+inline Result
+ExpectTagAndGetValue(Input& input, uint8_t tag, /*out*/ Input& value)
+{
+ uint16_t length;
+ if (internal::ExpectTagAndGetLength(input, tag, length) != Success) {
+ return Failure;
+ }
+ return input.Skip(length, value);
}
inline Result
End(Input& input)
{
if (!input.AtEnd()) {
return Fail(SEC_ERROR_BAD_DER);
}
return Success;
}
template <typename Decoder>
inline Result
Nested(Input& input, uint8_t tag, Decoder decoder)
{
- uint16_t length;
- if (ExpectTagAndGetLength(input, tag, length) != Success) {
+ Input nested;
+ if (ExpectTagAndGetValue(input, tag, nested) != Success) {
return Failure;
}
-
- Input nested;
- if (input.Skip(length, nested) != Success) {
- return Failure;
- }
-
if (decoder(nested) != Success) {
return Failure;
}
-
return End(nested);
}
template <typename Decoder>
inline Result
Nested(Input& input, uint8_t outerTag, uint8_t innerTag, Decoder decoder)
{
// XXX: This doesn't work (in VS2010):
// return Nested(input, outerTag, bind(Nested, _1, innerTag, decoder));
- uint16_t length;
- if (ExpectTagAndGetLength(input, outerTag, length) != Success) {
- return Failure;
- }
Input nestedInput;
- if (input.Skip(length, nestedInput) != Success) {
+ if (ExpectTagAndGetValue(input, outerTag, nestedInput) != Success) {
return Failure;
}
if (Nested(nestedInput, innerTag, decoder) != Success) {
return Failure;
}
-
return End(nestedInput);
}
// This can be used to decode constructs like this:
//
// ...
// foos SEQUENCE OF Foo,
// ...
@@ -319,23 +391,18 @@ Nested(Input& input, uint8_t outerTag, u
//
// In this example, Foo will get called once for each element of foos.
//
template <typename Decoder>
inline Result
NestedOf(Input& input, uint8_t outerTag, uint8_t innerTag,
EmptyAllowed mayBeEmpty, Decoder decoder)
{
- uint16_t responsesLength;
- if (ExpectTagAndGetLength(input, outerTag, responsesLength) != Success) {
- return Failure;
- }
-
Input inner;
- if (input.Skip(responsesLength, inner) != Success) {
+ if (ExpectTagAndGetValue(input, outerTag, inner) != Success) {
return Failure;
}
if (inner.AtEnd()) {
if (mayBeEmpty != EmptyAllowed::Yes) {
return Fail(SEC_ERROR_BAD_DER);
}
return Success;
@@ -345,37 +412,43 @@ NestedOf(Input& input, uint8_t outerTag,
if (Nested(inner, innerTag, decoder) != Success) {
return Failure;
}
} while (!inner.AtEnd());
return Success;
}
-inline Result
-Skip(Input& input, uint8_t tag)
+// Universal types
+
+namespace internal {
+
+// This parser will only parse values between 0..127. If this range is
+// increased then callers will need to be changed.
+template <typename T> inline Result
+IntegralValue(Input& input, uint8_t tag, T& value)
{
- uint16_t length;
- if (ExpectTagAndGetLength(input, tag, length) != Success) {
+ // Conveniently, all the Integers that we actually have to be able to parse
+ // are positive and very small. Consequently, this parser is *much* simpler
+ // than a general Integer parser would need to be.
+ if (ExpectTagAndLength(input, tag, 1) != Success) {
return Failure;
}
- return input.Skip(length);
+ uint8_t valueByte;
+ if (input.Read(valueByte) != Success) {
+ return Failure;
+ }
+ if (valueByte & 0x80) { // negative
+ return Fail(SEC_ERROR_BAD_DER);
+ }
+ value = valueByte;
+ return Success;
}
-inline Result
-Skip(Input& input, uint8_t tag, /*out*/ SECItem& value)
-{
- uint16_t length;
- if (ExpectTagAndGetLength(input, tag, length) != Success) {
- return Failure;
- }
- return input.Skip(length, value);
-}
-
-// Universal types
+} // namespace internal
inline Result
Boolean(Input& input, /*out*/ bool& value)
{
if (ExpectTagAndLength(input, BOOLEAN, 1) != Success) {
return Failure;
}
@@ -406,71 +479,71 @@ OptionalBoolean(Input& input, bool allow
}
if (!allowInvalidExplicitEncoding && !value) {
return Fail(SEC_ERROR_BAD_DER);
}
}
return Success;
}
+// This parser will only parse values between 0..127. If this range is
+// increased then callers will need to be changed.
inline Result
Enumerated(Input& input, uint8_t& value)
{
- if (ExpectTagAndLength(input, ENUMERATED | 0, 1) != Success) {
- return Failure;
- }
- return input.Read(value);
+ return internal::IntegralValue(input, ENUMERATED | 0, value);
}
inline Result
GeneralizedTime(Input& input, PRTime& time)
{
- uint16_t length;
SECItem encoded;
- if (ExpectTagAndGetLength(input, GENERALIZED_TIME, length) != Success) {
- return Failure;
- }
- if (input.Skip(length, encoded) != Success) {
+ if (ExpectTagAndGetValue(input, GENERALIZED_TIME, encoded) != Success) {
return Failure;
}
if (DER_GeneralizedTimeToTime(&time, &encoded) != SECSuccess) {
return Failure;
}
+ return Success;
+}
+// This parser will only parse values between 0..127. If this range is
+// increased then callers will need to be changed.
+inline Result
+Integer(Input& input, /*out*/ uint8_t& value)
+{
+ if (internal::IntegralValue(input, INTEGER, value) != Success) {
+ return Failure;
+ }
return Success;
}
+// This parser will only parse values between 0..127. If this range is
+// increased then callers will need to be changed. The default value must be
+// -1; defaultValue is only a parameter to make it clear in the calling code
+// what the default value is.
inline Result
-Integer(Input& input, /*out*/ SECItem& value)
+OptionalInteger(Input& input, long defaultValue, /*out*/ long& value)
{
- uint16_t length;
- if (ExpectTagAndGetLength(input, INTEGER, length) != Success) {
- return Failure;
- }
-
- if (input.Skip(length, value) != Success) {
- return Failure;
+ // If we need to support a different default value in the future, we need to
+ // test that parsedValue != defaultValue.
+ if (defaultValue != -1) {
+ return Fail(SEC_ERROR_INVALID_ARGS);
}
- if (value.len == 0) {
- return Fail(SEC_ERROR_BAD_DER);
+ if (!input.Peek(INTEGER)) {
+ value = defaultValue;
+ return Success;
}
- // Check for overly-long encodings. If the first byte is 0x00 then the high
- // bit on the second byte must be 1; otherwise the same *positive* value
- // could be encoded without the leading 0x00 byte. If the first byte is 0xFF
- // then the second byte must NOT have its high bit set; otherwise the same
- // *negative* value could be encoded without the leading 0xFF byte.
- if (value.len > 1) {
- if ((value.data[0] == 0x00 && (value.data[1] & 0x80) == 0) ||
- (value.data[0] == 0xff && (value.data[1] & 0x80) != 0)) {
- return Fail(SEC_ERROR_BAD_DER);
- }
+ uint8_t parsedValue;
+ if (Integer(input, parsedValue) != Success) {
+ return Failure;
}
-
+ value = parsedValue;
return Success;
}
inline Result
Null(Input& input)
{
return ExpectTagAndLength(input, NULLTag, 0);
}
@@ -489,42 +562,62 @@ OID(Input& input, const uint8_t (&expect
// PKI-specific types
// AlgorithmIdentifier ::= SEQUENCE {
// algorithm OBJECT IDENTIFIER,
// parameters ANY DEFINED BY algorithm OPTIONAL }
inline Result
AlgorithmIdentifier(Input& input, SECAlgorithmID& algorithmID)
{
- if (Skip(input, OIDTag, algorithmID.algorithm) != Success) {
+ if (ExpectTagAndGetValue(input, OIDTag, algorithmID.algorithm) != Success) {
return Failure;
}
algorithmID.parameters.data = nullptr;
algorithmID.parameters.len = 0;
if (input.AtEnd()) {
return Success;
}
return Null(input);
}
inline Result
-CertificateSerialNumber(Input& input, /*out*/ SECItem& serialNumber)
+CertificateSerialNumber(Input& input, /*out*/ SECItem& value)
{
// http://tools.ietf.org/html/rfc5280#section-4.1.2.2:
//
// * "The serial number MUST be a positive integer assigned by the CA to
// each certificate."
// * "Certificate users MUST be able to handle serialNumber values up to 20
// octets. Conforming CAs MUST NOT use serialNumber values longer than 20
// octets."
// * "Note: Non-conforming CAs may issue certificates with serial numbers
// that are negative or zero. Certificate users SHOULD be prepared to
// gracefully handle such certificates."
- return Integer(input, serialNumber);
+ if (ExpectTagAndGetValue(input, INTEGER, value) != Success) {
+ return Failure;
+ }
+
+ if (value.len == 0) {
+ return Fail(SEC_ERROR_BAD_DER);
+ }
+
+ // Check for overly-long encodings. If the first byte is 0x00 then the high
+ // bit on the second byte must be 1; otherwise the same *positive* value
+ // could be encoded without the leading 0x00 byte. If the first byte is 0xFF
+ // then the second byte must NOT have its high bit set; otherwise the same
+ // *negative* value could be encoded without the leading 0xFF byte.
+ if (value.len > 1) {
+ if ((value.data[0] == 0x00 && (value.data[1] & 0x80) == 0) ||
+ (value.data[0] == 0xff && (value.data[1] & 0x80) != 0)) {
+ return Fail(SEC_ERROR_BAD_DER);
+ }
+ }
+
+ return Success;
}
// x.509 and OCSP both use this same version numbering scheme, though OCSP
// only supports v1.
enum Version { v1 = 0, v2 = 1, v3 = 2 };
// X.509 Certificate and OCSP ResponseData both use this
// "[0] EXPLICIT Version DEFAULT <defaultVersion>" construct, but with
--- a/security/pkix/lib/pkixocsp.cpp
+++ b/security/pkix/lib/pkixocsp.cpp
@@ -141,18 +141,18 @@ CheckOCSPResponseSignerCert(TrustDomain&