Merge m-c to b2g-inbound.

This commit is contained in:
Ryan VanderMeulen 2014-09-19 14:17:27 -04:00
commit d861c1bfa6
135 changed files with 2970 additions and 1217 deletions

View File

@ -7,6 +7,7 @@
*/
const kWhitelist = new Set([
/defaults\/profile\/prefs.js$/,
/browser\/content\/browser\/places\/controller.js$/,
]);

View File

@ -36,6 +36,7 @@ function test() {
function continue_test() {
function test_autoFill(aTyped, aExpected, aCallback) {
info(`Testing with input: ${aTyped}`);
gURLBar.inputField.value = aTyped.substr(0, aTyped.length - 1);
gURLBar.focus();
gURLBar.selectionStart = aTyped.length - 1;
@ -49,7 +50,7 @@ function continue_test() {
}
test_autoFill("http://", "http://", function () {
test_autoFill("http://a", "http://autofilltrimurl.com/", function () {
test_autoFill("http://au", "http://autofilltrimurl.com/", function () {
test_autoFill("http://www.autofilltrimurl.com", "http://www.autofilltrimurl.com/", function () {
// Now ensure selecting from the popup correctly trims.
is(gURLBar.controller.matchCount, 1, "Found the expected number of matches");

View File

@ -13,11 +13,21 @@ var Cu = Components.utils;
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/PlacesUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "PluralForm",
"resource://gre/modules/PluralForm.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils",
"resource://gre/modules/PrivateBrowsingUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "NetUtil",
"resource://gre/modules/NetUtil.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Task",
"resource://gre/modules/Task.jsm");
// PlacesUtils exposes multiple symbols, so we can't use defineLazyModuleGetter.
Cu.import("resource://gre/modules/PlacesUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "PlacesTransactions",
"resource://gre/modules/PlacesTransactions.jsm");
#ifdef MOZ_SERVICES_CLOUDSYNC
XPCOMUtils.defineLazyModuleGetter(this, "CloudSync",
@ -31,10 +41,8 @@ XPCOMUtils.defineLazyModuleGetter(this, "Weave",
"resource://services-sync/main.js");
#endif
XPCOMUtils.defineLazyGetter(this, "PlacesUtils", function() {
Cu.import("resource://gre/modules/PlacesUtils.jsm");
return PlacesUtils;
});
// copied from utilityOverlay.js
const TAB_DROP_TYPE = "application/x-moz-tabbrowser-tab";
this.PlacesUIUtils = {
ORGANIZER_LEFTPANE_VERSION: 7,
@ -44,8 +52,6 @@ this.PlacesUIUtils = {
LOAD_IN_SIDEBAR_ANNO: "bookmarkProperties/loadInSidebar",
DESCRIPTION_ANNO: "bookmarkProperties/description",
TYPE_TAB_DROP: "application/x-moz-tabbrowser-tab",
/**
* Makes a URI from a spec, and do fixup
* @param aSpec
@ -328,7 +334,7 @@ this.PlacesUIUtils = {
default:
if (type == PlacesUtils.TYPE_X_MOZ_URL ||
type == PlacesUtils.TYPE_UNICODE ||
type == this.TYPE_TAB_DROP) {
type == TAB_DROP_TYPE) {
let title = type != PlacesUtils.TYPE_UNICODE ? data.title
: data.uri;
return new PlacesCreateBookmarkTransaction(PlacesUtils._uri(data.uri),
@ -338,6 +344,62 @@ this.PlacesUIUtils = {
return null;
},
/**
* ********* PlacesTransactions version of the function defined above ********
*
* Constructs a Places Transaction for the drop or paste of a blob of data
* into a container.
*
* @param aData
* The unwrapped data blob of dropped or pasted data.
* @param aType
* The content type of the data.
* @param aNewParentGuid
* GUID of the container the data was dropped or pasted into.
* @param aIndex
* The index within the container the item was dropped or pasted at.
* @param aCopy
* The drag action was copy, so don't move folders or links.
*
* @returns a Places Transaction that can be passed to
* PlacesTranactions.transact for performing the move/insert command.
*/
getTransactionForData: function(aData, aType, aNewParentGuid, aIndex, aCopy) {
if (this.SUPPORTED_FLAVORS.indexOf(aData.type) == -1)
throw new Error(`Unsupported '${aData.type}' data type`);
if ("itemGuid" in aData) {
if (this.PLACES_FLAVORS.indexOf(aData.type) == -1)
throw new Error (`itemGuid unexpectedly set on ${aData.type} data`);
let info = { GUID: aData.itemGuid
, newParentGUID: aNewParentGuid
, newIndex: aIndex };
if (aCopy)
return PlacesTransactions.Copy(info);
return PlacesTransactions.Move(info);
}
// Since it's cheap and harmless, we allow the paste of separators and
// bookmarks from builds that use legacy transactions (i.e. when itemGuid
// was not set on PLACES_FLAVORS data). Containers are a different story,
// and thus disallowed.
if (aData.type == PlacesUtils.TYPE_X_MOZ_PLACE_CONTAINER)
throw new Error("Can't copy a container from a legacy-transactions build");
if (aData.type == PlacesUtils.TYPE_X_MOZ_PLACE_SEPARATOR) {
return PlacesTransactions.NewSeparator({ parentGUID: aNewParentGuid
, index: aIndex });
}
let title = aData.type != PlacesUtils.TYPE_UNICODE ? aData.title
: aData.uri;
return PlacesTransactions.NewBookmark({ uri: NetUtil.newURI(aData.uri)
, title: title
, parentGUID: aNewParentGuid
, index: aIndex });
},
/**
* Shows the bookmark dialog corresponding to the specified info.
*
@ -1024,6 +1086,18 @@ this.PlacesUIUtils = {
},
};
PlacesUIUtils.PLACES_FLAVORS = [PlacesUtils.TYPE_X_MOZ_PLACE_CONTAINER,
PlacesUtils.TYPE_X_MOZ_PLACE_SEPARATOR,
PlacesUtils.TYPE_X_MOZ_PLACE];
PlacesUIUtils.URI_FLAVORS = [PlacesUtils.TYPE_X_MOZ_URL,
TAB_DROP_TYPE,
PlacesUtils.TYPE_UNICODE],
PlacesUIUtils.SUPPORTED_FLAVORS = [...PlacesUIUtils.PLACES_FLAVORS,
...PlacesUIUtils.URI_FLAVORS];
XPCOMUtils.defineLazyServiceGetter(PlacesUIUtils, "RDF",
"@mozilla.org/rdf/rdf-service;1",
"nsIRDFService");

View File

@ -169,7 +169,7 @@ PlacesViewBase.prototype = {
let index = PlacesUtils.bookmarks.DEFAULT_INDEX;
let container = this._resultNode;
let orientation = Ci.nsITreeView.DROP_BEFORE;
let isTag = false;
let tagName = null;
let selectedNode = this.selectedNode;
if (selectedNode) {
@ -185,7 +185,8 @@ PlacesViewBase.prototype = {
// In all other cases the insertion point is before that node.
container = selectedNode.parent;
index = container.getChildIndex(selectedNode);
isTag = PlacesUtils.nodeIsTagQuery(container);
if (PlacesUtils.nodeIsTagQuery(container))
tagName = container.title;
}
}
@ -193,7 +194,7 @@ PlacesViewBase.prototype = {
return null;
return new InsertionPoint(PlacesUtils.getConcreteItemId(container),
index, orientation, isTag);
index, orientation, tagName);
},
buildContextMenu: function PVB_buildContextMenu(aPopup) {
@ -1392,10 +1393,12 @@ PlacesToolbar.prototype = {
else if (this.isRTL ? (aEvent.clientX > eltRect.left + threshold)
: (aEvent.clientX < eltRect.right - threshold)) {
// Drop inside this folder.
let tagName = PlacesUtils.nodeIsTagQuery(elt._placesNode) ?
elt._placesNode.title : null;
dropPoint.ip =
new InsertionPoint(PlacesUtils.getConcreteItemId(elt._placesNode),
-1, Ci.nsITreeView.DROP_ON,
PlacesUtils.nodeIsTagQuery(elt._placesNode));
tagName);
dropPoint.beforeIndex = eltIndex;
dropPoint.folderElt = elt;
}
@ -1637,6 +1640,7 @@ PlacesToolbar.prototype = {
let dropPoint = this._getDropPoint(aEvent);
if (dropPoint && dropPoint.ip) {
PlacesControllerDragHelper.onDrop(dropPoint.ip, aEvent.dataTransfer)
.then(null, Components.utils.reportError);
aEvent.preventDefault();
}

View File

@ -37,18 +37,18 @@ const REMOVE_PAGES_CHUNKLEN = 300;
* insertion point to accommodate the orientation should be done by
* the person who constructs the IP, not the user. The orientation
* is provided for informational purposes only!
* @param [optional] aIsTag
* Indicates if parent container is a tag
* @param [optional] aTag
* The tag name if this IP is set to a tag, null otherwise.
* @param [optional] aDropNearItemId
* When defined we will calculate index based on this itemId
* @constructor
*/
function InsertionPoint(aItemId, aIndex, aOrientation, aIsTag,
aDropNearItemId) {
function InsertionPoint(aItemId, aIndex, aOrientation, aTagName = null,
aDropNearItemId = false) {
this.itemId = aItemId;
this._index = aIndex;
this.orientation = aOrientation;
this.isTag = aIsTag;
this.tagName = aTagName;
this.dropNearItemId = aDropNearItemId;
}
@ -57,7 +57,7 @@ InsertionPoint.prototype = {
return this._index = val;
},
promiseGUID: function () PlacesUtils.promiseItemGUID(this.itemId),
promiseGuid: function () PlacesUtils.promiseItemGUID(this.itemId),
get index() {
if (this.dropNearItemId > 0) {
@ -67,7 +67,9 @@ InsertionPoint.prototype = {
return this.orientation == Ci.nsITreeView.DROP_BEFORE ? index : index + 1;
}
return this._index;
}
},
get isTag() typeof(this.tagName) == "string"
};
/**
@ -127,14 +129,8 @@ PlacesController.prototype = {
isCommandEnabled: function PC_isCommandEnabled(aCommand) {
if (PlacesUIUtils.useAsyncTransactions) {
switch (aCommand) {
case "cmd_cut":
case "placesCmd_cut":
case "cmd_copy":
case "cmd_paste":
case "cmd_delete":
case "placesCmd_delete":
case "cmd_paste":
case "placesCmd_paste":
case "placesCmd_new:folder":
case "placesCmd_new:bookmark":
case "placesCmd_createBookmark":
@ -243,7 +239,7 @@ PlacesController.prototype = {
break;
case "cmd_paste":
case "placesCmd_paste":
this.paste();
this.paste().then(null, Components.utils.reportError);
break;
case "cmd_delete":
case "placesCmd_delete":
@ -378,7 +374,7 @@ PlacesController.prototype = {
// if the clipboard contains TYPE_X_MOZ_PLACE_* data, it is definitely
// pasteable, with no need to unwrap all the nodes.
var flavors = PlacesControllerDragHelper.placesFlavors;
var flavors = PlacesUIUtils.PLACES_FLAVORS;
var clipboard = this.clipboard;
var hasPlacesData =
clipboard.hasDataMatchingFlavors(flavors, flavors.length,
@ -785,7 +781,7 @@ PlacesController.prototype = {
return;
}
let txn = PlacesTransactions.NewSeparator({ parentGUID: yield ip.promiseGUID()
let txn = PlacesTransactions.NewSeparator({ parentGUID: yield ip.promiseGuid()
, index: ip.index });
let guid = yield PlacesTransactions.transact(txn);
let itemId = yield PlacesUtils.promiseItemId(guid);
@ -1253,7 +1249,7 @@ PlacesController.prototype = {
/**
* Paste Bookmarks and Folders from the clipboard
*/
paste: function PC_paste() {
paste: Task.async(function* () {
// No reason to proceed if there isn't a valid insertion point.
let ip = this._view.insertionPoint;
if (!ip)
@ -1285,54 +1281,91 @@ PlacesController.prototype = {
return;
}
let transactions = [];
let insertionIndex = ip.index;
for (let i = 0; i < items.length; ++i) {
let itemsToSelect = [];
if (PlacesUIUtils.useAsyncTransactions) {
if (ip.isTag) {
// Pasting into a tag container means tagging the item, regardless of
// the requested action.
let tagTxn = new PlacesTagURITransaction(NetUtil.newURI(items[i].uri),
[ip.itemId]);
transactions.push(tagTxn);
continue;
let uris = [for (item of items) if ("uri" in item)
NetUtil.newURI(item.uri)];
yield PlacesTransactions.transact(
PlacesTransactions.Tag({ uris: uris, tag: ip.tagName }));
}
else {
yield PlacesTransactions.transact(function* () {
let insertionIndex = ip.index;
let parent = yield ip.promiseGuid();
// Adjust index to make sure items are pasted in the correct position.
// If index is DEFAULT_INDEX, items are just appended.
if (ip.index != PlacesUtils.bookmarks.DEFAULT_INDEX)
insertionIndex = ip.index + i;
for (let item of items) {
let doCopy = action == "copy";
// If this is not a copy, check for safety that we can move the source,
// otherwise report an error and fallback to a copy.
if (action != "copy" && !PlacesControllerDragHelper.canMoveUnwrappedNode(items[i])) {
Components.utils.reportError("Tried to move an unmovable Places node, " +
"reverting to a copy operation.");
action = "copy";
// If this is not a copy, check for safety that we can move the
// source, otherwise report an error and fallback to a copy.
if (!doCopy &&
!PlacesControllerDragHelper.canMoveUnwrappedNode(item)) {
Cu.reportError("Tried to move an unmovable Places node, " +
"reverting to a copy operation.");
doCopy = true;
}
let guid = yield PlacesUIUtils.getTransactionForData(
item, type, parent, insertionIndex, doCopy);
itemsToSelect.push(yield PlacesUtils.promiseItemId(guid));
// Adjust index to make sure items are pasted in the correct
// position. If index is DEFAULT_INDEX, items are just appended.
if (insertionIndex != PlacesUtils.bookmarks.DEFAULT_INDEX)
insertionIndex++;
}
});
}
}
else {
let transactions = [];
for (let i = 0; i < items.length; ++i) {
let insertionIndex = ip.index + i;
if (ip.isTag) {
// Pasting into a tag container means tagging the item, regardless of
// the requested action.
let tagTxn = new PlacesTagURITransaction(NetUtil.newURI(items[i].uri),
[ip.itemId]);
transactions.push(tagTxn);
continue;
}
// Adjust index to make sure items are pasted in the correct position.
// If index is DEFAULT_INDEX, items are just appended.
if (ip.index != PlacesUtils.bookmarks.DEFAULT_INDEX)
insertionIndex = ip.index + i;
// If this is not a copy, check for safety that we can move the source,
// otherwise report an error and fallback to a copy.
if (action != "copy" && !PlacesControllerDragHelper.canMoveUnwrappedNode(items[i])) {
Components.utils.reportError("Tried to move an unmovable Places node, " +
"reverting to a copy operation.");
action = "copy";
}
transactions.push(
PlacesUIUtils.makeTransaction(items[i], type, ip.itemId,
insertionIndex, action == "copy")
);
}
let aggregatedTxn = new PlacesAggregatedTransaction("Paste", transactions);
PlacesUtils.transactionManager.doTransaction(aggregatedTxn);
for (let i = 0; i < transactions.length; ++i) {
itemsToSelect.push(
PlacesUtils.bookmarks.getIdForItemAt(ip.itemId, ip.index + i)
);
}
transactions.push(
PlacesUIUtils.makeTransaction(items[i], type, ip.itemId,
insertionIndex, action == "copy")
);
}
let aggregatedTxn = new PlacesAggregatedTransaction("Paste", transactions);
PlacesUtils.transactionManager.doTransaction(aggregatedTxn);
// Cut/past operations are not repeatable, so clear the clipboard.
if (action == "cut") {
this._clearClipboard();
}
// Select the pasted items, they should be consecutive.
let insertedNodeIds = [];
for (let i = 0; i < transactions.length; ++i) {
insertedNodeIds.push(
PlacesUtils.bookmarks.getIdForItemAt(ip.itemId, ip.index + i)
);
}
if (insertedNodeIds.length > 0)
this._view.selectItems(insertedNodeIds, false);
},
if (itemsToSelect.length > 0)
this._view.selectItems(itemsToSelect, false);
}),
/**
* Cache the livemark info for a node. This allows the controller and the
@ -1412,7 +1445,7 @@ let PlacesControllerDragHelper = {
*/
getFirstValidFlavor: function PCDH_getFirstValidFlavor(aFlavors) {
for (let i = 0; i < aFlavors.length; i++) {
if (this.GENERIC_VIEW_DROP_TYPES.indexOf(aFlavors[i]) != -1)
if (PlacesUIUtils.SUPPORTED_FLAVORS.indexOf(aFlavors[i]) != -1)
return aFlavors[i];
}
@ -1569,12 +1602,15 @@ let PlacesControllerDragHelper = {
* @param insertionPoint
* The insertion point where the items should be dropped
*/
onDrop: function PCDH_onDrop(insertionPoint, dt) {
onDrop: Task.async(function* (insertionPoint, dt) {
let doCopy = ["copy", "link"].indexOf(dt.dropEffect) != -1;
let transactions = [];
let dropCount = dt.mozItemCount;
let movedCount = 0;
let parentGuid = PlacesUIUtils.useAsyncTransactions ?
(yield insertionPoint.promiseGuid()) : null;
let tagName = insertionPoint.tagName;
for (let i = 0; i < dropCount; ++i) {
let flavor = this.getFirstValidFlavor(dt.mozTypesAt(i));
if (!flavor)
@ -1613,8 +1649,10 @@ let PlacesControllerDragHelper = {
insertionPoint.orientation == Ci.nsITreeView.DROP_ON) {
let uri = NetUtil.newURI(unwrapped.uri);
let tagItemId = insertionPoint.itemId;
let tagTxn = new PlacesTagURITransaction(uri, [tagItemId]);
transactions.push(tagTxn);
if (PlacesUIUtils.useAsyncTransactions)
transactions.push(PlacesTransactions.Tag({ uri: uri, tag: tagName }));
else
transactions.push(new PlacesTagURITransaction(uri, [tagItemId]));
}
else {
// If this is not a copy, check for safety that we can move the source,
@ -1624,15 +1662,30 @@ let PlacesControllerDragHelper = {
"reverting to a copy operation.");
doCopy = true;
}
transactions.push(PlacesUIUtils.makeTransaction(unwrapped,
flavor, insertionPoint.itemId,
index, doCopy));
if (PlacesUIUtils.useAsyncTransactions) {
transactions.push(
PlacesUIUtils.getTransactionForData(unwrapped,
flavor,
parentGuid,
index,
doCopy));
}
else {
transactions.push(PlacesUIUtils.makeTransaction(unwrapped,
flavor, insertionPoint.itemId,
index, doCopy));
}
}
}
let txn = new PlacesAggregatedTransaction("DropItems", transactions);
PlacesUtils.transactionManager.doTransaction(txn);
},
if (PlacesUIUtils.useAsyncTransactions) {
yield PlacesTransactions.transact(transactions);
}
else {
let txn = new PlacesAggregatedTransaction("DropItems", transactions);
PlacesUtils.transactionManager.doTransaction(txn);
}
}),
/**
* Checks if we can insert into a container.
@ -1647,19 +1700,7 @@ let PlacesControllerDragHelper = {
// Disallow insertion of items under readonly folders.
return (!PlacesUtils.nodeIsFolder(aContainer) ||
PlacesUtils.nodeIsReadOnly(aContainer));
},
placesFlavors: [PlacesUtils.TYPE_X_MOZ_PLACE_CONTAINER,
PlacesUtils.TYPE_X_MOZ_PLACE_SEPARATOR,
PlacesUtils.TYPE_X_MOZ_PLACE],
// The order matters.
GENERIC_VIEW_DROP_TYPES: [PlacesUtils.TYPE_X_MOZ_PLACE_CONTAINER,
PlacesUtils.TYPE_X_MOZ_PLACE_SEPARATOR,
PlacesUtils.TYPE_X_MOZ_PLACE,
PlacesUtils.TYPE_X_MOZ_URL,
TAB_DROP_TYPE,
PlacesUtils.TYPE_UNICODE],
}
};

View File

@ -103,9 +103,12 @@
dropPoint.folderElt = elt;
return dropPoint;
}
else if ((PlacesUtils.nodeIsFolder(elt._placesNode) ||
PlacesUtils.nodeIsTagQuery(elt._placesNode)) &&
!PlacesUtils.nodeIsReadOnly(elt._placesNode)) {
let tagName = PlacesUtils.nodeIsTagQuery(elt._placesNode) ?
elt._placesNode.title : null;
if ((PlacesUtils.nodeIsFolder(elt._placesNode) ||
PlacesUtils.nodeIsTagQuery(elt._placesNode)) &&
!PlacesUtils.nodeIsReadOnly(elt._placesNode)) {
// This is a folder or a tag container.
if (eventY - eltY < eltHeight * 0.20) {
// If mouse is in the top part of the element, drop above folder.
@ -113,7 +116,7 @@
PlacesUtils.getConcreteItemId(resultNode),
-1,
Ci.nsITreeView.DROP_BEFORE,
PlacesUtils.nodeIsTagQuery(elt._placesNode),
tagName,
elt._placesNode.itemId);
return dropPoint;
}
@ -123,7 +126,7 @@
PlacesUtils.getConcreteItemId(elt._placesNode),
-1,
Ci.nsITreeView.DROP_ON,
PlacesUtils.nodeIsTagQuery(elt._placesNode));
tagName);
dropPoint.folderElt = elt;
return dropPoint;
}
@ -135,7 +138,7 @@
PlacesUtils.getConcreteItemId(resultNode),
-1,
Ci.nsITreeView.DROP_BEFORE,
PlacesUtils.nodeIsTagQuery(elt._placesNode),
tagName,
elt._placesNode.itemId);
return dropPoint;
}
@ -145,7 +148,7 @@
PlacesUtils.getConcreteItemId(resultNode),
-1,
Ci.nsITreeView.DROP_AFTER,
PlacesUtils.nodeIsTagQuery(elt._placesNode),
tagName,
elt._placesNode.itemId);
return dropPoint;
]]></body>
@ -364,7 +367,8 @@
let dropPoint = this._getDropPoint(event);
if (dropPoint && dropPoint.ip) {
PlacesControllerDragHelper.onDrop(dropPoint.ip, event.dataTransfer);
PlacesControllerDragHelper.onDrop(dropPoint.ip, event.dataTransfer)
.then(null, Components.utils.reportError);
event.preventDefault();
}

View File

@ -529,9 +529,11 @@
if (PlacesControllerDragHelper.disallowInsertion(container))
return null;
let tagName = PlacesUtils.nodeIsTagQuery(container) ?
container.title : null;
return new InsertionPoint(PlacesUtils.getConcreteItemId(container),
index, orientation,
PlacesUtils.nodeIsTagQuery(container),
tagName,
dropNearItemId);
]]></body>
</method>

View File

@ -1343,9 +1343,10 @@ PlacesTreeView.prototype = {
if (PlacesControllerDragHelper.disallowInsertion(container))
return null;
let tagName = PlacesUtils.nodeIsTagQuery(container) ? container.title : null;
return new InsertionPoint(PlacesUtils.getConcreteItemId(container),
index, orientation,
PlacesUtils.nodeIsTagQuery(container),
tagName,
dropNearItemId);
},
@ -1354,8 +1355,10 @@ PlacesTreeView.prototype = {
// parameters into a container id and index within the container,
// since this information is specific to the tree view.
let ip = this._getInsertionPoint(aRow, aOrientation);
if (ip)
PlacesControllerDragHelper.onDrop(ip, aDataTransfer);
if (ip) {
PlacesControllerDragHelper.onDrop(ip, aDataTransfer)
.then(null, Components.utils.reportError);
}
PlacesControllerDragHelper.currentDropTarget = null;
},

View File

@ -346,6 +346,9 @@ let SessionStoreInternal = {
// and restore the session.
_promiseReadyForInitialization: null,
// Keep busy state counters per window.
_windowBusyStates: new WeakMap(),
/**
* A promise fulfilled once initialization is complete.
*/
@ -1556,8 +1559,7 @@ let SessionStoreInternal = {
this._resetTabRestoringState(aTab);
}
this._setWindowStateBusy(window);
this.restoreTabs(window, [aTab], [tabState], 0);
this.restoreTab(aTab, tabState);
},
duplicateTab: function ssi_duplicateTab(aWindow, aTab, aDelta = 0) {
@ -1579,14 +1581,11 @@ let SessionStoreInternal = {
tabState.index = Math.max(1, Math.min(tabState.index, tabState.entries.length));
tabState.pinned = false;
this._setWindowStateBusy(aWindow);
let newTab = aTab == aWindow.gBrowser.selectedTab ?
aWindow.gBrowser.addTab(null, {relatedToCurrent: true, ownerTab: aTab}) :
aWindow.gBrowser.addTab();
this.restoreTabs(aWindow, [newTab], [tabState], 0,
true /* Load this tab right away. */);
this.restoreTab(newTab, tabState, true /* Load this tab right away. */);
return newTab;
},
@ -1632,13 +1631,12 @@ let SessionStoreInternal = {
let closedTab = closedTabs.splice(aIndex, 1).shift();
let closedTabState = closedTab.state;
this._setWindowStateBusy(aWindow);
// create a new tab
let tabbrowser = aWindow.gBrowser;
let tab = tabbrowser.addTab();
let tab = tabbrowser.selectedTab = tabbrowser.addTab();
// restore tab content
this.restoreTabs(aWindow, [tab], [closedTabState], 1);
this.restoreTab(tab, closedTabState);
// restore the tab's position
tabbrowser.moveTabTo(tab, closedTab.pos);
@ -2383,8 +2381,11 @@ let SessionStoreInternal = {
newClosedTabsData.slice(0, this._max_tabs_undo);
}
this.restoreTabs(aWindow, tabs, winData.tabs,
(overwriteTabs ? (parseInt(winData.selected || "1")) : 0));
// Restore tabs, if any.
if (winData.tabs.length) {
this.restoreTabs(aWindow, tabs, winData.tabs,
(overwriteTabs ? (parseInt(winData.selected || "1")) : 0));
}
if (aState.scratchpads) {
ScratchpadManager.restoreSession(aState.scratchpads);
@ -2395,6 +2396,7 @@ let SessionStoreInternal = {
TelemetryStopwatch.finish("FX_SESSION_RESTORE_RESTORE_WINDOW_MS");
this._setWindowStateReady(aWindow);
this._sendRestoreCompletedNotifications();
},
@ -2410,14 +2412,8 @@ let SessionStoreInternal = {
* Index of the tab to select. This is a 1-based index where "1"
* indicates the first tab should be selected, and "0" indicates that
* the currently selected tab will not be changed.
* @param aRestoreImmediately
* Flag to indicate whether the given set of tabs aTabs should be
* restored/loaded immediately even if restore_on_demand = true
*/
restoreTabs: function (aWindow, aTabs, aTabData, aSelectTab,
aRestoreImmediately = false)
{
restoreTabs(aWindow, aTabs, aTabData, aSelectTab) {
var tabbrowser = aWindow.gBrowser;
if (!this._isWindowLoaded(aWindow)) {
@ -2427,130 +2423,150 @@ let SessionStoreInternal = {
delete this._windows[aWindow.__SSi]._restoring;
}
// It's important to set the window state to dirty so that
// we collect their data for the first time when saving state.
DirtyWindows.add(aWindow);
let numTabsToRestore = aTabs.length;
let numTabsInWindow = tabbrowser.tabs.length;
let tabsDataArray = this._windows[aWindow.__SSi].tabs;
// Set the state to restore as the window's current state. Normally, this
// will just be overridden the next time we collect state but we need this
// as a fallback should Firefox be shutdown early without notifying us
// beforehand.
this._windows[aWindow.__SSi].tabs = aTabData.slice();
this._windows[aWindow.__SSi].selected = aSelectTab;
if (aTabs.length == 0) {
// This is normally done later, but as we're returning early
// here we need to take care of it.
this._setWindowStateReady(aWindow);
return;
// Update the window state in case we shut down without being notified.
// Individual tab states will be taken care of by restoreTab() below.
if (numTabsInWindow == numTabsToRestore) {
// Remove all previous tab data.
tabsDataArray.length = 0;
} else {
// Remove all previous tab data except tabs that should not be overriden.
tabsDataArray.splice(numTabsInWindow - numTabsToRestore);
}
// Let the tab data array have the right number of slots.
tabsDataArray.length = numTabsInWindow;
// If provided, set the selected tab.
if (aSelectTab > 0 && aSelectTab <= aTabs.length) {
tabbrowser.selectedTab = aTabs[aSelectTab - 1];
// Update the window state in case we shut down without being notified.
this._windows[aWindow.__SSi].selected = aSelectTab;
}
// Prepare the tabs so that they can be properly restored. We'll pin/unpin
// and show/hide tabs as necessary. We'll also set the labels, user typed
// value, and attach a copy of the tab's data in case we close it before
// it's been restored.
// Restore all tabs.
for (let t = 0; t < aTabs.length; t++) {
let tab = aTabs[t];
let browser = tabbrowser.getBrowserForTab(tab);
let tabData = aTabData[t];
this.restoreTab(aTabs[t], aTabData[t]);
}
},
if (tabData.pinned)
tabbrowser.pinTab(tab);
else
tabbrowser.unpinTab(tab);
// Restores the given tab state for a given tab.
restoreTab(tab, tabData, restoreImmediately = false) {
let browser = tab.linkedBrowser;
let window = tab.ownerDocument.defaultView;
let tabbrowser = window.gBrowser;
if (tabData.hidden)
tabbrowser.hideTab(tab);
else
tabbrowser.showTab(tab);
// Increase the busy state counter before modifying the tab.
this._setWindowStateBusy(window);
if (tabData.lastAccessed) {
tab.lastAccessed = tabData.lastAccessed;
}
// It's important to set the window state to dirty so that
// we collect their data for the first time when saving state.
DirtyWindows.add(window);
if ("attributes" in tabData) {
// Ensure that we persist tab attributes restored from previous sessions.
Object.keys(tabData.attributes).forEach(a => TabAttributes.persist(a));
}
// Update the tab state in case we shut down without being notified.
this._windows[window.__SSi].tabs[tab._tPos] = tabData;
if (!tabData.entries) {
tabData.entries = [];
}
if (tabData.extData) {
tab.__SS_extdata = {};
for (let key in tabData.extData)
tab.__SS_extdata[key] = tabData.extData[key];
} else {
delete tab.__SS_extdata;
}
delete tabData.closedAt; // Tab is now open.
// Flush all data from the content script synchronously. This is done so
// that all async messages that are still on their way to chrome will
// be ignored and don't override any tab data set when restoring.
TabState.flush(tab.linkedBrowser);
// Ensure the index is in bounds.
let activeIndex = (tabData.index || tabData.entries.length) - 1;
activeIndex = Math.min(activeIndex, tabData.entries.length - 1);
activeIndex = Math.max(activeIndex, 0);
// Save the index in case we updated it above.
tabData.index = activeIndex + 1;
// In electrolysis, we may need to change the browser's remote
// attribute so that it runs in a content process.
let activePageData = tabData.entries[activeIndex] || null;
let uri = activePageData ? activePageData.url || null : null;
tabbrowser.updateBrowserRemotenessByURL(browser, uri);
// Start a new epoch and include the epoch in the restoreHistory
// message. If a message is received that relates to a previous epoch, we
// discard it.
let epoch = this._nextRestoreEpoch++;
this._browserEpochs.set(browser.permanentKey, epoch);
// keep the data around to prevent dataloss in case
// a tab gets closed before it's been properly restored
browser.__SS_data = tabData;
browser.__SS_restoreState = TAB_STATE_NEEDS_RESTORE;
browser.setAttribute("pending", "true");
tab.setAttribute("pending", "true");
// Update the persistent tab state cache with |tabData| information.
TabStateCache.update(browser, {
history: {entries: tabData.entries, index: tabData.index},
scroll: tabData.scroll || null,
storage: tabData.storage || null,
formdata: tabData.formdata || null,
disallow: tabData.disallow || null,
pageStyle: tabData.pageStyle || null
});
browser.messageManager.sendAsyncMessage("SessionStore:restoreHistory",
{tabData: tabData, epoch: epoch});
// Restore tab attributes.
if ("attributes" in tabData) {
TabAttributes.set(tab, tabData.attributes);
}
// This could cause us to ignore MAX_CONCURRENT_TAB_RESTORES a bit, but
// it ensures each window will have its selected tab loaded.
if (aRestoreImmediately || tabbrowser.selectedBrowser == browser) {
this.restoreTabContent(tab);
} else {
TabRestoreQueue.add(tab);
this.restoreNextTab();
}
// Prepare the tab so that it can be properly restored. We'll pin/unpin
// and show/hide tabs as necessary. We'll also attach a copy of the tab's
// data in case we close it before it's been restored.
if (tabData.pinned) {
tabbrowser.pinTab(tab);
} else {
tabbrowser.unpinTab(tab);
}
this._setWindowStateReady(aWindow);
if (tabData.hidden) {
tabbrowser.hideTab(tab);
} else {
tabbrowser.showTab(tab);
}
if (tabData.lastAccessed) {
tab.lastAccessed = tabData.lastAccessed;
}
if ("attributes" in tabData) {
// Ensure that we persist tab attributes restored from previous sessions.
Object.keys(tabData.attributes).forEach(a => TabAttributes.persist(a));
}
if (!tabData.entries) {
tabData.entries = [];
}
if (tabData.extData) {
tab.__SS_extdata = Cu.cloneInto(tabData.extData, {});
} else {
delete tab.__SS_extdata;
}
// Tab is now open.
delete tabData.closedAt;
// Flush all data from the content script synchronously. This is done so
// that all async messages that are still on their way to chrome will
// be ignored and don't override any tab data set when restoring.
TabState.flush(browser);
// Ensure the index is in bounds.
let activeIndex = (tabData.index || tabData.entries.length) - 1;
activeIndex = Math.min(activeIndex, tabData.entries.length - 1);
activeIndex = Math.max(activeIndex, 0);
// Save the index in case we updated it above.
tabData.index = activeIndex + 1;
// In electrolysis, we may need to change the browser's remote
// attribute so that it runs in a content process.
let activePageData = tabData.entries[activeIndex] || null;
let uri = activePageData ? activePageData.url || null : null;
tabbrowser.updateBrowserRemotenessByURL(browser, uri);
// Start a new epoch and include the epoch in the restoreHistory
// message. If a message is received that relates to a previous epoch, we
// discard it.
let epoch = this._nextRestoreEpoch++;
this._browserEpochs.set(browser.permanentKey, epoch);
// keep the data around to prevent dataloss in case
// a tab gets closed before it's been properly restored
browser.__SS_data = tabData;
browser.__SS_restoreState = TAB_STATE_NEEDS_RESTORE;
browser.setAttribute("pending", "true");
tab.setAttribute("pending", "true");
// Update the persistent tab state cache with |tabData| information.
TabStateCache.update(browser, {
history: {entries: tabData.entries, index: tabData.index},
scroll: tabData.scroll || null,
storage: tabData.storage || null,
formdata: tabData.formdata || null,
disallow: tabData.disallow || null,
pageStyle: tabData.pageStyle || null
});
browser.messageManager.sendAsyncMessage("SessionStore:restoreHistory",
{tabData: tabData, epoch: epoch});
// Restore tab attributes.
if ("attributes" in tabData) {
TabAttributes.set(tab, tabData.attributes);
}
// This could cause us to ignore MAX_CONCURRENT_TAB_RESTORES a bit, but
// it ensures each window will have its selected tab loaded.
if (restoreImmediately || tabbrowser.selectedBrowser == browser) {
this.restoreTabContent(tab);
} else {
TabRestoreQueue.add(tab);
this.restoreNextTab();
}
// Decrease the busy state counter after we're done.
this._setWindowStateReady(window);
},
/**
@ -3240,8 +3256,16 @@ let SessionStoreInternal = {
* @param aWindow the window
*/
_setWindowStateReady: function ssi_setWindowStateReady(aWindow) {
this._setWindowStateBusyValue(aWindow, false);
this._sendWindowStateEvent(aWindow, "Ready");
let newCount = (this._windowBusyStates.get(aWindow) || 0) - 1;
if (newCount < 0) {
throw new Error("Invalid window busy state (less than zero).");
}
this._windowBusyStates.set(aWindow, newCount);
if (newCount == 0) {
this._setWindowStateBusyValue(aWindow, false);
this._sendWindowStateEvent(aWindow, "Ready");
}
},
/**
@ -3249,8 +3273,13 @@ let SessionStoreInternal = {
* @param aWindow the window
*/
_setWindowStateBusy: function ssi_setWindowStateBusy(aWindow) {
this._setWindowStateBusyValue(aWindow, true);
this._sendWindowStateEvent(aWindow, "Busy");
let newCount = (this._windowBusyStates.get(aWindow) || 0) + 1;
this._windowBusyStates.set(aWindow, newCount);
if (newCount == 1) {
this._setWindowStateBusyValue(aWindow, true);
this._sendWindowStateEvent(aWindow, "Busy");
}
},
/**

View File

@ -113,9 +113,9 @@
crop="end"
value="&profilerUI.table.percentage;"/>
<label class="plain call-tree-header"
type="invocations"
type="samples"
crop="end"
value="&profilerUI.table.invocations;"/>
value="&profilerUI.table.samples;"/>
<label class="plain call-tree-header"
type="function"
crop="end"

View File

@ -1,4 +1,4 @@
s/* Any copyright is dedicated to the Public Domain.
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/**

View File

@ -130,7 +130,7 @@ function test() {
is(Object.keys(root.calls.A.calls.E.calls.F.calls).length, 0,
"The correct number of child calls were calculated for the '.A.E.F' node.");
// Check the location, sample times, duration and invocations of the root.
// Check the location, sample times, duration and samples of the root.
is(root.calls.A.location, "A",
"The '.A' node has the correct location.");
@ -139,8 +139,8 @@ function test() {
"The '.A' node has the correct sample times.");
is(root.calls.A.duration, 20,
"The '.A' node has the correct duration in milliseconds.");
is(root.calls.A.invocations, 4,
"The '.A' node has the correct number of invocations.");
is(root.calls.A.samples, 4,
"The '.A' node has the correct number of samples.");
// ...and the rightmost leaf.
@ -151,8 +151,8 @@ function test() {
"The '.A.E.F' node has the correct sample times.");
is(root.calls.A.calls.E.calls.F.duration, 7,
"The '.A.E.F' node has the correct duration in milliseconds.");
is(root.calls.A.calls.E.calls.F.invocations, 1,
"The '.A.E.F' node has the correct number of invocations.");
is(root.calls.A.calls.E.calls.F.samples, 1,
"The '.A.E.F' node has the correct number of samples.");
// ...and the leftmost leaf.
@ -163,8 +163,8 @@ function test() {
"The '.A.B.C.D.E.F.G' node has the correct sample times.");
is(root.calls.A.calls.B.calls.C.calls.D.calls.E.calls.F.calls.G.duration, 2,
"The '.A.B.C.D.E.F.G' node has the correct duration in milliseconds.");
is(root.calls.A.calls.B.calls.C.calls.D.calls.E.calls.F.calls.G.invocations, 1,
"The '.A.B.C.D.E.F.G' node has the correct number of invocations.");
is(root.calls.A.calls.B.calls.C.calls.D.calls.E.calls.F.calls.G.samples, 1,
"The '.A.B.C.D.E.F.G' node has the correct number of samples.");
finish();
}

View File

@ -37,10 +37,10 @@ function test() {
is(container.childNodes[0].childNodes[1].getAttribute("value"), "100%",
"The root node in the tree has the correct percentage cell value.");
is(container.childNodes[0].childNodes[2].getAttribute("type"), "invocations",
"The root node in the tree has an invocations cell.");
is(container.childNodes[0].childNodes[2].getAttribute("value"), "",
"The root node in the tree has the correct invocations cell value.");
is(container.childNodes[0].childNodes[2].getAttribute("type"), "samples",
"The root node in the tree has an samples cell.");
is(container.childNodes[0].childNodes[2].getAttribute("value"), "3",
"The root node in the tree has the correct samples cell value.");
is(container.childNodes[0].childNodes[3].getAttribute("type"), "function",
"The root node in the tree has a function cell.");

View File

@ -20,7 +20,7 @@ function test() {
let $$fun = node => container.querySelectorAll(".call-tree-cell[type=function] > " + node);
let $$dur = i => container.querySelectorAll(".call-tree-cell[type=duration]")[i];
let $$perc = i => container.querySelectorAll(".call-tree-cell[type=percentage]")[i];
let $$invoc = i => container.querySelectorAll(".call-tree-cell[type=invocations]")[i];
let $$sampl = i => container.querySelectorAll(".call-tree-cell[type=samples]")[i];
is(container.childNodes.length, 1,
"The container node should have one child available.");
@ -31,8 +31,8 @@ function test() {
"The root's duration cell displays the correct value.");
is($$perc(0).getAttribute("value"), "100%",
"The root's percentage cell displays the correct value.");
is($$invoc(0).getAttribute("value"), "",
"The root's invocations cell displays the correct value.");
is($$sampl(0).getAttribute("value"), "3",
"The root's samples cell displays the correct value.");
is($$fun(".call-tree-name")[0].getAttribute("value"), "(root)",
"The root's function cell displays the correct name.");
is($$fun(".call-tree-url")[0].getAttribute("value"), "",
@ -57,8 +57,8 @@ function test() {
"The .A node's duration cell displays the correct value.");
is($$perc(1).getAttribute("value"), "100%",
"The .A node's percentage cell displays the correct value.");
is($$invoc(1).getAttribute("value"), "3",
"The .A node's invocations cell displays the correct value.");
is($$sampl(1).getAttribute("value"), "3",
"The .A node's samples cell displays the correct value.");
is($$fun(".call-tree-name")[1].getAttribute("value"), "A",
"The .A node's function cell displays the correct name.");
is($$fun(".call-tree-url")[1].getAttribute("value"), "baz",
@ -84,10 +84,10 @@ function test() {
is($$dur(2).getAttribute("value"), "11",
"The .A.B node's duration cell displays the correct value.");
is($$perc(2).getAttribute("value"), "61.11%",
is($$perc(2).getAttribute("value"), "66.66%",
"The .A.B node's percentage cell displays the correct value.");
is($$invoc(2).getAttribute("value"), "2",
"The .A.B node's invocations cell displays the correct value.");
is($$sampl(2).getAttribute("value"), "2",
"The .A.B node's samples cell displays the correct value.");
is($$fun(".call-tree-name")[2].getAttribute("value"), "B",
"The .A.B node's function cell displays the correct name.");
is($$fun(".call-tree-url")[2].getAttribute("value"), "baz",
@ -103,10 +103,10 @@ function test() {
is($$dur(3).getAttribute("value"), "7",
"The .A.E node's duration cell displays the correct value.");
is($$perc(3).getAttribute("value"), "38.88%",
is($$perc(3).getAttribute("value"), "33.33%",
"The .A.E node's percentage cell displays the correct value.");
is($$invoc(3).getAttribute("value"), "1",
"The .A.E node's invocations cell displays the correct value.");
is($$sampl(3).getAttribute("value"), "1",
"The .A.E node's samples cell displays the correct value.");
is($$fun(".call-tree-name")[3].getAttribute("value"), "E",
"The .A.E node's function cell displays the correct name.");
is($$fun(".call-tree-url")[3].getAttribute("value"), "baz",

View File

@ -48,7 +48,7 @@ function test() {
"The first column displayed for tree items is correct.");
is(C.target.childNodes[1].getAttribute("type"), "percentage",
"The second column displayed for tree items is correct.");
is(C.target.childNodes[2].getAttribute("type"), "invocations",
is(C.target.childNodes[2].getAttribute("type"), "samples",
"The third column displayed for tree items is correct.");
is(C.target.childNodes[3].getAttribute("type"), "function",
"The fourth column displayed for tree items is correct.");

View File

@ -23,7 +23,7 @@ exports._isContent = isContent; // used in tests
/**
* A call tree for a thread. This is essentially a linkage between all frames
* of all samples into a single tree structure, with additional information
* on each node, like the time spent (in milliseconds) and invocations count.
* on each node, like the time spent (in milliseconds) and samples count.
*
* Example:
* {
@ -32,8 +32,8 @@ exports._isContent = isContent; // used in tests
* "FunctionName (url:line)": {
* line: number,
* category: number,
* samples: number,
* duration: number,
* invocations: number,
* calls: {
* ...
* }
@ -52,6 +52,7 @@ exports._isContent = isContent; // used in tests
* @see ThreadNode.prototype.insert
*/
function ThreadNode(threadSamples, contentOnly, beginAt, endAt) {
this.samples = 0;
this.duration = 0;
this.calls = {};
this._previousSampleTime = 0;
@ -97,6 +98,7 @@ ThreadNode.prototype = {
let sampleDuration = sampleTime - this._previousSampleTime;
this._previousSampleTime = sampleTime;
this.samples++;
this.duration += sampleDuration;
FrameNode.prototype.insert(
@ -132,8 +134,8 @@ function FrameNode({ location, line, category }) {
this.line = line;
this.category = category;
this.sampleTimes = [];
this.samples = 0;
this.duration = 0;
this.invocations = 0;
this.calls = {};
}
@ -165,8 +167,8 @@ FrameNode.prototype = {
let location = frame.location;
let child = _store[location] || (_store[location] = new FrameNode(frame));
child.sampleTimes.push({ start: time, end: time + duration });
child.samples++;
child.duration += duration;
child.invocations++;
child.insert(frames, ++index, time, duration);
},

View File

@ -38,7 +38,7 @@ exports.CallView = CallView;
* The CallView considered the "caller" frame. This instance will be
* represent the "callee". Should be null for root nodes.
* @param ThreadNode | FrameNode frame
* Details about this function, like { duration, invocation, calls } etc.
* Details about this function, like { samples, duration, calls } etc.
* @param number level
* The indentation level in the call tree. The root node is at level 0.
*/
@ -63,11 +63,11 @@ CallView.prototype = Heritage.extend(AbstractTreeItem.prototype, {
this.document = document;
let frameInfo = this.frame.getInfo();
let framePercentage = this.frame.duration / this.root.frame.duration * 100;
let framePercentage = this.frame.samples / this.root.frame.samples * 100;
let durationCell = this._createTimeCell(this.frame.duration);
let percentageCell = this._createExecutionCell(framePercentage);
let invocationsCell = this._createInvocationsCell(this.frame.invocations);
let samplesCell = this._createSamplesCell(this.frame.samples);
let functionCell = this._createFunctionCell(arrowNode, frameInfo, this.level);
let targetNode = document.createElement("hbox");
@ -84,7 +84,7 @@ CallView.prototype = Heritage.extend(AbstractTreeItem.prototype, {
targetNode.appendChild(durationCell);
targetNode.appendChild(percentageCell);
targetNode.appendChild(invocationsCell);
targetNode.appendChild(samplesCell);
targetNode.appendChild(functionCell);
return targetNode;
@ -130,10 +130,10 @@ CallView.prototype = Heritage.extend(AbstractTreeItem.prototype, {
cell.setAttribute("value", L10N.numberWithDecimals(percentage, 2) + "%");
return cell;
},
_createInvocationsCell: function(count) {
_createSamplesCell: function(count) {
let cell = this.document.createElement("label");
cell.className = "plain call-tree-cell";
cell.setAttribute("type", "invocations");
cell.setAttribute("type", "samples");
cell.setAttribute("crop", "end");
cell.setAttribute("value", count || "");
return cell;

View File

@ -41,7 +41,7 @@
- in the call tree headers for a recording. -->
<!ENTITY profilerUI.table.duration "Time (ms)">
<!ENTITY profilerUI.table.percentage "Cost">
<!ENTITY profilerUI.table.invocations "Calls">
<!ENTITY profilerUI.table.samples "Samples">
<!ENTITY profilerUI.table.function "Function">
<!-- LOCALIZATION NOTE (profilerUI.newtab.tooltiptext): The tooltiptext shown

View File

@ -1469,11 +1469,16 @@ notification[value="translation"] menulist > .menulist-dropmarker {
.ac-result-type-keyword,
.autocomplete-treebody::-moz-tree-image(keyword, treecolAutoCompleteImage) {
list-style-image: url(moz-icon://stock/gtk-find?size=menu);
list-style-image: url(chrome://global/skin/icons/autocomplete-search.svg#search-icon);
width: 16px;
height: 16px;
}
.ac-result-type-keyword[selected="true"],
.autocomplete-treebody::-moz-tree-image(keyword, treecolAutoCompleteImage, selected) {
list-style-image: url(chrome://global/skin/icons/autocomplete-search.svg#search-icon-inverted);
}
.ac-result-type-tag,
.autocomplete-treebody::-moz-tree-image(tag, treecolAutoCompleteImage) {
list-style-image: url("chrome://browser/skin/places/tag.png");

View File

@ -2175,10 +2175,14 @@ toolbarbutton[sdk-button="true"][cui-areatype="toolbar"] > .toolbarbutton-icon {
.ac-result-type-keyword,
.autocomplete-treebody::-moz-tree-image(keyword, treecolAutoCompleteImage) {
list-style-image: url(chrome://global/skin/icons/search-textbox.png);
margin: 2px;
width: 12px;
height: 12px;
list-style-image: url(chrome://global/skin/icons/autocomplete-search.svg#search-icon);
width: 16px;
height: 16px;
}
.ac-result-type-keyword[selected="true"],
.autocomplete-treebody::-moz-tree-image(keyword, treecolAutoCompleteImage, selected) {
list-style-image: url(chrome://global/skin/icons/autocomplete-search.svg#search-icon-inverted);
}
richlistitem[selected="true"][current="true"] > .ac-title-box > .ac-result-type-bookmark,

View File

@ -260,8 +260,8 @@
width: 5em;
}
.call-tree-header[type="invocations"],
.call-tree-cell[type="invocations"] {
.call-tree-header[type="samples"],
.call-tree-cell[type="samples"] {
width: 5em;
}

View File

@ -1395,12 +1395,22 @@ richlistitem[selected="true"][current="true"] > .ac-title-box > .ac-result-type-
.ac-result-type-keyword,
.autocomplete-treebody::-moz-tree-image(keyword, treecolAutoCompleteImage) {
list-style-image: url(chrome://global/skin/icons/Search-glass.png);
-moz-image-region: rect(0px 32px 16px 16px);
list-style-image: url(chrome://global/skin/icons/autocomplete-search.svg#search-icon);
width: 16px;
height: 16px;
}
%ifdef WINDOWS_AERO
@media not all and (-moz-windows-default-theme) {
%endif
.ac-result-type-keyword[selected="true"],
.autocomplete-treebody::-moz-tree-image(keyword, treecolAutoCompleteImage, selected) {
list-style-image: url(chrome://global/skin/icons/autocomplete-search.svg#search-icon-inverted);
}
%ifdef WINDOWS_AERO
}
%endif
.ac-result-type-tag,
.autocomplete-treebody::-moz-tree-image(tag, treecolAutoCompleteImage) {
list-style-image: url("chrome://browser/skin/places/tag.png");

View File

@ -320,12 +320,15 @@ GetDirectionFromText(const char16_t* aText, const uint32_t aLength,
current++;
}
Directionality dir = GetDirectionFromChar(ch);
if (dir != eDir_NotSet) {
if (aFirstStrong) {
*aFirstStrong = current;
// Just ignore lone surrogates
if (!IS_SURROGATE(ch)) {
Directionality dir = GetDirectionFromChar(ch);
if (dir != eDir_NotSet) {
if (aFirstStrong) {
*aFirstStrong = current;
}
return dir;
}
return dir;
}
}

View File

@ -10,6 +10,7 @@
#include "DOMMediaStream.h"
#include "EncodedBufferCache.h"
#include "MediaEncoder.h"
#include "mozilla/StaticPtr.h"
#include "mozilla/DOMEventTargetHelper.h"
#include "mozilla/Preferences.h"
#include "mozilla/dom/AudioStreamTrack.h"
@ -35,6 +36,73 @@ namespace mozilla {
namespace dom {
/**
+ * MediaRecorderReporter measures memory being used by the Media Recorder.
+ *
+ * It is a singleton reporter and the single class object lives as long as at
+ * least one Recorder is registered. In MediaRecorder, the reporter is unregistered
+ * when it is destroyed.
+ */
class MediaRecorderReporter MOZ_FINAL : public nsIMemoryReporter
{
public:
NS_DECL_THREADSAFE_ISUPPORTS
MediaRecorderReporter() {};
static MediaRecorderReporter* UniqueInstance();
void InitMemoryReporter();
static void AddMediaRecorder(MediaRecorder *aRecorder)
{
GetRecorders().AppendElement(aRecorder);
}
static void RemoveMediaRecorder(MediaRecorder *aRecorder)
{
RecordersArray& recorders = GetRecorders();
recorders.RemoveElement(aRecorder);
if (recorders.IsEmpty()) {
sUniqueInstance = nullptr;
}
}
NS_METHOD
CollectReports(nsIHandleReportCallback* aHandleReport,
nsISupports* aData, bool aAnonymize)
{
int64_t amount = 0;
RecordersArray& recorders = GetRecorders();
for (size_t i = 0; i < recorders.Length(); ++i) {
amount += recorders[i]->SizeOfExcludingThis(MallocSizeOf);
}
#define MEMREPORT(_path, _amount, _desc) \
do { \
nsresult rv; \
rv = aHandleReport->Callback(EmptyCString(), NS_LITERAL_CSTRING(_path), \
KIND_HEAP, UNITS_BYTES, _amount, \
NS_LITERAL_CSTRING(_desc), aData); \
NS_ENSURE_SUCCESS(rv, rv); \
} while (0)
MEMREPORT("explicit/media/recorder", amount,
"Memory used by media recorder.");
return NS_OK;
}
private:
MOZ_DEFINE_MALLOC_SIZE_OF(MallocSizeOf)
virtual ~MediaRecorderReporter();
static StaticRefPtr<MediaRecorderReporter> sUniqueInstance;
typedef nsTArray<MediaRecorder*> RecordersArray;
static RecordersArray& GetRecorders()
{
return UniqueInstance()->mRecorders;
}
RecordersArray mRecorders;
};
NS_IMPL_ISUPPORTS(MediaRecorderReporter, nsIMemoryReporter);
NS_IMPL_CYCLE_COLLECTION_INHERITED(MediaRecorder, DOMEventTargetHelper,
mDOMStream, mAudioNode)
@ -332,6 +400,14 @@ public:
return false;
}
size_t
SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const
{
size_t amount = mEncoder->SizeOfExcludingThis(aMallocSizeOf);
return amount;
}
private:
// Only DestroyRunnable is allowed to delete Session object.
virtual ~Session()
@ -681,7 +757,7 @@ MediaRecorder::Start(const Optional<int32_t>& aTimeSlice, ErrorResult& aResult)
timeSlice = aTimeSlice.Value();
}
MediaRecorderReporter::AddMediaRecorder(this);
mState = RecordingState::Recording;
// Start a session.
mSessions.AppendElement();
@ -693,6 +769,7 @@ void
MediaRecorder::Stop(ErrorResult& aResult)
{
LOG(PR_LOG_DEBUG, ("MediaRecorder.Stop %p", this));
MediaRecorderReporter::RemoveMediaRecorder(this);
if (mState == RecordingState::Inactive) {
aResult.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
return;
@ -988,5 +1065,36 @@ MediaRecorder::GetSourcePrincipal()
return doc ? doc->NodePrincipal() : nullptr;
}
size_t
MediaRecorder::SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const
{
size_t amount = 42;
for (size_t i = 0; i < mSessions.Length(); ++i) {
amount += mSessions[i]->SizeOfExcludingThis(aMallocSizeOf);
}
return amount;
}
StaticRefPtr<MediaRecorderReporter> MediaRecorderReporter::sUniqueInstance;
MediaRecorderReporter* MediaRecorderReporter::UniqueInstance()
{
if (!sUniqueInstance) {
sUniqueInstance = new MediaRecorderReporter();
sUniqueInstance->InitMemoryReporter();
}
return sUniqueInstance;
}
void MediaRecorderReporter::InitMemoryReporter()
{
RegisterWeakMemoryReporter(this);
}
MediaRecorderReporter::~MediaRecorderReporter()
{
UnregisterWeakMemoryReporter(this);
}
}
}

View File

@ -9,6 +9,7 @@
#include "mozilla/dom/MediaRecorderBinding.h"
#include "mozilla/DOMEventTargetHelper.h"
#include "mozilla/MemoryReporting.h"
#include "nsIDocumentActivity.h"
// Max size for allowing queue encoded data in memory
@ -90,6 +91,11 @@ public:
const MediaRecorderOptions& aInitDict,
ErrorResult& aRv);
/*
* Measure the size of the buffer, and memory occupied by mAudioEncoder
* and mVideoEncoder
*/
size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const;
// EventHandler
IMPL_EVENT_HANDLER(dataavailable)
IMPL_EVENT_HANDLER(error)

View File

@ -8,6 +8,7 @@
#include "nsMimeTypes.h"
#include "prlog.h"
#include "mozilla/Preferences.h"
#include "mozilla/StaticPtr.h"
#include "OggWriter.h"
#ifdef MOZ_OPUS
@ -204,6 +205,9 @@ MediaEncoder::GetEncodedData(nsTArray<nsTArray<uint8_t> >* aOutputBufs,
rv = mWriter->GetContainerData(aOutputBufs,
ContainerWriter::GET_HEADER);
if (aOutputBufs != nullptr) {
mSizeOfBuffer = aOutputBufs->SizeOfExcludingThis(MallocSizeOf);
}
if (NS_FAILED(rv)) {
LOG(PR_LOG_ERROR,("Error! writer fail to generate header!"));
mState = ENCODE_ERROR;
@ -236,6 +240,9 @@ MediaEncoder::GetEncodedData(nsTArray<nsTArray<uint8_t> >* aOutputBufs,
rv = mWriter->GetContainerData(aOutputBufs,
isAudioCompleted && isVideoCompleted ?
ContainerWriter::FLUSH_NEEDED : 0);
if (aOutputBufs != nullptr) {
mSizeOfBuffer = aOutputBufs->SizeOfExcludingThis(MallocSizeOf);
}
if (NS_SUCCEEDED(rv)) {
// Successfully get the copy of final container data from writer.
reloop = false;
@ -250,6 +257,7 @@ MediaEncoder::GetEncodedData(nsTArray<nsTArray<uint8_t> >* aOutputBufs,
case ENCODE_DONE:
case ENCODE_ERROR:
LOG(PR_LOG_DEBUG, ("MediaEncoder has been shutdown."));
mSizeOfBuffer = 0;
mShutdown = true;
reloop = false;
break;
@ -323,4 +331,21 @@ MediaEncoder::IsOMXEncoderEnabled()
}
#endif
/*
* SizeOfExcludingThis measures memory being used by the Media Encoder.
* Currently it measures the size of the Encoder buffer and memory occupied
* by mAudioEncoder and mVideoEncoder.
*/
size_t
MediaEncoder::SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const
{
size_t amount = 0;
if (mState == ENCODE_TRACK) {
amount = mSizeOfBuffer +
(mAudioEncoder != nullptr ? mAudioEncoder->SizeOfExcludingThis(aMallocSizeOf) : 0) +
(mVideoEncoder != nullptr ? mVideoEncoder->SizeOfExcludingThis(aMallocSizeOf) : 0);
}
return amount;
}
}

View File

@ -10,6 +10,8 @@
#include "TrackEncoder.h"
#include "ContainerWriter.h"
#include "MediaStreamGraph.h"
#include "nsIMemoryReporter.h"
#include "mozilla/MemoryReporting.h"
namespace mozilla {
@ -66,6 +68,7 @@ public :
, mVideoEncoder(aVideoEncoder)
, mStartTime(TimeStamp::Now())
, mMIMEType(aMIMEType)
, mSizeOfBuffer(0)
, mState(MediaEncoder::ENCODE_METADDATA)
, mShutdown(false)
{}
@ -140,6 +143,13 @@ public :
static bool IsOMXEncoderEnabled();
#endif
MOZ_DEFINE_MALLOC_SIZE_OF(MallocSizeOf)
/*
* Measure the size of the buffer, and memory occupied by mAudioEncoder
* and mVideoEncoder
*/
size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const;
private:
// Get encoded data from trackEncoder and write to muxer
nsresult WriteEncodedDataToMuxer(TrackEncoder *aTrackEncoder);
@ -150,6 +160,7 @@ private:
nsAutoPtr<VideoTrackEncoder> mVideoEncoder;
TimeStamp mStartTime;
nsString mMIMEType;
int64_t mSizeOfBuffer;
int mState;
bool mShutdown;
// Get duration from create encoder, for logging purpose

View File

@ -173,6 +173,12 @@ AudioTrackEncoder::DeInterleaveTrackData(AudioDataValue* aInput,
}
}
size_t
AudioTrackEncoder::SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const
{
return mRawSegment.SizeOfExcludingThis(aMallocSizeOf);
}
void
VideoTrackEncoder::NotifyQueuedTrackChanges(MediaStreamGraph* aGraph,
TrackID aID,
@ -263,4 +269,10 @@ VideoTrackEncoder::NotifyEndOfStream()
mReentrantMonitor.NotifyAll();
}
size_t
VideoTrackEncoder::SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const
{
return mRawSegment.SizeOfExcludingThis(aMallocSizeOf);
}
}

View File

@ -168,6 +168,10 @@ public:
*/
static void DeInterleaveTrackData(AudioDataValue* aInput, int32_t aDuration,
int32_t aChannels, AudioDataValue* aOutput);
/**
* Measure size of mRawSegment
*/
size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const;
protected:
/**
@ -241,6 +245,10 @@ public:
TrackTicks aTrackOffset,
uint32_t aTrackEvents,
const MediaSegment& aQueuedMedia) MOZ_OVERRIDE;
/**
* Measure size of mRawSegment
*/
size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const;
protected:
/**

View File

@ -0,0 +1,281 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* 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 "ContainerParser.h"
#include "WebMBufferedParser.h"
#include "mozilla/Endian.h"
#include "mp4_demuxer/BufferStream.h"
#include "mp4_demuxer/MoofParser.h"
#include "prlog.h"
#ifdef PR_LOGGING
extern PRLogModuleInfo* GetMediaSourceLog();
extern PRLogModuleInfo* GetMediaSourceAPILog();
#define MSE_DEBUG(...) PR_LOG(GetMediaSourceLog(), PR_LOG_DEBUG, (__VA_ARGS__))
#define MSE_DEBUGV(...) PR_LOG(GetMediaSourceLog(), PR_LOG_DEBUG+1, (__VA_ARGS__))
#define MSE_API(...) PR_LOG(GetMediaSourceAPILog(), PR_LOG_DEBUG, (__VA_ARGS__))
#else
#define MSE_DEBUG(...)
#define MSE_DEBUGV(...)
#define MSE_API(...)
#endif
namespace mozilla {
bool
ContainerParser::IsInitSegmentPresent(const uint8_t* aData, uint32_t aLength)
{
MSE_DEBUG("ContainerParser(%p)::IsInitSegmentPresent aLength=%u [%x%x%x%x]",
this, aLength,
aLength > 0 ? aData[0] : 0,
aLength > 1 ? aData[1] : 0,
aLength > 2 ? aData[2] : 0,
aLength > 3 ? aData[3] : 0);
return false;
}
bool
ContainerParser::IsMediaSegmentPresent(const uint8_t* aData, uint32_t aLength)
{
MSE_DEBUG("ContainerParser(%p)::IsMediaSegmentPresent aLength=%u [%x%x%x%x]",
this, aLength,
aLength > 0 ? aData[0] : 0,
aLength > 1 ? aData[1] : 0,
aLength > 2 ? aData[2] : 0,
aLength > 3 ? aData[3] : 0);
return false;
}
bool
ContainerParser::ParseStartAndEndTimestamps(const uint8_t* aData, uint32_t aLength,
int64_t& aStart, int64_t& aEnd)
{
return false;
}
bool
ContainerParser::TimestampsFuzzyEqual(int64_t aLhs, int64_t aRhs)
{
NS_WARNING("Using default ContainerParser::TimestampFuzzyEquals implementation");
return aLhs == aRhs;
}
const nsTArray<uint8_t>&
ContainerParser::InitData()
{
MOZ_ASSERT(mHasInitData);
return mInitData;
}
class WebMContainerParser : public ContainerParser {
public:
WebMContainerParser()
: mParser(0), mOffset(0)
{}
static const unsigned NS_PER_USEC = 1000;
bool IsInitSegmentPresent(const uint8_t* aData, uint32_t aLength)
{
ContainerParser::IsInitSegmentPresent(aData, aLength);
// XXX: This is overly primitive, needs to collect data as it's appended
// to the SB and handle, rather than assuming everything is present in a
// single aData segment.
// 0x1a45dfa3 // EBML
// ...
// DocType == "webm"
// ...
// 0x18538067 // Segment (must be "unknown" size)
// 0x1549a966 // -> Segment Info
// 0x1654ae6b // -> One or more Tracks
if (aLength >= 4 &&
aData[0] == 0x1a && aData[1] == 0x45 && aData[2] == 0xdf && aData[3] == 0xa3) {
return true;
}
return false;
}
bool IsMediaSegmentPresent(const uint8_t* aData, uint32_t aLength)
{
ContainerParser::IsMediaSegmentPresent(aData, aLength);
// XXX: This is overly primitive, needs to collect data as it's appended
// to the SB and handle, rather than assuming everything is present in a
// single aData segment.
// 0x1a45dfa3 // EBML
// ...
// DocType == "webm"
// ...
// 0x18538067 // Segment (must be "unknown" size)
// 0x1549a966 // -> Segment Info
// 0x1654ae6b // -> One or more Tracks
if (aLength >= 4 &&
aData[0] == 0x1f && aData[1] == 0x43 && aData[2] == 0xb6 && aData[3] == 0x75) {
return true;
}
return false;
}
bool ParseStartAndEndTimestamps(const uint8_t* aData, uint32_t aLength,
int64_t& aStart, int64_t& aEnd)
{
bool initSegment = IsInitSegmentPresent(aData, aLength);
if (initSegment) {
mOffset = 0;
mParser = WebMBufferedParser(0);
mOverlappedMapping.Clear();
}
// XXX if it only adds new mappings, overlapped but not available
// (e.g. overlap < 0) frames are "lost" from the reported mappings here.
nsTArray<WebMTimeDataOffset> mapping;
mapping.AppendElements(mOverlappedMapping);
mOverlappedMapping.Clear();
ReentrantMonitor dummy("dummy");
mParser.Append(aData, aLength, mapping, dummy);
// XXX This is a bit of a hack. Assume if there are no timecodes
// present and it's an init segment that it's _just_ an init segment.
// We should be more precise.
if (initSegment) {
uint32_t length = aLength;
if (!mapping.IsEmpty()) {
length = mapping[0].mSyncOffset;
MOZ_ASSERT(length <= aLength);
}
MSE_DEBUG("WebMContainerParser(%p)::ParseStartAndEndTimestamps: Stashed init of %u bytes.",
this, length);
mInitData.ReplaceElementsAt(0, mInitData.Length(), aData, length);
mHasInitData = true;
}
mOffset += aLength;
if (mapping.IsEmpty()) {
return false;
}
// Exclude frames that we don't enough data to cover the end of.
uint32_t endIdx = mapping.Length() - 1;
while (mOffset < mapping[endIdx].mEndOffset && endIdx > 0) {
endIdx -= 1;
}
if (endIdx == 0) {
return false;
}
uint64_t frameDuration = mapping[endIdx].mTimecode - mapping[endIdx - 1].mTimecode;
aStart = mapping[0].mTimecode / NS_PER_USEC;
aEnd = (mapping[endIdx].mTimecode + frameDuration) / NS_PER_USEC;
MSE_DEBUG("WebMContainerParser(%p)::ParseStartAndEndTimestamps: [%lld, %lld] [fso=%lld, leo=%lld, l=%u endIdx=%u]",
this, aStart, aEnd, mapping[0].mSyncOffset, mapping[endIdx].mEndOffset, mapping.Length(), endIdx);
mapping.RemoveElementsAt(0, endIdx + 1);
mOverlappedMapping.AppendElements(mapping);
return true;
}
bool TimestampsFuzzyEqual(int64_t aLhs, int64_t aRhs)
{
int64_t error = mParser.GetTimecodeScale() / NS_PER_USEC;
return llabs(aLhs - aRhs) <= error * 2;
}
private:
WebMBufferedParser mParser;
nsTArray<WebMTimeDataOffset> mOverlappedMapping;
int64_t mOffset;
};
class MP4ContainerParser : public ContainerParser {
public:
MP4ContainerParser() {}
bool IsInitSegmentPresent(const uint8_t* aData, uint32_t aLength)
{
ContainerParser::IsInitSegmentPresent(aData, aLength);
// Each MP4 atom has a chunk size and chunk type. The root chunk in an MP4
// file is the 'ftyp' atom followed by a file type. We just check for a
// vaguely valid 'ftyp' atom.
if (aLength < 8) {
return false;
}
uint32_t chunk_size = BigEndian::readUint32(aData);
if (chunk_size < 8) {
return false;
}
return aData[4] == 'f' && aData[5] == 't' && aData[6] == 'y' &&
aData[7] == 'p';
}
bool ParseStartAndEndTimestamps(const uint8_t* aData, uint32_t aLength,
int64_t& aStart, int64_t& aEnd)
{
bool initSegment = IsInitSegmentPresent(aData, aLength);
if (initSegment) {
mStream = new mp4_demuxer::BufferStream();
mParser = new mp4_demuxer::MoofParser(mStream, 0);
} else if (!mStream || !mParser) {
return false;
}
mStream->AppendBytes(aData, aLength);
nsTArray<MediaByteRange> byteRanges;
byteRanges.AppendElement(mStream->GetByteRange());
mParser->RebuildFragmentedIndex(byteRanges);
if (initSegment) {
const MediaByteRange& range = mParser->mInitRange;
MSE_DEBUG("MP4ContainerParser(%p)::ParseStartAndEndTimestamps: Stashed init of %u bytes.",
this, range.mEnd - range.mStart);
mInitData.ReplaceElementsAt(0, mInitData.Length(),
aData + range.mStart,
range.mEnd - range.mStart);
mHasInitData = true;
}
mp4_demuxer::Interval<mp4_demuxer::Microseconds> compositionRange =
mParser->GetCompositionRange(byteRanges);
mStream->DiscardBefore(mParser->mOffset);
if (compositionRange.IsNull()) {
return false;
}
aStart = compositionRange.start;
aEnd = compositionRange.end;
MSE_DEBUG("MP4ContainerParser(%p)::ParseStartAndEndTimestamps: [%lld, %lld]",
this, aStart, aEnd);
return true;
}
private:
nsRefPtr<mp4_demuxer::BufferStream> mStream;
nsAutoPtr<mp4_demuxer::MoofParser> mParser;
};
/*static*/ ContainerParser*
ContainerParser::CreateForMIMEType(const nsACString& aType)
{
if (aType.LowerCaseEqualsLiteral("video/webm") || aType.LowerCaseEqualsLiteral("audio/webm")) {
return new WebMContainerParser();
}
if (aType.LowerCaseEqualsLiteral("video/mp4") || aType.LowerCaseEqualsLiteral("audio/mp4")) {
return new MP4ContainerParser();
}
return new ContainerParser();
}
} // namespace mozilla

View File

@ -0,0 +1,55 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* 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 MOZILLA_CONTAINERPARSER_H_
#define MOZILLA_CONTAINERPARSER_H_
#include "nsTArray.h"
namespace mozilla {
class ContainerParser {
public:
ContainerParser() : mHasInitData(false) {}
virtual ~ContainerParser() {}
// Return true if aData starts with an initialization segment.
// The base implementation exists only for debug logging and is expected
// to be called first from the overriding implementation.
virtual bool IsInitSegmentPresent(const uint8_t* aData, uint32_t aLength);
// Return true if aData starts with a media segment.
// The base implementation exists only for debug logging and is expected
// to be called first from the overriding implementation.
virtual bool IsMediaSegmentPresent(const uint8_t* aData, uint32_t aLength);
// Parse aData to extract the start and end frame times from the media
// segment. aData may not start on a parser sync boundary. Return true
// if aStart and aEnd have been updated.
virtual bool ParseStartAndEndTimestamps(const uint8_t* aData, uint32_t aLength,
int64_t& aStart, int64_t& aEnd);
// Compare aLhs and rHs, considering any error that may exist in the
// timestamps from the format's base representation. Return true if aLhs
// == aRhs within the error epsilon.
virtual bool TimestampsFuzzyEqual(int64_t aLhs, int64_t aRhs);
const nsTArray<uint8_t>& InitData();
bool HasInitData()
{
return mHasInitData;
}
static ContainerParser* CreateForMIMEType(const nsACString& aType);
protected:
nsTArray<uint8_t> mInitData;
bool mHasInitData;
};
} // namespace mozilla
#endif /* MOZILLA_CONTAINERPARSER_H_ */

View File

@ -9,15 +9,11 @@
#include "AsyncEventRunner.h"
#include "MediaSourceUtils.h"
#include "TrackBuffer.h"
#include "WebMBufferedParser.h"
#include "mozilla/Endian.h"
#include "mozilla/ErrorResult.h"
#include "mozilla/FloatingPoint.h"
#include "mozilla/Preferences.h"
#include "mozilla/dom/MediaSourceBinding.h"
#include "mozilla/dom/TimeRanges.h"
#include "mp4_demuxer/BufferStream.h"
#include "mp4_demuxer/MoofParser.h"
#include "nsError.h"
#include "nsIEventTarget.h"
#include "nsIRunnable.h"
@ -42,273 +38,6 @@ extern PRLogModuleInfo* GetMediaSourceAPILog();
namespace mozilla {
class ContainerParser {
public:
virtual ~ContainerParser() {}
// Return true if aData starts with an initialization segment.
// The base implementation exists only for debug logging and is expected
// to be called first from the overriding implementation.
virtual bool IsInitSegmentPresent(const uint8_t* aData, uint32_t aLength)
{
MSE_DEBUG("ContainerParser(%p)::IsInitSegmentPresent aLength=%u [%x%x%x%x]",
this, aLength,
aLength > 0 ? aData[0] : 0,
aLength > 1 ? aData[1] : 0,
aLength > 2 ? aData[2] : 0,
aLength > 3 ? aData[3] : 0);
return false;
}
// Return true if aData starts with a media segment.
// The base implementation exists only for debug logging and is expected
// to be called first from the overriding implementation.
virtual bool IsMediaSegmentPresent(const uint8_t* aData, uint32_t aLength)
{
MSE_DEBUG("ContainerParser(%p)::IsMediaSegmentPresent aLength=%u [%x%x%x%x]",
this, aLength,
aLength > 0 ? aData[0] : 0,
aLength > 1 ? aData[1] : 0,
aLength > 2 ? aData[2] : 0,
aLength > 3 ? aData[3] : 0);
return false;
}
// Parse aData to extract the start and end frame times from the media
// segment. aData may not start on a parser sync boundary. Return true
// if aStart and aEnd have been updated.
virtual bool ParseStartAndEndTimestamps(const uint8_t* aData, uint32_t aLength,
int64_t& aStart, int64_t& aEnd)
{
return false;
}
// Compare aLhs and rHs, considering any error that may exist in the
// timestamps from the format's base representation. Return true if aLhs
// == aRhs within the error epsilon.
virtual bool TimestampsFuzzyEqual(int64_t aLhs, int64_t aRhs)
{
NS_WARNING("Using default ContainerParser::TimestampFuzzyEquals implementation");
return aLhs == aRhs;
}
virtual const nsTArray<uint8_t>& InitData()
{
MOZ_ASSERT(mInitData.Length() > 0);
return mInitData;
}
static ContainerParser* CreateForMIMEType(const nsACString& aType);
protected:
nsTArray<uint8_t> mInitData;
};
class WebMContainerParser : public ContainerParser {
public:
WebMContainerParser()
: mParser(0), mOffset(0)
{}
static const unsigned NS_PER_USEC = 1000;
bool IsInitSegmentPresent(const uint8_t* aData, uint32_t aLength)
{
ContainerParser::IsInitSegmentPresent(aData, aLength);
// XXX: This is overly primitive, needs to collect data as it's appended
// to the SB and handle, rather than assuming everything is present in a
// single aData segment.
// 0x1a45dfa3 // EBML
// ...
// DocType == "webm"
// ...
// 0x18538067 // Segment (must be "unknown" size)
// 0x1549a966 // -> Segment Info
// 0x1654ae6b // -> One or more Tracks
if (aLength >= 4 &&
aData[0] == 0x1a && aData[1] == 0x45 && aData[2] == 0xdf && aData[3] == 0xa3) {
return true;
}
return false;
}
bool IsMediaSegmentPresent(const uint8_t* aData, uint32_t aLength)
{
ContainerParser::IsMediaSegmentPresent(aData, aLength);
// XXX: This is overly primitive, needs to collect data as it's appended
// to the SB and handle, rather than assuming everything is present in a
// single aData segment.
// 0x1a45dfa3 // EBML
// ...
// DocType == "webm"
// ...
// 0x18538067 // Segment (must be "unknown" size)
// 0x1549a966 // -> Segment Info
// 0x1654ae6b // -> One or more Tracks
if (aLength >= 4 &&
aData[0] == 0x1f && aData[1] == 0x43 && aData[2] == 0xb6 && aData[3] == 0x75) {
return true;
}
return false;
}
bool ParseStartAndEndTimestamps(const uint8_t* aData, uint32_t aLength,
int64_t& aStart, int64_t& aEnd)
{
bool initSegment = IsInitSegmentPresent(aData, aLength);
if (initSegment) {
mOffset = 0;
mParser = WebMBufferedParser(0);
mOverlappedMapping.Clear();
}
// XXX if it only adds new mappings, overlapped but not available
// (e.g. overlap < 0) frames are "lost" from the reported mappings here.
nsTArray<WebMTimeDataOffset> mapping;
mapping.AppendElements(mOverlappedMapping);
mOverlappedMapping.Clear();
ReentrantMonitor dummy("dummy");
mParser.Append(aData, aLength, mapping, dummy);
// XXX This is a bit of a hack. Assume if there are no timecodes
// present and it's an init segment that it's _just_ an init segment.
// We should be more precise.
if (initSegment) {
uint32_t length = aLength;
if (!mapping.IsEmpty()) {
length = mapping[0].mSyncOffset;
MOZ_ASSERT(length <= aLength);
}
MSE_DEBUG("WebMContainerParser(%p)::ParseStartAndEndTimestamps: Stashed init of %u bytes.",
this, length);
mInitData.ReplaceElementsAt(0, mInitData.Length(), aData, length);
}
mOffset += aLength;
if (mapping.IsEmpty()) {
return false;
}
// Exclude frames that we don't enough data to cover the end of.
uint32_t endIdx = mapping.Length() - 1;
while (mOffset < mapping[endIdx].mEndOffset && endIdx > 0) {
endIdx -= 1;
}
if (endIdx == 0) {
return false;
}
uint64_t frameDuration = mapping[endIdx].mTimecode - mapping[endIdx - 1].mTimecode;
aStart = mapping[0].mTimecode / NS_PER_USEC;
aEnd = (mapping[endIdx].mTimecode + frameDuration) / NS_PER_USEC;
MSE_DEBUG("WebMContainerParser(%p)::ParseStartAndEndTimestamps: [%lld, %lld] [fso=%lld, leo=%lld, l=%u endIdx=%u]",
this, aStart, aEnd, mapping[0].mSyncOffset, mapping[endIdx].mEndOffset, mapping.Length(), endIdx);
mapping.RemoveElementsAt(0, endIdx + 1);
mOverlappedMapping.AppendElements(mapping);
return true;
}
bool TimestampsFuzzyEqual(int64_t aLhs, int64_t aRhs)
{
int64_t error = mParser.GetTimecodeScale() / NS_PER_USEC;
return llabs(aLhs - aRhs) <= error * 2;
}
private:
WebMBufferedParser mParser;
nsTArray<WebMTimeDataOffset> mOverlappedMapping;
int64_t mOffset;
};
class MP4ContainerParser : public ContainerParser {
public:
MP4ContainerParser() {}
bool IsInitSegmentPresent(const uint8_t* aData, uint32_t aLength)
{
ContainerParser::IsInitSegmentPresent(aData, aLength);
// Each MP4 atom has a chunk size and chunk type. The root chunk in an MP4
// file is the 'ftyp' atom followed by a file type. We just check for a
// vaguely valid 'ftyp' atom.
if (aLength < 8) {
return false;
}
uint32_t chunk_size = BigEndian::readUint32(aData);
if (chunk_size < 8) {
return false;
}
return aData[4] == 'f' && aData[5] == 't' && aData[6] == 'y' &&
aData[7] == 'p';
}
bool ParseStartAndEndTimestamps(const uint8_t* aData, uint32_t aLength,
int64_t& aStart, int64_t& aEnd)
{
bool initSegment = IsInitSegmentPresent(aData, aLength);
if (initSegment) {
mStream = new mp4_demuxer::BufferStream();
mParser = new mp4_demuxer::MoofParser(mStream, 0);
} else if (!mStream || !mParser) {
return false;
}
mStream->AppendBytes(aData, aLength);
nsTArray<MediaByteRange> byteRanges;
byteRanges.AppendElement(mStream->GetByteRange());
mParser->RebuildFragmentedIndex(byteRanges);
if (initSegment) {
const MediaByteRange& range = mParser->mInitRange;
MSE_DEBUG("MP4ContainerParser(%p)::ParseStartAndEndTimestamps: Stashed init of %u bytes.",
this, range.mEnd - range.mStart);
mInitData.ReplaceElementsAt(0, mInitData.Length(),
aData + range.mStart,
range.mEnd - range.mStart);
}
mp4_demuxer::Interval<mp4_demuxer::Microseconds> compositionRange =
mParser->GetCompositionRange(byteRanges);
mStream->DiscardBefore(mParser->mOffset);
if (compositionRange.IsNull()) {
return false;
}
aStart = compositionRange.start;
aEnd = compositionRange.end;
MSE_DEBUG("MP4ContainerParser(%p)::ParseStartAndEndTimestamps: [%lld, %lld]",
this, aStart, aEnd);
return true;
}
private:
nsRefPtr<mp4_demuxer::BufferStream> mStream;
nsAutoPtr<mp4_demuxer::MoofParser> mParser;
};
/*static*/ ContainerParser*
ContainerParser::CreateForMIMEType(const nsACString& aType)
{
if (aType.LowerCaseEqualsLiteral("video/webm") || aType.LowerCaseEqualsLiteral("audio/webm")) {
return new WebMContainerParser();
}
if (aType.LowerCaseEqualsLiteral("video/mp4") || aType.LowerCaseEqualsLiteral("audio/mp4")) {
return new MP4ContainerParser();
}
return new ContainerParser();
}
namespace dom {
void
@ -497,7 +226,6 @@ SourceBuffer::Ended()
SourceBuffer::SourceBuffer(MediaSource* aMediaSource, const nsACString& aType)
: DOMEventTargetHelper(aMediaSource->GetParentObject())
, mMediaSource(aMediaSource)
, mType(aType)
, mAppendWindowStart(0)
, mAppendWindowEnd(PositiveInfinity<double>())
, mTimestampOffset(0)
@ -508,10 +236,9 @@ SourceBuffer::SourceBuffer(MediaSource* aMediaSource, const nsACString& aType)
MOZ_ASSERT(aMediaSource);
mEvictionThreshold = Preferences::GetUint("media.mediasource.eviction_threshold",
75 * (1 << 20));
mParser = ContainerParser::CreateForMIMEType(aType);
mTrackBuffer = new TrackBuffer(aMediaSource->GetDecoder(), aType);
MSE_DEBUG("SourceBuffer(%p)::SourceBuffer: Create mParser=%p mTrackBuffer=%p",
this, mParser.get(), mTrackBuffer.get());
MSE_DEBUG("SourceBuffer(%p)::SourceBuffer: Create mTrackBuffer=%p",
this, mTrackBuffer.get());
}
SourceBuffer::~SourceBuffer()
@ -586,52 +313,7 @@ SourceBuffer::AppendData(const uint8_t* aData, uint32_t aLength, ErrorResult& aR
return;
}
StartUpdating();
// TODO: Run more of the buffer append algorithm asynchronously.
if (mParser->IsInitSegmentPresent(aData, aLength)) {
MSE_DEBUG("SourceBuffer(%p)::AppendData: New initialization segment.", this);
mMediaSource->QueueInitializationEvent();
mTrackBuffer->DiscardDecoder();
if (!mTrackBuffer->NewDecoder()) {
aRv.Throw(NS_ERROR_FAILURE); // XXX: Review error handling.
return;
}
MSE_DEBUG("SourceBuffer(%p)::AppendData: Decoder marked as initialized.", this);
} else if (!mTrackBuffer->HasInitSegment()) {
MSE_DEBUG("SourceBuffer(%p)::AppendData: Non-init segment appended during initialization.", this);
Optional<MediaSourceEndOfStreamError> decodeError(MediaSourceEndOfStreamError::Decode);
ErrorResult dummy;
mMediaSource->EndOfStream(decodeError, dummy);
aRv.Throw(NS_ERROR_FAILURE);
return;
}
int64_t start, end;
if (mParser->ParseStartAndEndTimestamps(aData, aLength, start, end)) {
int64_t lastStart, lastEnd;
mTrackBuffer->LastTimestamp(lastStart, lastEnd);
if (mParser->IsMediaSegmentPresent(aData, aLength) &&
!mParser->TimestampsFuzzyEqual(start, lastEnd)) {
MSE_DEBUG("SourceBuffer(%p)::AppendData: Data last=[%lld, %lld] overlaps [%lld, %lld]",
this, lastStart, lastEnd, start, end);
// This data is earlier in the timeline than data we have already
// processed, so we must create a new decoder to handle the decoding.
mTrackBuffer->DiscardDecoder();
// If we've got a decoder here, it's not initialized, so we can use it
// rather than creating a new one.
if (!mTrackBuffer->NewDecoder()) {
aRv.Throw(NS_ERROR_FAILURE); // XXX: Review error handling.
return;
}
MSE_DEBUG("SourceBuffer(%p)::AppendData: Decoder marked as initialized.", this);
const nsTArray<uint8_t>& initData = mParser->InitData();
mTrackBuffer->AppendData(initData.Elements(), initData.Length());
mTrackBuffer->SetLastStartTimestamp(start);
}
mTrackBuffer->SetLastEndTimestamp(end);
MSE_DEBUG("SourceBuffer(%p)::AppendData: Segment last=[%lld, %lld] [%lld, %lld]",
this, lastStart, lastEnd, start, end);
}
if (!mTrackBuffer->AppendData(aData, aLength)) {
Optional<MediaSourceEndOfStreamError> decodeError(MediaSourceEndOfStreamError::Decode);
ErrorResult dummy;
@ -640,9 +322,9 @@ SourceBuffer::AppendData(const uint8_t* aData, uint32_t aLength, ErrorResult& aR
return;
}
// Schedule the state machine thread to ensure playback starts
// if required when data is appended.
mMediaSource->GetDecoder()->ScheduleStateMachineThread();
if (mTrackBuffer->HasInitSegment()) {
mMediaSource->QueueInitializationEvent();
}
// Run the final step of the buffer append algorithm asynchronously to
// ensure the SourceBuffer's updating flag transition behaves as required

View File

@ -28,7 +28,6 @@ struct JSContext;
namespace mozilla {
class ContainerParser;
class ErrorResult;
class TrackBuffer;
template <typename T> class AsyncEventRunner;
@ -120,13 +119,6 @@ private:
void DispatchSimpleEvent(const char* aName);
void QueueAsyncSimpleEvent(const char* aName);
// Create a new decoder for mType, and store the result in mDecoder.
// Returns true if mDecoder was set.
bool InitNewDecoder();
// Set mDecoder to null and reset mDecoderInitialized.
void DiscardDecoder();
// Update mUpdating and fire the appropriate events.
void StartUpdating();
void StopUpdating();
@ -141,12 +133,8 @@ private:
nsRefPtr<MediaSource> mMediaSource;
const nsCString mType;
uint32_t mEvictionThreshold;
nsAutoPtr<ContainerParser> mParser;
nsRefPtr<TrackBuffer> mTrackBuffer;
double mAppendWindowStart;

View File

@ -6,27 +6,19 @@
#include "TrackBuffer.h"
#include "ContainerParser.h"
#include "MediaSourceDecoder.h"
#include "SharedThreadPool.h"
#include "MediaTaskQueue.h"
#include "SourceBufferDecoder.h"
#include "SourceBufferResource.h"
#include "VideoUtils.h"
#include "mozilla/FloatingPoint.h"
#include "mozilla/dom/TimeRanges.h"
#include "nsError.h"
#include "nsIRunnable.h"
#include "nsThreadUtils.h"
#include "prlog.h"
#if defined(DEBUG)
#include <sys/stat.h>
#include <sys/types.h>
#endif
struct JSContext;
class JSObject;
#ifdef PR_LOGGING
extern PRLogModuleInfo* GetMediaSourceLog();
extern PRLogModuleInfo* GetMediaSourceAPILog();
@ -47,9 +39,9 @@ TrackBuffer::TrackBuffer(MediaSourceDecoder* aParentDecoder, const nsACString& a
, mType(aType)
, mLastStartTimestamp(0)
, mLastEndTimestamp(0)
, mHasInit(false)
{
MOZ_COUNT_CTOR(TrackBuffer);
mParser = ContainerParser::CreateForMIMEType(aType);
mTaskQueue = new MediaTaskQueue(GetMediaDecodeThreadPool());
aParentDecoder->AddTrackBuffer(this);
}
@ -102,6 +94,53 @@ TrackBuffer::Shutdown()
bool
TrackBuffer::AppendData(const uint8_t* aData, uint32_t aLength)
{
MOZ_ASSERT(NS_IsMainThread());
// TODO: Run more of the buffer append algorithm asynchronously.
if (mParser->IsInitSegmentPresent(aData, aLength)) {
MSE_DEBUG("TrackBuffer(%p)::AppendData: New initialization segment.", this);
if (!NewDecoder()) {
return false;
}
} else if (!mParser->HasInitData()) {
MSE_DEBUG("TrackBuffer(%p)::AppendData: Non-init segment appended during initialization.", this);
return false;
}
int64_t start, end;
if (mParser->ParseStartAndEndTimestamps(aData, aLength, start, end)) {
if (mParser->IsMediaSegmentPresent(aData, aLength) &&
!mParser->TimestampsFuzzyEqual(start, mLastEndTimestamp)) {
MSE_DEBUG("TrackBuffer(%p)::AppendData: Data last=[%lld, %lld] overlaps [%lld, %lld]",
this, mLastStartTimestamp, mLastEndTimestamp, start, end);
// This data is earlier in the timeline than data we have already
// processed, so we must create a new decoder to handle the decoding.
if (!NewDecoder()) {
return false;
}
MSE_DEBUG("TrackBuffer(%p)::AppendData: Decoder marked as initialized.", this);
const nsTArray<uint8_t>& initData = mParser->InitData();
AppendDataToCurrentResource(initData.Elements(), initData.Length());
mLastStartTimestamp = start;
}
mLastEndTimestamp = end;
MSE_DEBUG("TrackBuffer(%p)::AppendData: Segment last=[%lld, %lld] [%lld, %lld]",
this, mLastStartTimestamp, mLastEndTimestamp, start, end);
}
if (!AppendDataToCurrentResource(aData, aLength)) {
return false;
}
// Schedule the state machine thread to ensure playback starts if required
// when data is appended.
mParentDecoder->ScheduleStateMachineThread();
return true;
}
bool
TrackBuffer::AppendDataToCurrentResource(const uint8_t* aData, uint32_t aLength)
{
MOZ_ASSERT(NS_IsMainThread());
if (!mCurrentDecoder) {
@ -179,7 +218,9 @@ bool
TrackBuffer::NewDecoder()
{
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(!mCurrentDecoder && mParentDecoder);
MOZ_ASSERT(mParentDecoder);
DiscardDecoder();
nsRefPtr<SourceBufferDecoder> decoder = mParentDecoder->CreateSubDecoder(mType);
if (!decoder) {
@ -191,7 +232,6 @@ TrackBuffer::NewDecoder()
mLastStartTimestamp = 0;
mLastEndTimestamp = 0;
mHasInit = true;
return QueueInitializeDecoder(decoder);
}
@ -316,7 +356,7 @@ bool
TrackBuffer::HasInitSegment()
{
ReentrantMonitorAutoEnter mon(mParentDecoder->GetReentrantMonitor());
return mHasInit;
return mParser->HasInitData();
}
bool
@ -324,29 +364,7 @@ TrackBuffer::IsReady()
{
ReentrantMonitorAutoEnter mon(mParentDecoder->GetReentrantMonitor());
MOZ_ASSERT((mInfo.HasAudio() || mInfo.HasVideo()) || mInitializedDecoders.IsEmpty());
return HasInitSegment() && (mInfo.HasAudio() || mInfo.HasVideo());
}
void
TrackBuffer::LastTimestamp(int64_t& aStart, int64_t& aEnd)
{
MOZ_ASSERT(NS_IsMainThread());
aStart = mLastStartTimestamp;
aEnd = mLastEndTimestamp;
}
void
TrackBuffer::SetLastStartTimestamp(int64_t aStart)
{
MOZ_ASSERT(NS_IsMainThread());
mLastStartTimestamp = aStart;
}
void
TrackBuffer::SetLastEndTimestamp(int64_t aEnd)
{
MOZ_ASSERT(NS_IsMainThread());
mLastEndTimestamp = aEnd;
return mParser->HasInitData() && (mInfo.HasAudio() || mInfo.HasVideo());
}
bool

View File

@ -17,6 +17,7 @@
namespace mozilla {
class ContainerParser;
class MediaSourceDecoder;
namespace dom {
@ -45,11 +46,6 @@ public:
// decoders buffered ranges in aRanges.
double Buffered(dom::TimeRanges* aRanges);
// Create a new decoder, set mCurrentDecoder to the new decoder, and queue
// the decoder for initialization. The decoder is not considered
// initialized until it is added to mDecoders.
bool NewDecoder();
// Mark the current decoder's resource as ended, clear mCurrentDecoder and
// reset mLast{Start,End}Timestamp.
void DiscardDecoder();
@ -59,15 +55,10 @@ public:
// Returns true if an init segment has been appended.
bool HasInitSegment();
// Returns true iff HasInitSegment() and the decoder using that init
// Returns true iff mParser->HasInitData() and the decoder using that init
// segment has successfully initialized by setting mHas{Audio,Video}..
bool IsReady();
// Query and update mLast{Start,End}Timestamp.
void LastTimestamp(int64_t& aStart, int64_t& aEnd);
void SetLastStartTimestamp(int64_t aStart);
void SetLastEndTimestamp(int64_t aEnd);
// Returns true if any of the decoders managed by this track buffer
// contain aTime in their buffered ranges.
bool ContainsTime(int64_t aTime);
@ -89,6 +80,15 @@ public:
private:
~TrackBuffer();
// Create a new decoder, set mCurrentDecoder to the new decoder, and queue
// the decoder for initialization. The decoder is not considered
// initialized until it is added to mDecoders.
bool NewDecoder();
// Helper for AppendData, ensures NotifyDataArrived is called whenever
// data is appended to the current decoder's SourceBufferResource.
bool AppendDataToCurrentResource(const uint8_t* aData, uint32_t aLength);
// Queue execution of InitializeDecoder on mTaskQueue.
bool QueueInitializeDecoder(nsRefPtr<SourceBufferDecoder> aDecoder);
@ -112,6 +112,8 @@ private:
// function.
void RemoveDecoder(nsRefPtr<SourceBufferDecoder> aDecoder);
nsAutoPtr<ContainerParser> mParser;
// A task queue using the shared media thread pool. Used exclusively to
// initialize (i.e. call ReadMetadata on) decoders as they are created via
// NewDecoder.
@ -136,10 +138,6 @@ private:
int64_t mLastStartTimestamp;
int64_t mLastEndTimestamp;
// Set when the initialization segment is first seen and cached (implied
// by new decoder creation). Protected by mParentDecoder's monitor.
bool mHasInit;
// Set when the first decoder used by this TrackBuffer is initialized.
// Protected by mParentDecoder's monitor.
MediaInfo mInfo;

View File

@ -17,6 +17,7 @@ EXPORTS.mozilla.dom += [
]
UNIFIED_SOURCES += [
'ContainerParser.cpp',
'MediaSource.cpp',
'MediaSourceDecoder.cpp',
'MediaSourceReader.cpp',

View File

@ -669,10 +669,12 @@ public:
unsigned flags,
JS::MutableHandle<JS::Value> vp) const MOZ_OVERRIDE;
static void ObjectMoved(JSObject *obj, const JSObject *old);
static const nsOuterWindowProxy singleton;
protected:
nsGlobalWindow* GetWindow(JSObject *proxy) const
static nsGlobalWindow* GetWindow(JSObject *proxy)
{
return nsGlobalWindow::FromSupports(
static_cast<nsISupports*>(js::GetProxyExtra(proxy, 0).toPrivate()));
@ -704,7 +706,8 @@ const js::Class OuterWindowProxyClass =
nullptr, /* outerObject */
js::proxy_innerObject,
nullptr, /* iteratorObject */
false /* isWrappedNative */
false, /* isWrappedNative */
nsOuterWindowProxy::ObjectMoved
));
bool
@ -1024,6 +1027,15 @@ nsOuterWindowProxy::unwatch(JSContext *cx, JS::Handle<JSObject*> proxy,
return js::UnwatchGuts(cx, proxy, id);
}
void
nsOuterWindowProxy::ObjectMoved(JSObject *obj, const JSObject *old)
{
nsGlobalWindow* global = GetWindow(obj);
if (global) {
global->UpdateWrapper(obj, old);
}
}
const nsOuterWindowProxy
nsOuterWindowProxy::singleton;

View File

@ -8,6 +8,7 @@
#include "nsCycleCollectionParticipant.h"
#include "mozilla/Assertions.h"
#include "js/Class.h"
#include "js/Id.h" // must come before js/RootingAPI.h
#include "js/Value.h" // must come before js/RootingAPI.h
#include "js/RootingAPI.h"
@ -44,6 +45,14 @@ class XPCWrappedNativeScope;
*
* The finalizer for the wrapper clears the cache.
*
* A compacting GC can move the wrapper object. Pointers to moved objects are
* usually found and updated by tracing the heap, however non-preserved wrappers
* are weak references and are not traced, so another approach is
* necessary. Instead a class hook (objectMovedOp) is provided that is called
* when an object is moved and is responsible for ensuring pointers are
* updated. It does this by calling UpdateWrapper() on the wrapper
* cache. SetWrapper() asserts that the hook is implemented for any wrapper set.
*
* A number of the methods are implemented in nsWrapperCacheInlines.h because we
* have to include some JS headers that don't play nicely with the rest of the
* codebase. Include nsWrapperCacheInlines.h if you need to call those methods.
@ -89,6 +98,8 @@ public:
{
MOZ_ASSERT(!PreservingWrapper(), "Clearing a preserved wrapper!");
MOZ_ASSERT(aWrapper, "Use ClearWrapper!");
MOZ_ASSERT(js::HasObjectMovedOp(aWrapper),
"Object has not provided the hook to update the wrapper if it is moved");
SetWrapperJSObject(aWrapper);
}
@ -104,6 +115,18 @@ public:
SetWrapperJSObject(nullptr);
}
/**
* Update the wrapper if the object it contains is moved.
*
* This method must be called from the objectMovedOp class extension hook for
* any wrapper cached object.
*/
void UpdateWrapper(JSObject* aNewObject, const JSObject* aOldObject)
{
MOZ_ASSERT(mWrapper == aOldObject);
mWrapper = aNewObject;
}
bool PreservingWrapper()
{
return HasWrapperFlag(WRAPPER_BIT_PRESERVED);
@ -162,7 +185,7 @@ public:
}
}
/*
/*
* The following methods for getting and manipulating flags allow the unused
* bits of mFlags to be used by derived classes.
*/

View File

@ -1213,6 +1213,24 @@ ClearWrapper(T* p, void*)
ClearWrapper(p, cache);
}
template<class T>
inline void
UpdateWrapper(T* p, nsWrapperCache* cache, JSObject* obj, const JSObject* old)
{
JS::AutoAssertGCCallback inCallback(obj);
cache->UpdateWrapper(obj, old);
}
template<class T>
inline void
UpdateWrapper(T* p, void*, JSObject* obj, const JSObject* old)
{
JS::AutoAssertGCCallback inCallback(obj);
nsWrapperCache* cache;
CallQueryInterface(p, &cache);
UpdateWrapper(p, cache, obj, old);
}
// Attempt to preserve the wrapper, if any, for a Paris DOM bindings object.
// Return true if we successfully preserved the wrapper, or there is no wrapper
// to preserve. In the latter case we don't need to preserve the wrapper, because

View File

@ -17,6 +17,7 @@ AUTOGENERATED_WARNING_COMMENT = \
"/* THIS FILE IS AUTOGENERATED - DO NOT EDIT */\n\n"
ADDPROPERTY_HOOK_NAME = '_addProperty'
FINALIZE_HOOK_NAME = '_finalize'
OBJECT_MOVED_HOOK_NAME = '_objectMoved'
CONSTRUCT_HOOK_NAME = '_constructor'
LEGACYCALLER_HOOK_NAME = '_legacycaller'
HASINSTANCE_HOOK_NAME = '_hasInstance'
@ -364,48 +365,61 @@ class CGDOMJSClass(CGThing):
def define(self):
traceHook = 'nullptr'
callHook = LEGACYCALLER_HOOK_NAME if self.descriptor.operations["LegacyCaller"] else 'nullptr'
objectMovedHook = OBJECT_MOVED_HOOK_NAME if self.descriptor.wrapperCache else 'nullptr'
slotCount = INSTANCE_RESERVED_SLOTS + self.descriptor.interface.totalMembersInSlots
classFlags = "JSCLASS_IS_DOMJSCLASS | "
classExtensionAndObjectOps = """\
JS_NULL_CLASS_EXT,
JS_NULL_OBJECT_OPS
"""
classExtensionAndObjectOps = fill(
"""
{
nullptr, /* outerObject */
nullptr, /* innerObject */
nullptr, /* iteratorObject */
false, /* isWrappedNative */
nullptr, /* weakmapKeyDelegateOp */
${objectMoved} /* objectMovedOp */
},
JS_NULL_OBJECT_OPS
""",
objectMoved=objectMovedHook)
if self.descriptor.isGlobal():
classFlags += "JSCLASS_DOM_GLOBAL | JSCLASS_GLOBAL_FLAGS_WITH_SLOTS(DOM_GLOBAL_SLOTS) | JSCLASS_IMPLEMENTS_BARRIERS"
traceHook = "JS_GlobalObjectTraceHook"
reservedSlots = "JSCLASS_GLOBAL_APPLICATION_SLOTS"
if not self.descriptor.workers:
classExtensionAndObjectOps = """\
{
nsGlobalWindow::OuterObject, /* outerObject */
nullptr, /* innerObject */
nullptr, /* iteratorObject */
false, /* isWrappedNative */
nullptr /* weakmapKeyDelegateOp */
},
{
nullptr, /* lookupGeneric */
nullptr, /* lookupProperty */
nullptr, /* lookupElement */
nullptr, /* defineGeneric */
nullptr, /* defineProperty */
nullptr, /* defineElement */
nullptr, /* getGeneric */
nullptr, /* getProperty */
nullptr, /* getElement */
nullptr, /* setGeneric */
nullptr, /* setProperty */
nullptr, /* setElement */
nullptr, /* getGenericAttributes */
nullptr, /* setGenericAttributes */
nullptr, /* deleteGeneric */
nullptr, /* watch */
nullptr, /* unwatch */
nullptr, /* slice */
nullptr, /* enumerate */
JS_ObjectToOuterObject /* thisObject */
}
"""
classExtensionAndObjectOps = fill(
"""
{
nsGlobalWindow::OuterObject, /* outerObject */
nullptr, /* innerObject */
nullptr, /* iteratorObject */
false, /* isWrappedNative */
nullptr, /* weakmapKeyDelegateOp */
${objectMoved} /* objectMovedOp */
},
{
nullptr, /* lookupGeneric */
nullptr, /* lookupProperty */
nullptr, /* lookupElement */
nullptr, /* defineGeneric */
nullptr, /* defineProperty */
nullptr, /* defineElement */
nullptr, /* getGeneric */
nullptr, /* getProperty */
nullptr, /* getElement */
nullptr, /* setGeneric */
nullptr, /* setProperty */
nullptr, /* setElement */
nullptr, /* getGenericAttributes */
nullptr, /* setGenericAttributes */
nullptr, /* deleteGeneric */
nullptr, /* watch */
nullptr, /* unwatch */
nullptr, /* slice */
nullptr, /* enumerate */
JS_ObjectToOuterObject /* thisObject */
}
""",
objectMoved=objectMovedHook)
else:
classFlags += "JSCLASS_HAS_RESERVED_SLOTS(%d)" % slotCount
reservedSlots = slotCount
@ -481,17 +495,24 @@ class CGDOMProxyJSClass(CGThing):
# HTMLAllCollection. So just hardcode it here.
if self.descriptor.interface.identifier.name == "HTMLAllCollection":
flags.append("JSCLASS_EMULATES_UNDEFINED")
objectMovedHook = OBJECT_MOVED_HOOK_NAME if self.descriptor.wrapperCache else 'nullptr'
return fill(
"""
static const DOMJSClass Class = {
PROXY_CLASS_DEF("${name}",
0, /* extra slots */
${flags}),
PROXY_CLASS_WITH_EXT("${name}",
0, /* extra slots */
${flags},
PROXY_MAKE_EXT(nullptr, /* outerObject */
nullptr, /* innerObject */
nullptr, /* iteratorObject */
false, /* isWrappedNative */
${objectMoved})),
$*{descriptor}
};
""",
name=self.descriptor.interface.identifier.name,
flags=" | ".join(flags),
objectMoved=objectMovedHook,
descriptor=DOMClass(self.descriptor))
@ -1549,11 +1570,28 @@ class CGClassFinalizeHook(CGAbstractClassHook):
return finalizeHook(self.descriptor, self.name, self.args[0].name).define()
class CGClassObjectMovedHook(CGAbstractClassHook):
"""
A hook for objectMovedOp, used to update the wrapper cache when an object it
is holding moves.
"""
def __init__(self, descriptor):
args = [Argument('JSObject*', 'obj'), Argument('const JSObject*', 'old')]
CGAbstractClassHook.__init__(self, descriptor, OBJECT_MOVED_HOOK_NAME,
'void', args)
def generate_code(self):
assert self.descriptor.wrapperCache
return CGIfWrapper(CGGeneric("UpdateWrapper(self, self, obj, old);\n"),
"self").define()
def JSNativeArguments():
return [Argument('JSContext*', 'cx'),
Argument('unsigned', 'argc'),
Argument('JS::Value*', 'vp')]
class CGClassConstructor(CGAbstractStaticMethod):
"""
JS-visible constructor for our objects
@ -10919,6 +10957,9 @@ class CGDescriptor(CGThing):
# wants a custom hook.
cgThings.append(CGClassFinalizeHook(descriptor))
if descriptor.concrete and descriptor.wrapperCache:
cgThings.append(CGClassObjectMovedHook(descriptor))
if len(descriptor.permissions):
for (k, v) in sorted(descriptor.permissions.items()):
perms = CGList((CGGeneric('"%s",' % p) for p in k), joiner="\n")

View File

@ -341,6 +341,7 @@ namespace mozilla {
namespace dom {
#ifdef MOZ_NUWA_PROCESS
int32_t ContentParent::sNuwaPid = 0;
bool ContentParent::sNuwaReady = false;
#endif
@ -586,6 +587,7 @@ ContentParent::RunNuwaProcess()
/* aIsNuwaProcess = */ true);
nuwaProcess->Init();
#ifdef MOZ_NUWA_PROCESS
sNuwaPid = nuwaProcess->Pid();
sNuwaReady = false;
#endif
return nuwaProcess.forget();
@ -1987,6 +1989,7 @@ ContentParent::~ContentParent()
#ifdef MOZ_NUWA_PROCESS
if (IsNuwaProcess()) {
sNuwaReady = false;
sNuwaPid = 0;
}
#endif
}
@ -3678,6 +3681,12 @@ ContentParent::DoSendAsyncMessage(JSContext* aCx,
if (aCpows && !GetCPOWManager()->Wrap(aCx, aCpows, &cpows)) {
return false;
}
#ifdef MOZ_NUWA_PROCESS
if (IsNuwaProcess() && IsNuwaReady()) {
// Nuwa won't receive frame messages after it is frozen.
return true;
}
#endif
return SendAsyncMessage(nsString(aMessage), data, cpows, Principal(aPrincipal));
}

View File

@ -79,6 +79,10 @@ class ContentParent MOZ_FINAL : public PContentParent
public:
#ifdef MOZ_NUWA_PROCESS
static int32_t NuwaPid() {
return sNuwaPid;
}
static bool IsNuwaReady() {
return sNuwaReady;
}
@ -712,6 +716,7 @@ private:
#endif
#ifdef MOZ_NUWA_PROCESS
static int32_t sNuwaPid;
static bool sNuwaReady;
#endif
};

View File

@ -1898,8 +1898,9 @@ TabChild::RecvHandleDoubleTap(const CSSPoint& aPoint, const ScrollableLayerGuid&
bool
TabChild::RecvHandleSingleTap(const CSSPoint& aPoint, const ScrollableLayerGuid& aGuid)
{
TABC_LOG("Handling single tap at %s with %p %p %d\n",
Stringify(aPoint).c_str(), mGlobal.get(), mTabChildGlobal.get(), mTouchEndCancelled);
TABC_LOG("Handling single tap at %s on %s with %p %p %d\n",
Stringify(aPoint).c_str(), Stringify(aGuid).c_str(), mGlobal.get(),
mTabChildGlobal.get(), mTouchEndCancelled);
if (!mGlobal || !mTabChildGlobal) {
return true;

View File

@ -20,7 +20,7 @@
var test = new PeerConnectionTest();
// TODO: Stop using old constraint-like RTCOptions soon (Bug 1064223).
// Watch out for case-difference when fixing: { offerToReceiveVideo: true }
test.setOfferOptions({ optional: [{ OfferToReceiveAudio: true }] });
test.setOfferOptions({ optional: [{ OfferToReceiveVideo: true }] });
test.run();
});
</script>

View File

@ -589,8 +589,15 @@ DOMStorageDBParent::Observe(const char* aTopic,
const nsACString& aScopePrefix)
{
if (mIPCOpen) {
mozilla::unused << SendObserve(nsDependentCString(aTopic),
nsCString(aScopePrefix));
#ifdef MOZ_NUWA_PROCESS
if (!(static_cast<ContentParent*>(Manager())->IsNuwaProcess() &&
ContentParent::IsNuwaReady())) {
#endif
mozilla::unused << SendObserve(nsDependentCString(aTopic),
nsCString(aScopePrefix));
#ifdef MOZ_NUWA_PROCESS
}
#endif
}
return NS_OK;

View File

@ -152,6 +152,15 @@ AppendToString(std::stringstream& aStream, const FrameMetrics& m,
aStream << sfx;
}
void
AppendToString(std::stringstream& aStream, const ScrollableLayerGuid& s,
const char* pfx, const char* sfx)
{
aStream << pfx
<< nsPrintfCString("{ l=%llu, p=%u, v=%llu }", s.mLayersId, s.mPresShellId, s.mScrollId).get()
<< sfx;
}
void
AppendToString(std::stringstream& aStream, const Matrix4x4& m,
const char* pfx, const char* sfx)

View File

@ -97,6 +97,10 @@ void
AppendToString(std::stringstream& aStream, const FrameMetrics& m,
const char* pfx="", const char* sfx="", bool detailed = false);
void
AppendToString(std::stringstream& aStream, const ScrollableLayerGuid& s,
const char* pfx="", const char* sfx="");
template<class T>
void
AppendToString(std::stringstream& aStream, const mozilla::gfx::MarginTyped<T>& m,

View File

@ -2750,7 +2750,7 @@ void AsyncPanZoomController::NotifyLayersUpdated(const FrameMetrics& aLayerMetri
// more "legitimate" sources like content scripts.
nsRefPtr<GeckoContentController> controller = GetGeckoContentController();
if (controller) {
APZC_LOG("%p sending scroll update acknowledgement with gen %lu\n", this, aLayerMetrics.GetScrollGeneration());
APZC_LOG("%p sending scroll update acknowledgement with gen %u\n", this, aLayerMetrics.GetScrollGeneration());
controller->AcknowledgeScrollUpdate(aLayerMetrics.GetScrollId(),
aLayerMetrics.GetScrollGeneration());
}

View File

@ -292,6 +292,15 @@ public:
sf->ResetScrollInfoIfGeneration(mScrollGeneration);
}
// Since the APZ and content are in sync, we need to clear any callback transform
// that might have been set on the last repaint request (which might have failed
// due to the inflight scroll update that this message is acknowledging).
nsCOMPtr<nsIContent> content = nsLayoutUtils::FindContentFor(mScrollId);
if (content) {
content->SetProperty(nsGkAtoms::apzCallbackTransform, new CSSPoint(),
nsINode::DeleteProperty<CSSPoint>);
}
return NS_OK;
}

View File

@ -205,7 +205,6 @@
#define SK_ALLOW_STATIC_GLOBAL_INITIALIZERS 0
#define SK_SUPPORT_LEGACY_GETDEVICE
#define SK_SUPPORT_LEGACY_GETTOPDEVICE
#define SK_IGNORE_ETC1_SUPPORT
#define SK_RASTERIZE_EVEN_ROUNDING

View File

@ -1106,10 +1106,9 @@ void imgLoader::GlobalInit()
int32_t cachesize;
rv = Preferences::GetInt("image.cache.size", &cachesize);
if (NS_SUCCEEDED(rv))
sCacheMaxSize = cachesize;
sCacheMaxSize = cachesize > 0 ? cachesize : 0;
else
sCacheMaxSize = 5 * 1024 * 1024;
sCacheMaxSize = sCacheMaxSize > 0 ? sCacheMaxSize : 0;
sMemReporter = new imgMemoryReporter();
RegisterStrongMemoryReporter(sMemReporter);

View File

@ -42,7 +42,7 @@ FileDescriptor::FileDescriptor(PlatformHandleType aHandle)
void
FileDescriptor::DuplicateInCurrentProcess(PlatformHandleType aHandle)
{
MOZ_ASSERT_IF(mHandleCreatedByOtherProcess,
MOZ_ASSERT_IF(mHandleCreatedByOtherProcess && IsValid(),
mHandleCreatedByOtherProcessWasUsed);
if (IsValid(aHandle)) {
@ -65,7 +65,7 @@ FileDescriptor::DuplicateInCurrentProcess(PlatformHandleType aHandle)
void
FileDescriptor::CloseCurrentProcessHandle()
{
MOZ_ASSERT_IF(mHandleCreatedByOtherProcess,
MOZ_ASSERT_IF(mHandleCreatedByOtherProcess && IsValid(),
mHandleCreatedByOtherProcessWasUsed);
// Don't actually close handles created by another process.

View File

@ -13,6 +13,7 @@
#ifdef MOZ_NUWA_PROCESS
#include "ipc/Nuwa.h"
#include "mozilla/Preferences.h"
#include "mozilla/dom/ContentParent.h"
#endif
#include "mozilla/Assertions.h"
@ -70,6 +71,9 @@ ProcessLink::ProcessLink(MessageChannel *aChan)
, mTransport(nullptr)
, mIOLoop(nullptr)
, mExistingListener(nullptr)
#ifdef MOZ_NUWA_PROCESS
, mIsToNuwaProcess(false)
#endif
{
}
@ -168,6 +172,20 @@ ProcessLink::SendMessage(Message *msg)
mChan->AssertWorkerThread();
mChan->mMonitor->AssertCurrentThreadOwns();
#ifdef MOZ_NUWA_PROCESS
if (mIsToNuwaProcess && mozilla::dom::ContentParent::IsNuwaReady()) {
switch (msg->type()) {
case mozilla::dom::PContent::Msg_NuwaFork__ID:
case mozilla::dom::PContent::Reply_AddNewProcess__ID:
case mozilla::dom::PContent::Msg_NotifyPhoneStateChange__ID:
case GOODBYE_MESSAGE_TYPE:
break;
default:
MOZ_CRASH();
}
}
#endif
mIOLoop->PostTask(
FROM_HERE,
NewRunnableMethod(mTransport, &Transport::Send, msg));
@ -360,6 +378,10 @@ ProcessLink::OnChannelConnected(int32_t peer_pid)
if (mExistingListener)
mExistingListener->OnChannelConnected(peer_pid);
#ifdef MOZ_NUWA_PROCESS
mIsToNuwaProcess = (peer_pid == mozilla::dom::ContentParent::NuwaPid());
#endif
if (notifyChannel) {
mChan->OnChannelConnected(peer_pid);
}

View File

@ -170,6 +170,9 @@ class ProcessLink
Transport* mTransport;
MessageLoop* mIOLoop; // thread where IO happens
Transport::Listener* mExistingListener; // channel's previous listener
#ifdef MOZ_NUWA_PROCESS
bool mIsToNuwaProcess;
#endif
};
class ThreadLink : public MessageLink

View File

@ -393,10 +393,32 @@ class JS_PUBLIC_API(AutoAssertOnGC)
};
/*
* Disable the static rooting hazard analysis in the live region, but assert if
* any GC occurs while this guard object is live. This is most useful to help
* the exact rooting hazard analysis in complex regions, since it cannot
* understand dataflow.
* Assert if an allocation of a GC thing occurs while this class is live. This
* class does not disable the static rooting hazard analysis.
*/
class JS_PUBLIC_API(AutoAssertNoAlloc)
{
#ifdef JS_DEBUG
js::gc::GCRuntime *gc;
public:
AutoAssertNoAlloc() : gc(nullptr) {}
explicit AutoAssertNoAlloc(JSRuntime *rt);
void disallowAlloc(JSRuntime *rt);
~AutoAssertNoAlloc();
#else
public:
AutoAssertNoAlloc() {}
explicit AutoAssertNoAlloc(JSRuntime *rt) {}
void disallowAlloc(JSRuntime *rt) {}
#endif
};
/*
* Disable the static rooting hazard analysis in the live region and assert if
* any allocation that could potentially trigger a GC occurs while this guard
* object is live. This is most useful to help the exact rooting hazard analysis
* in complex regions, since it cannot understand dataflow.
*
* Note: GC behavior is unpredictable even when deterministice and is generally
* non-deterministic in practice. The fact that this guard has not
@ -406,11 +428,25 @@ class JS_PUBLIC_API(AutoAssertOnGC)
* that the hazard analysis is correct for that code, rather than relying
* on this class.
*/
class JS_PUBLIC_API(AutoSuppressGCAnalysis) : public AutoAssertOnGC
class JS_PUBLIC_API(AutoSuppressGCAnalysis) : public AutoAssertNoAlloc
{
public:
AutoSuppressGCAnalysis() : AutoAssertOnGC() {}
explicit AutoSuppressGCAnalysis(JSRuntime *rt) : AutoAssertOnGC(rt) {}
AutoSuppressGCAnalysis() : AutoAssertNoAlloc() {}
explicit AutoSuppressGCAnalysis(JSRuntime *rt) : AutoAssertNoAlloc(rt) {}
};
/*
* Assert that code is only ever called from a GC callback, disable the static
* rooting hazard analysis and assert if any allocation that could potentially
* trigger a GC occurs while this guard object is live.
*
* This is useful to make the static analysis ignore code that runs in GC
* callbacks.
*/
class JS_PUBLIC_API(AutoAssertGCCallback) : public AutoSuppressGCAnalysis
{
public:
explicit AutoAssertGCCallback(JSObject *obj);
};
/*

View File

@ -360,6 +360,10 @@ ValidateSimdOperation(JSContext *cx, AsmJSModule::Global &global, HandleValue gl
case AsmJSSimdOperation_xor: native = simd_int32x4_xor; break;
case AsmJSSimdOperation_select: native = simd_int32x4_select; break;
case AsmJSSimdOperation_splat: native = simd_int32x4_splat; break;
case AsmJSSimdOperation_withX: native = simd_int32x4_withX; break;
case AsmJSSimdOperation_withY: native = simd_int32x4_withY; break;
case AsmJSSimdOperation_withZ: native = simd_int32x4_withZ; break;
case AsmJSSimdOperation_withW: native = simd_int32x4_withW; break;
case AsmJSSimdOperation_lessThanOrEqual:
case AsmJSSimdOperation_greaterThanOrEqual:
case AsmJSSimdOperation_notEqual:
@ -390,6 +394,10 @@ ValidateSimdOperation(JSContext *cx, AsmJSModule::Global &global, HandleValue gl
case AsmJSSimdOperation_xor: native = simd_float32x4_xor; break;
case AsmJSSimdOperation_select: native = simd_float32x4_select; break;
case AsmJSSimdOperation_splat: native = simd_float32x4_splat; break;
case AsmJSSimdOperation_withX: native = simd_float32x4_withX; break;
case AsmJSSimdOperation_withY: native = simd_float32x4_withY; break;
case AsmJSSimdOperation_withZ: native = simd_float32x4_withZ; break;
case AsmJSSimdOperation_withW: native = simd_float32x4_withW; break;
}
break;
}

View File

@ -92,7 +92,11 @@ enum AsmJSSimdOperation
AsmJSSimdOperation_or,
AsmJSSimdOperation_xor,
AsmJSSimdOperation_select,
AsmJSSimdOperation_splat
AsmJSSimdOperation_splat,
AsmJSSimdOperation_withX,
AsmJSSimdOperation_withY,
AsmJSSimdOperation_withZ,
AsmJSSimdOperation_withW
};
// These labels describe positions in the prologue/epilogue of functions while

View File

@ -1423,7 +1423,11 @@ class MOZ_STACK_CLASS ModuleCompiler
!addStandardLibrarySimdOpName("select", AsmJSSimdOperation_select) ||
!addStandardLibrarySimdOpName("splat", AsmJSSimdOperation_splat) ||
!addStandardLibrarySimdOpName("max", AsmJSSimdOperation_max) ||
!addStandardLibrarySimdOpName("min", AsmJSSimdOperation_min))
!addStandardLibrarySimdOpName("min", AsmJSSimdOperation_min) ||
!addStandardLibrarySimdOpName("withX", AsmJSSimdOperation_withX) ||
!addStandardLibrarySimdOpName("withY", AsmJSSimdOperation_withY) ||
!addStandardLibrarySimdOpName("withZ", AsmJSSimdOperation_withZ) ||
!addStandardLibrarySimdOpName("withW", AsmJSSimdOperation_withW))
{
return false;
}
@ -2478,6 +2482,18 @@ class FunctionCompiler
return ins;
}
MDefinition *insertElementSimd(MDefinition *vec, MDefinition *val, SimdLane lane, MIRType type)
{
if (inDeadCode())
return nullptr;
MOZ_ASSERT(IsSimdType(vec->type()) && vec->type() == type);
MOZ_ASSERT(!IsSimdType(val->type()));
MSimdInsertElement *ins = MSimdInsertElement::NewAsmJS(alloc(), vec, val, type, lane);
curBlock_->add(ins);
return ins;
}
MDefinition *ternarySimd(MDefinition *mask, MDefinition *lhs, MDefinition *rhs,
MSimdTernaryBitwise::Operation op, MIRType type)
{
@ -3567,6 +3583,10 @@ IsSimdValidOperationType(AsmJSSimdType type, AsmJSSimdOperation op)
case AsmJSSimdOperation_xor:
case AsmJSSimdOperation_select:
case AsmJSSimdOperation_splat:
case AsmJSSimdOperation_withX:
case AsmJSSimdOperation_withY:
case AsmJSSimdOperation_withZ:
case AsmJSSimdOperation_withW:
return true;
case AsmJSSimdOperation_mul:
case AsmJSSimdOperation_div:
@ -4353,13 +4373,21 @@ CheckMathMinMax(FunctionCompiler &f, ParseNode *callNode, MDefinition **def, boo
if (!CheckExpr(f, firstArg, &firstDef, &firstType))
return false;
bool opIsDouble = firstType.isMaybeDouble();
bool opIsInteger = firstType.isInt();
if (firstType.isMaybeDouble()) {
*type = MathRetType::Double;
firstType = Type::MaybeDouble;
} else if (firstType.isMaybeFloat()) {
*type = MathRetType::Float;
firstType = Type::MaybeFloat;
} else if (firstType.isInt()) {
*type = MathRetType::Signed;
firstType = Type::Int;
} else {
return f.failf(firstArg, "%s is not a subtype of double?, float? or int",
firstType.toChars());
}
MIRType opType = firstType.toMIRType();
if (!opIsDouble && !opIsInteger)
return f.failf(firstArg, "%s is not a subtype of double? or int", firstType.toChars());
MDefinition *lastDef = firstDef;
ParseNode *nextArg = NextNode(firstArg);
for (unsigned i = 1; i < CallArgListLength(callNode); i++, nextArg = NextNode(nextArg)) {
@ -4368,15 +4396,12 @@ CheckMathMinMax(FunctionCompiler &f, ParseNode *callNode, MDefinition **def, boo
if (!CheckExpr(f, nextArg, &nextDef, &nextType))
return false;
if (opIsDouble && !nextType.isMaybeDouble())
return f.failf(nextArg, "%s is not a subtype of double?", nextType.toChars());
if (opIsInteger && !nextType.isInt())
return f.failf(nextArg, "%s is not a subtype of int", nextType.toChars());
if (!(nextType <= firstType))
return f.failf(nextArg, "%s is not a subtype of %s", nextType.toChars(), firstType.toChars());
lastDef = f.minMax(lastDef, nextDef, opType, isMax);
}
*type = MathRetType(opIsDouble ? MathRetType::Double : MathRetType::Signed);
*def = lastDef;
return true;
}
@ -4818,6 +4843,35 @@ class CheckSimdSelectArgs
}
};
class CheckSimdVectorScalarArgs
{
Type formalType_;
public:
explicit CheckSimdVectorScalarArgs(Type t) : formalType_(t) {}
bool operator()(FunctionCompiler &f, ParseNode *arg, unsigned argIndex, Type actualType) const
{
MOZ_ASSERT(argIndex < 2);
if (argIndex == 0) {
// First argument is the vector
if (!(actualType <= formalType_)) {
return f.failf(arg, "%s is not a subtype of %s", actualType.toChars(),
formalType_.toChars());
}
return true;
}
// Second argument is the scalar
Type coercedFormalType = formalType_.simdToCoercedScalarType();
if (!(actualType <= coercedFormalType)) {
return f.failf(arg, "%s is not a subtype of %s", actualType.toChars(),
coercedFormalType.toChars());
}
return true;
}
};
} // anonymous namespace
template<class OpEnum>
@ -4847,6 +4901,18 @@ CheckSimdBinary<MSimdBinaryComp::Operation>(FunctionCompiler &f, ParseNode *call
return true;
}
static bool
CheckSimdWith(FunctionCompiler &f, ParseNode *call, Type retType, SimdLane lane, MDefinition **def,
Type *type)
{
DefinitionVector defs;
if (!CheckSimdCallArgs(f, call, 2, CheckSimdVectorScalarArgs(retType), &defs))
return false;
*def = f.insertElementSimd(defs[0], defs[1], lane, retType.toMIRType());
*type = retType;
return true;
}
static bool
CheckSimdOperationCall(FunctionCompiler &f, ParseNode *call, const ModuleCompiler::Global *global,
MDefinition **def, Type *type)
@ -4889,6 +4955,15 @@ CheckSimdOperationCall(FunctionCompiler &f, ParseNode *call, const ModuleCompile
case AsmJSSimdOperation_xor:
return CheckSimdBinary(f, call, retType, MSimdBinaryBitwise::xor_, def, type);
case AsmJSSimdOperation_withX:
return CheckSimdWith(f, call, retType, SimdLane::LaneX, def, type);
case AsmJSSimdOperation_withY:
return CheckSimdWith(f, call, retType, SimdLane::LaneY, def, type);
case AsmJSSimdOperation_withZ:
return CheckSimdWith(f, call, retType, SimdLane::LaneZ, def, type);
case AsmJSSimdOperation_withW:
return CheckSimdWith(f, call, retType, SimdLane::LaneW, def, type);
case AsmJSSimdOperation_splat: {
DefinitionVector defs;
Type formalType = retType.simdToCoercedScalarType();

View File

@ -222,7 +222,7 @@ GC(JSContext *cx, unsigned argc, jsval *vp)
* scheduled for GC). Otherwise, we collect all compartments.
*/
bool compartment = false;
if (args.length() == 1) {
if (args.length() >= 1) {
Value arg = args[0];
if (arg.isString()) {
if (!JS_StringEqualsAscii(cx, arg.toString(), "compartment", &compartment))
@ -233,6 +233,15 @@ GC(JSContext *cx, unsigned argc, jsval *vp)
}
}
bool shrinking = false;
if (args.length() >= 2) {
Value arg = args[1];
if (arg.isString()) {
if (!JS_StringEqualsAscii(cx, arg.toString(), "shrinking", &shrinking))
return false;
}
}
#ifndef JS_MORE_DETERMINISTIC
size_t preBytes = cx->runtime()->gc.usage.gcBytes();
#endif
@ -241,7 +250,11 @@ GC(JSContext *cx, unsigned argc, jsval *vp)
PrepareForDebugGC(cx->runtime());
else
PrepareForFullGC(cx->runtime());
GCForReason(cx->runtime(), gcreason::API);
if (shrinking)
ShrinkingGC(cx->runtime(), gcreason::API);
else
GCForReason(cx->runtime(), gcreason::API);
char buf[256] = { '\0' };
#ifndef JS_MORE_DETERMINISTIC
@ -2001,10 +2014,12 @@ IsSimdAvailable(JSContext *cx, unsigned argc, Value *vp)
static const JSFunctionSpecWithHelp TestingFunctions[] = {
JS_FN_HELP("gc", ::GC, 0, 0,
"gc([obj] | 'compartment')",
"gc([obj] | 'compartment' [, 'shrinking'])",
" Run the garbage collector. When obj is given, GC only its compartment.\n"
" If 'compartment' is given, GC any compartments that were scheduled for\n"
" GC via schedulegc."),
" GC via schedulegc.\n"
" If 'shrinking' is passes as the optional second argument, perform a\n"
" shrinking GC rather than a normal GC."),
JS_FN_HELP("minorgc", ::MinorGC, 0, 0,
"minorgc([aboutToOverflow])",

View File

@ -2889,38 +2889,6 @@ js::ClampToUint8(ThreadSafeContext *, unsigned argc, Value *vp)
JS_JITINFO_NATIVE_PARALLEL_THREADSAFE(js::ClampToUint8JitInfo, ClampToUint8JitInfo,
js::ClampToUint8);
bool
js::Memcpy(ThreadSafeContext *, unsigned argc, Value *vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
JS_ASSERT(args.length() == 5);
JS_ASSERT(args[0].isObject() && args[0].toObject().is<TypedObject>());
JS_ASSERT(args[1].isInt32());
JS_ASSERT(args[2].isObject() && args[2].toObject().is<TypedObject>());
JS_ASSERT(args[3].isInt32());
JS_ASSERT(args[4].isInt32());
TypedObject &targetTypedObj = args[0].toObject().as<TypedObject>();
int32_t targetOffset = args[1].toInt32();
TypedObject &sourceTypedObj = args[2].toObject().as<TypedObject>();
int32_t sourceOffset = args[3].toInt32();
int32_t size = args[4].toInt32();
JS_ASSERT(targetOffset >= 0);
JS_ASSERT(sourceOffset >= 0);
JS_ASSERT(size >= 0);
JS_ASSERT(size + targetOffset <= targetTypedObj.size());
JS_ASSERT(size + sourceOffset <= sourceTypedObj.size());
uint8_t *target = targetTypedObj.typedMem(targetOffset);
uint8_t *source = sourceTypedObj.typedMem(sourceOffset);
memcpy(target, source, size);
args.rval().setUndefined();
return true;
}
JS_JITINFO_NATIVE_PARALLEL_THREADSAFE(js::MemcpyJitInfo, MemcpyJitInfo, js::Memcpy);
bool
js::GetTypedObjectModule(JSContext *cx, unsigned argc, Value *vp)
{

View File

@ -851,20 +851,6 @@ extern const JSJitInfo TypedObjectIsAttachedJitInfo;
bool ClampToUint8(ThreadSafeContext *cx, unsigned argc, Value *vp);
extern const JSJitInfo ClampToUint8JitInfo;
/*
* Usage: Memcpy(targetDatum, targetOffset,
* sourceDatum, sourceOffset,
* size)
*
* Intrinsic function. Copies size bytes from the data for
* `sourceDatum` at `sourceOffset` into the data for
* `targetDatum` at `targetOffset`.
*
* Both `sourceDatum` and `targetDatum` must be attached.
*/
bool Memcpy(ThreadSafeContext *cx, unsigned argc, Value *vp);
extern const JSJitInfo MemcpyJitInfo;
/*
* Usage: GetTypedObjectModule()
*

View File

@ -195,20 +195,6 @@ function TypedObjectSet(descr, typedObj, offset, fromValue) {
if (!TypedObjectIsAttached(typedObj))
ThrowError(JSMSG_TYPEDOBJECT_HANDLE_UNATTACHED);
// Fast path: `fromValue` is a typed object with same type
// representation as the destination. In that case, we can just do a
// memcpy.
if (IsObject(fromValue) && ObjectIsTypedObject(fromValue)) {
if (!descr.variable && DescrsEquiv(descr, TypedObjectTypeDescr(fromValue))) {
if (!TypedObjectIsAttached(fromValue))
ThrowError(JSMSG_TYPEDOBJECT_HANDLE_UNATTACHED);
var size = DESCR_SIZE(descr);
Memcpy(typedObj, offset, fromValue, 0, size);
return;
}
}
switch (DESCR_KIND(descr)) {
case JS_TYPEREPR_SCALAR_KIND:
TypedObjectSetScalar(descr, typedObj, offset, fromValue);
@ -343,13 +329,33 @@ function TypedObjectSetReference(descr, typedObj, offset, fromValue) {
// Sets `fromValue` to `this` assuming that `this` is a scalar type.
function TypedObjectSetSimd(descr, typedObj, offset, fromValue) {
// It is only permitted to set a float32x4/int32x4 value from another
// float32x4/int32x4; in that case, the "fast path" that uses memcopy will
// have already matched. So if we get to this point, we're supposed
// to "adapt" fromValue, but there are no legal adaptions.
ThrowError(JSMSG_CANT_CONVERT_TO,
typeof(fromValue),
DESCR_STRING_REPR(descr));
if (!IsObject(fromValue) || !ObjectIsTypedObject(fromValue))
ThrowError(JSMSG_CANT_CONVERT_TO,
typeof(fromValue),
DESCR_STRING_REPR(descr));
if (!DescrsEquiv(descr, TypedObjectTypeDescr(fromValue)))
ThrowError(JSMSG_CANT_CONVERT_TO,
typeof(fromValue),
DESCR_STRING_REPR(descr));
var type = DESCR_TYPE(descr);
switch (type) {
case JS_SIMDTYPEREPR_FLOAT32:
Store_float32(typedObj, offset + 0, Load_float32(fromValue, 0));
Store_float32(typedObj, offset + 4, Load_float32(fromValue, 4));
Store_float32(typedObj, offset + 8, Load_float32(fromValue, 8));
Store_float32(typedObj, offset + 12, Load_float32(fromValue, 12));
break;
case JS_SIMDTYPEREPR_INT32:
Store_int32(typedObj, offset + 0, Load_int32(fromValue, 0));
Store_int32(typedObj, offset + 4, Load_int32(fromValue, 4));
Store_int32(typedObj, offset + 8, Load_int32(fromValue, 8));
Store_int32(typedObj, offset + 12, Load_int32(fromValue, 12));
break;
default:
assert(false, "Unhandled Simd type: " + type);
}
}
///////////////////////////////////////////////////////////////////////////

View File

@ -222,6 +222,7 @@ function isRootedPointerTypeName(name)
function isSuppressConstructor(name)
{
return name.indexOf("::AutoSuppressGC") != -1
|| name.indexOf("::AutoAssertGCCallback") != -1
|| name.indexOf("::AutoEnterAnalysis") != -1
|| name.indexOf("::AutoSuppressGCAnalysis") != -1
|| name.indexOf("::AutoIgnoreRootingHazards") != -1;

View File

@ -138,7 +138,7 @@ CheckHashTablesAfterMovingGC(JSRuntime *rt);
#ifdef JSGC_COMPACTING
struct MovingTracer : JSTracer {
MovingTracer(JSRuntime *rt) : JSTracer(rt, Visit, TraceWeakMapValues) {}
MovingTracer(JSRuntime *rt) : JSTracer(rt, Visit, TraceWeakMapKeysValues) {}
static void Visit(JSTracer *jstrc, void **thingp, JSGCTraceKind kind);
static void Sweep(JSTracer *jstrc);

View File

@ -624,6 +624,8 @@ struct ArenaHeader : public JS::shadow::ArenaHeader
inline ArenaHeader *getNextAllocDuringSweep() const;
inline void setNextAllocDuringSweep(ArenaHeader *aheader);
inline void unsetAllocDuringSweep();
void unmarkAll();
};
struct Arena

View File

@ -66,7 +66,10 @@ for (n of [0, 1, 2, 15, 16, Math.pow(2,31)-1, Math.pow(2,31), Math.pow(2,31)+1,
assertEq(f(n), Math.clz32(n|0));
var doubleNumbers = [NaN, Infinity, -Infinity, -10000, -3.4, -0, 0, 3.4, 10000];
var floatNumbers = [];
for (var x of doubleNumbers) floatNumbers.push(Math.fround(x));
var intNumbers = [-10000, -3, -1, 0, 3, 10000];
function testBinary(f, g, numbers) {
for (n of numbers)
for (o of numbers)
@ -82,7 +85,6 @@ assertAsmLinkFail(asmCompile('glob', USE_ASM + 'var at=glob.Math.atan2; function
testBinary(asmLink(asmCompile('glob', USE_ASM + 'var at=glob.Math.atan2; function f(d,e) { d=+d;e=+e; return +at(d,e) } return f'), {Math:{atan2:Math.atan2}}), Math.atan2, doubleNumbers);
assertAsmTypeFail('glob', USE_ASM + 'var min=glob.Math.min; function f(d) { d=+d; return +min(d) } return f');
assertAsmTypeFail('glob', USE_ASM + 'var f32=glob.Math.fround; var min=glob.Math.min; function f(d) { d=f32(d); return +min(d, f32(5)) } return f');
assertAsmTypeFail('glob', 'ffi', 'heap', USE_ASM + 'var i32=new glob.Int32Array(heap); var min=glob.Math.min; function f() { return min(i32[0], 5)|0 } return f');
assertAsmTypeFail('glob', USE_ASM + 'var min=glob.Math.min; function f(x) { x=x|0; return min(3 + x, 5)|0 } return f');
assertAsmTypeFail('glob', USE_ASM + 'var min=glob.Math.min; function f(x) { x=x|0; return min(5, 3 + x)|0 } return f');
@ -90,11 +92,13 @@ assertAsmTypeFail('glob', USE_ASM + 'var min=glob.Math.min; function f(x) { x=x|
assertAsmLinkFail(asmCompile('glob', USE_ASM + 'var min=glob.Math.min; function f(d,e) { d=+d;e=+e; return +min(d,e) } return f'), {Math:{min:Math.sin}});
assertAsmLinkFail(asmCompile('glob', USE_ASM + 'var min=glob.Math.min; function f(d,e) { d=+d;e=+e; return +min(d,e) } return f'), {Math:{min:null}});
testBinary(asmLink(asmCompile('glob', USE_ASM + 'var min=glob.Math.min; function f(d,e) { d=+d;e=+e; return +min(d,e) } return f'), {Math:{min:Math.min}}), Math.min, doubleNumbers);
testBinary(asmLink(asmCompile('glob', USE_ASM + 'var min=glob.Math.min; var f32=glob.Math.fround; function f(d,e) { d=f32(d);e=f32(e); return f32(min(d,e)) } return f'), this), Math.min, floatNumbers);
testBinary(asmLink(asmCompile('glob', USE_ASM + 'var min=glob.Math.min; function f(d,e) { d=d|0;e=e|0; return min(d,e)|0} return f'), {Math:{min:Math.min}}), Math.min, intNumbers);
assertAsmLinkFail(asmCompile('glob', USE_ASM + 'var max=glob.Math.max; function f(d,e) { d=+d;e=+e; return +max(d,e) } return f'), {Math:{max:Math.sin}});
assertAsmLinkFail(asmCompile('glob', USE_ASM + 'var max=glob.Math.max; function f(d,e) { d=+d;e=+e; return +max(d,e) } return f'), {Math:{max:null}});
testBinary(asmLink(asmCompile('glob', USE_ASM + 'var max=glob.Math.max; function f(d,e) { d=+d;e=+e; return +max(d,e) } return f'), {Math:{max:Math.max}}), Math.max, doubleNumbers);
testBinary(asmLink(asmCompile('glob', USE_ASM + 'var max=glob.Math.max; var f32=glob.Math.fround; function f(d,e) { d=f32(d);e=f32(e); return f32(max(d,e)) } return f'), this), Math.max, floatNumbers);
testBinary(asmLink(asmCompile('glob', USE_ASM + 'var max=glob.Math.max; function f(d,e) { d=d|0;e=e|0; return max(d,e)|0} return f'), {Math:{max:Math.max}}), Math.max, intNumbers);
function testTernary(f, g, numbers) {
@ -110,8 +114,10 @@ assertAsmTypeFail('glob', USE_ASM + 'var min=glob.Math.min; function f(d,e,g) {
testTernary(asmLink(asmCompile('glob', USE_ASM + 'var max=glob.Math.max; function f(d,e,g) { d=d|0;e=e|0;g=g|0; return +max(d,e,g) } return f'), {Math:{max:Math.max}}), Math.max, intNumbers);
testTernary(asmLink(asmCompile('glob', USE_ASM + 'var max=glob.Math.max; function f(d,e,g) { d=d|0;e=e|0;g=g|0; return max(d,e,g)|0 } return f'), {Math:{max:Math.max}}), Math.max, intNumbers);
testTernary(asmLink(asmCompile('glob', USE_ASM + 'var max=glob.Math.max; function f(d,e,g) { d=+d;e=+e;g=+g; return +max(d,e,g) } return f'), {Math:{max:Math.max}}), Math.max, doubleNumbers);
testTernary(asmLink(asmCompile('glob', USE_ASM + 'var max=glob.Math.max; var _=glob.Math.fround; function f(d,e,g) { d=_(d);e=_(e);g=_(g); return _(max(d,e,g)) } return f'), this), Math.max, floatNumbers);
testTernary(asmLink(asmCompile('glob', USE_ASM + 'var min=glob.Math.min; function f(d,e,g) { d=d|0;e=e|0;g=g|0; return min(d,e,g)|0 } return f'), {Math:{min:Math.min}}), Math.min, intNumbers);
testTernary(asmLink(asmCompile('glob', USE_ASM + 'var min=glob.Math.min; function f(d,e,g) { d=+d;e=+e;g=+g; return +min(d,e,g) } return f'), {Math:{min:Math.min}}), Math.min, doubleNumbers);
testTernary(asmLink(asmCompile('glob', USE_ASM + 'var min=glob.Math.min; var _=glob.Math.fround; function f(d,e,g) { d=_(d);e=_(e);g=_(g); return _(min(d,e,g)) } return f'), this), Math.min, floatNumbers);
// Implicit return coercions of math functions
assertEq(asmLink(asmCompile('glob', USE_ASM + 'var im=glob.Math.imul; function f(i) { i=i|0; i = im(i,i); return i|0 } return f'), this)(3), 9);
@ -134,7 +140,7 @@ assertAsmTypeFail('glob', USE_ASM + 'var sqrt=glob.Math.sqrt; function f(n) { n=
assertEq(asmLink(asmCompile('glob', USE_ASM + 'var min=glob.Math.min; function f(d) { d=+d; d = min(d, 13.); return +d } return f'), this)(12), 12);
assertEq(asmLink(asmCompile('glob', USE_ASM + 'var min=glob.Math.min; function f(d) { d=d|0; d = min(d, 11); return d|0 } return f'), this)(12), 11);
assertAsmTypeFail('glob', USE_ASM + FROUND + 'var max=glob.Math.max; function f(d) { d=fround(d); d = max(d) } return f');
assertEq(asmLink(asmCompile('glob', USE_ASM + FROUND + 'var min=glob.Math.min; function f(d) { d=fround(d); d = min(d, fround(13.37)); return fround(d) } return f'), this)(14), Math.fround(13.37));
assertEq(asmLink(asmCompile('glob', USE_ASM + 'var sin=glob.Math.sin; function f(d) { d=+d; d = sin(d); return +d } return f'), this)(Math.PI), Math.sin(Math.PI));
assertAsmTypeFail('glob', USE_ASM + FROUND + 'var sin=glob.Math.sin; function f(d) { d=fround(d); d = sin(d) } return f');
@ -153,9 +159,13 @@ assertAsmTypeFail('glob', USE_ASM + 'var abs=glob.Math.abs; function f(d) { d=+d
assertEq(asmLink(asmCompile('glob', USE_ASM + 'var im=glob.Math.imul; function f(i) { i=i|0; var d=0.0; d = +im(i,i); return +d } return f'), this)(42), Math.imul(42, 42));
assertEq(asmLink(asmCompile('glob', USE_ASM + 'var abs=glob.Math.abs; function f(i) { i=i|0; var d=0.0; d = +abs(i|0); return +d } return f'), this)(-42), 42);
assertEq(asmLink(asmCompile('glob', USE_ASM + 'var min=glob.Math.min; function f(i) { i=i|0; var d=0.0; d = +min(i, 0); return +d } return f'), this)(-42), -42);
assertEq(asmLink(asmCompile('glob', USE_ASM + FROUND + 'var min=glob.Math.min; function f(i) { i=i|0; var d=fround(0); d = fround(min(i, 0)); return +d } return f'), this)(-42), -42);
assertEq(asmLink(asmCompile('glob', USE_ASM + 'var max=glob.Math.max; function f(i) { i=i|0; var d=0.0; d = +max(i, 0); return +d } return f'), this)(-42), 0);
assertEq(asmLink(asmCompile('glob', USE_ASM + FROUND + 'var max=glob.Math.max; function f(i) { i=i|0; var d=fround(0); d = fround(max(i, 0)); return +d } return f'), this)(-42), 0);
assertEq(asmLink(asmCompile('glob', USE_ASM + 'var min=glob.Math.min; function f(d) { d=+d; var i=0; i = ~~min(d, 0.)|0; return i|0 } return f'), this)(-42), -42);
assertEq(asmLink(asmCompile('glob', USE_ASM + FROUND + 'var min=glob.Math.min; function f(d) { d=fround(d); var i=0; i = ~~min(d, fround(0))|0; return i|0 } return f'), this)(-42), -42);
assertEq(asmLink(asmCompile('glob', USE_ASM + 'var max=glob.Math.max; function f(d) { d=+d; var i=0; i = ~~max(d, 0.)|0; return i|0 } return f'), this)(-42), 0);
assertEq(asmLink(asmCompile('glob', USE_ASM + FROUND + 'var max=glob.Math.max; function f(d) { d=fround(d); var i=0; i = ~~max(d, fround(0))|0; return i|0 } return f'), this)(-42), 0);
assertEq(asmLink(asmCompile('glob', USE_ASM + 'var abs=glob.Math.abs; function f(i) { i=i|0; var d=0.0; return +d; +abs(i|0); return 3.0;} return f'), this)(-42), 0);

View File

@ -477,6 +477,35 @@ var f32x4 = SIMD.float32x4(0, 0, -0, NaN);
var another = SIMD.float32x4(0, -0, 0, 0);
assertEqX4(asmLink(asmCompile('glob', USE_ASM + F32 + F32D + "function f(x,y) {x=f4(x); y=f4(y); x=f4d(x,y); return f4(x);} return f"), this)(f32x4, another), [NaN, NaN, NaN, NaN]);
// With
const WXF = 'var w = f4.withX;';
const WYF = 'var w = f4.withY;';
const WZF = 'var w = f4.withZ;';
const WWF = 'var w = f4.withW;';
assertAsmTypeFail('glob', USE_ASM + F32 + WXF + "function f() {var x = f4(1,2,3,4); x = w(x, 1);} return f");
assertAsmTypeFail('glob', USE_ASM + F32 + WXF + "function f() {var x = f4(1,2,3,4); x = w(x, 1.0);} return f");
assertAsmTypeFail('glob', USE_ASM + F32 + WXF + "function f() {var x = f4(1,2,3,4); x = w(x, x);} return f");
assertAsmTypeFail('glob', USE_ASM + F32 + WXF + FROUND + "function f() {var x = f4(1,2,3,4); x = w(1, f32(1));} return f");
assertAsmTypeFail('glob', USE_ASM + F32 + WXF + FROUND + "function f() {var x = f4(1,2,3,4); x = w(1., f32(1));} return f");
assertAsmTypeFail('glob', USE_ASM + F32 + WXF + FROUND + "function f() {var x = f4(1,2,3,4); x = w(f32(1), f32(1));} return f");
assertAsmTypeFail('glob', USE_ASM + I32 + F32 + WXF + FROUND + "function f() {var x = f4(1,2,3,4); var y = i4(1,2,3,4); x = w(y, f32(1));} return f");
CheckF4(WXF + FROUND, 'var x = f4(1,2,3,4); x = w(x, f32(13.37));', [Math.fround(13.37), 2, 3, 4]);
CheckF4(WYF + FROUND, 'var x = f4(1,2,3,4); x = w(x, f32(13.37));', [1, Math.fround(13.37), 3, 4]);
CheckF4(WZF + FROUND, 'var x = f4(1,2,3,4); x = w(x, f32(13.37));', [1, 2, Math.fround(13.37), 4]);
CheckF4(WWF + FROUND, 'var x = f4(1,2,3,4); x = w(x, f32(13.37));', [1, 2, 3, Math.fround(13.37)]);
CheckF4(WWF + FROUND, 'var x = f4(1,2,3,4); x = w(x, f32(13.37) + f32(6.63));', [1, 2, 3, Math.fround(Math.fround(13.37) + Math.fround(6.63))]);
const WXI = 'var w = i4.withX;';
const WYI = 'var w = i4.withY;';
const WZI = 'var w = i4.withZ;';
const WWI = 'var w = i4.withW;';
CheckI4(WXI, 'var x = i4(1,2,3,4); x = w(x, 42);', [42, 2, 3, 4]);
CheckI4(WYI, 'var x = i4(1,2,3,4); x = w(x, 42);', [1, 42, 3, 4]);
CheckI4(WZI, 'var x = i4(1,2,3,4); x = w(x, 42);', [1, 2, 42, 4]);
CheckI4(WWI, 'var x = i4(1,2,3,4); x = w(x, 42);', [1, 2, 3, 42]);
// Comparisons
// True yields all bits set to 1 (i.e as an int32, 0xFFFFFFFF === -1), false
// yields all bits set to 0 (i.e 0).

View File

@ -461,6 +461,14 @@ function rmin_number(i) {
return i;
}
var uceFault_min_float = eval(uneval(uceFault).replace('uceFault', 'uceFault_min_float'));
function rmin_float(i) {
var x = Math.fround(Math.min(Math.fround(20), Math.fround(13.37)));
if (uceFault_min_number(i) || uceFault_min_number(i))
assertEq(x, Math.fround(13.37));
return i;
}
var uceFault_min_object = eval(uneval(uceFault).replace('uceFault', 'uceFault_min_object'));
function rmin_object(i) {
var t = i;
@ -480,6 +488,14 @@ function rmax_number(i) {
return i;
}
var uceFault_max_float = eval(uneval(uceFault).replace('uceFault', 'uceFault_max_float'));
function rmax_float(i) {
var x = Math.fround(Math.max(Math.fround(2), Math.fround(13.37)));
if (uceFault_max_number(i) || uceFault_max_number(i))
assertEq(x, Math.fround(13.37));
return i;
}
var uceFault_max_object = eval(uneval(uceFault).replace('uceFault', 'uceFault_max_object'));
function rmax_object(i) {
var t = i;
@ -975,8 +991,10 @@ for (i = 0; i < 100; i++) {
rpowhalf_number(i);
rpowhalf_object(i);
rmin_number(i);
rmin_float(i);
rmin_object(i);
rmax_number(i);
rmax_float(i);
rmax_object(i);
rabs_number(i);
rabs_object(i);

View File

@ -89,6 +89,38 @@ function sqrt() {
}
test(setupSqrt, sqrt);
// MMinMax
function setupMinMax() {
f32[0] = -0;
f32[1] = 0;
f32[2] = 1;
f32[3] = 4;
f32[4] = -1;
f32[5] = Infinity;
f32[6] = NaN;
f32[7] = 13.37;
f32[8] = -Infinity;
f32[9] = Math.pow(2,31) - 1;
}
function minMax() {
for(var i = 0; i < 9; ++i) {
for(var j = 0; j < 9; j++) {
var minf = Math.fround(Math.min(f32[i], f32[j]));
var mind = 1 / (1 / Math.min(f32[i], f32[j])); // force no float32 by chaining arith ops
assertFloat32(minf, true);
assertFloat32(mind, false);
assertEq( minf, Math.fround(mind) );
var maxf = Math.fround(Math.max(f32[i], f32[j]));
var maxd = 1 / (1 / Math.max(f32[i], f32[j])); // force no float32 by chaining arith ops
assertFloat32(maxf, true);
assertFloat32(maxd, false);
assertEq( maxf, Math.fround(maxd) );
}
}
}
test(setupMinMax, minMax);
// MTruncateToInt32
// The only way to get a MTruncateToInt32 with a Float32 input is to use Math.imul
function setupTruncateToInt32() {

View File

@ -61,8 +61,8 @@ function test(f) {
f();
}
var f32 = new Float32Array(2);
var f64 = new Float64Array(2);
var f32 = new Float32Array(4);
var f64 = new Float64Array(4);
function acceptAdd() {
var use = f32[0] + 1;
@ -151,6 +151,79 @@ function refuseSqrt() {
}
test(refuseSqrt);
function acceptMin() {
var res = Math.min(f32[0], f32[1]);
assertFloat32(res, true);
f64[0] = res;
}
test(acceptMin);
// In theory, we could do it, as Math.min/max actually behave as a Phi (it's a
// float32 producer iff its inputs are producers, it's a consumer iff its uses
// are consumers). In practice, this would involve some overhead for big chains
// of min/max.
function refuseMinAdd() {
var res = Math.min(f32[0], f32[1]) + f32[2];
assertFloat32(res, false);
f32[3] = res;
}
test(refuseMinAdd);
function acceptSeveralMinMax() {
var x = Math.min(f32[0], f32[1]);
var y = Math.max(f32[2], f32[3]);
var res = Math.min(x, y);
assertFloat32(res, true);
f64[0] = res;
}
test(acceptSeveralMinMax);
function acceptSeveralMinMax2() {
var res = Math.min(f32[0], f32[1], f32[2], f32[3]);
assertFloat32(res, true);
f64[0] = res;
}
test(acceptSeveralMinMax2);
function partialMinMax() {
var x = Math.min(f32[0], f32[1]);
var y = Math.min(f64[0], f32[1]);
var res = Math.min(x, y);
assertFloat32(x, true);
assertFloat32(y, false);
assertFloat32(res, false);
f64[0] = res;
}
test(partialMinMax);
function refuseSeveralMinMax() {
var res = Math.min(f32[0], f32[1] + f32[2], f32[2], f32[3]);
assertFloat32(res, false);
f64[0] = res;
}
test(refuseSeveralMinMax);
function refuseMin() {
var res = Math.min(f32[0], 42.13 + f32[1]);
assertFloat32(res, false);
f64[0] = res;
}
test(refuseMin);
function acceptMax() {
var res = Math.max(f32[0], f32[1]);
assertFloat32(res, true);
f64[0] = res;
}
test(acceptMax);
function refuseMax() {
var res = Math.max(f32[0], 42.13 + f32[1]);
assertFloat32(res, false);
f64[0] = res;
}
test(refuseMax);
function acceptAbs() {
var res = Math.abs(f32[0]);
assertFloat32(res, true);

View File

@ -5,9 +5,10 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "jit/JitOptions.h"
#include "mozilla/TypeTraits.h"
#include <cstdlib>
#include "jsfun.h"
using namespace js;
using namespace js::jit;
@ -16,58 +17,87 @@ namespace jit {
JitOptions js_JitOptions;
template<typename T> struct IsBool : mozilla::FalseType {};
template<> struct IsBool<bool> : mozilla::TrueType {};
template<typename T>
T overrideDefault(const char *param, T dflt) {
char *str = getenv(param);
if (!str)
return dflt;
if (IsBool<T>::value) {
if (strcmp(str, "true") == 0 ||
strcmp(str, "yes")) {
return true;
}
if (strcmp(str, "false") == 0 ||
strcmp(str, "no")) {
return false;
}
fprintf(stderr, "Warning: I didn't understand %s=\"%s\"", param, str);
} else {
char *endp;
int retval = strtol(str, &endp, 0);
if (*endp == '\0')
return retval;
fprintf(stderr, "Warning: I didn't understand %s=\"%s\"", param, str);
}
return dflt;
}
#define SET_DEFAULT(var, dflt) var = overrideDefault("JIT_OPTION_" #var, dflt)
JitOptions::JitOptions()
{
// Whether to perform expensive graph-consistency DEBUG-only assertions.
// It can be useful to disable this to reduce DEBUG-compile time of large
// asm.js programs.
checkGraphConsistency = true;
SET_DEFAULT(checkGraphConsistency, true);
#ifdef CHECK_OSIPOINT_REGISTERS
// Emit extra code to verify live regs at the start of a VM call
// are not modified before its OsiPoint.
checkOsiPointRegisters = false;
SET_DEFAULT(checkOsiPointRegisters, false);
#endif
// Whether to enable extra code to perform dynamic validation of
// RangeAnalysis results.
checkRangeAnalysis = false;
SET_DEFAULT(checkRangeAnalysis, false);
// Whether Ion should compile try-catch statements.
compileTryCatch = true;
SET_DEFAULT(compileTryCatch, true);
// Toggle whether eager scalar replacement is globally disabled.
disableScalarReplacement = true; // experimental
SET_DEFAULT(disableScalarReplacement, true); // experimental
// Toggle whether global value numbering is globally disabled.
disableGvn = false;
SET_DEFAULT(disableGvn, false);
// Toggles whether loop invariant code motion is globally disabled.
disableLicm = false;
SET_DEFAULT(disableLicm, false);
// Toggles whether inlining is globally disabled.
disableInlining = false;
SET_DEFAULT(disableInlining, false);
// Toggles whether Edge Case Analysis is gobally disabled.
disableEdgeCaseAnalysis = false;
SET_DEFAULT(disableEdgeCaseAnalysis, false);
// Toggles whether Range Analysis is globally disabled.
disableRangeAnalysis = false;
SET_DEFAULT(disableRangeAnalysis, false);
// Toggles whether Loop Unrolling is globally disabled.
disableLoopUnrolling = true;
SET_DEFAULT(disableLoopUnrolling, true);
// Toggles whether Effective Address Analysis is globally disabled.
disableEaa = false;
SET_DEFAULT(disableEaa, false);
// Whether functions are compiled immediately.
eagerCompilation = false;
SET_DEFAULT(eagerCompilation, false);
// Force how many invocation or loop iterations are needed before compiling
// a function with the highest ionmonkey optimization level.
// (i.e. OptimizationLevel_Normal)
forceDefaultIonWarmUpThreshold = false;
forcedDefaultIonWarmUpThreshold = 1000;
SET_DEFAULT(forceDefaultIonWarmUpThreshold, false);
SET_DEFAULT(forcedDefaultIonWarmUpThreshold, 1000);
// Force the used register allocator instead of letting the
// optimization pass decide.
@ -75,39 +105,39 @@ JitOptions::JitOptions()
forcedRegisterAllocator = RegisterAllocator_LSRA;
// Toggles whether large scripts are rejected.
limitScriptSize = true;
SET_DEFAULT(limitScriptSize, true);
// Toggles whether functions may be entered at loop headers.
osr = true;
SET_DEFAULT(osr, true);
// How many invocations or loop iterations are needed before functions
// are compiled with the baseline compiler.
baselineWarmUpThreshold = 10;
SET_DEFAULT(baselineWarmUpThreshold, 10);
// Number of exception bailouts (resuming into catch/finally block) before
// we invalidate and forbid Ion compilation.
exceptionBailoutThreshold = 10;
SET_DEFAULT(exceptionBailoutThreshold, 10);
// Number of bailouts without invalidation before we set
// JSScript::hadFrequentBailouts and invalidate.
frequentBailoutThreshold = 10;
SET_DEFAULT(frequentBailoutThreshold, 10);
// How many actual arguments are accepted on the C stack.
maxStackArgs = 4096;
SET_DEFAULT(maxStackArgs, 4096);
// How many times we will try to enter a script via OSR before
// invalidating the script.
osrPcMismatchesBeforeRecompile = 6000;
SET_DEFAULT(osrPcMismatchesBeforeRecompile, 6000);
// The bytecode length limit for small function.
//
// The default for this was arrived at empirically via benchmarking.
// We may want to tune it further after other optimizations have gone
// in.
smallFunctionMaxBytecodeLength_ = 100;
SET_DEFAULT(smallFunctionMaxBytecodeLength_, 100);
// How many uses of a parallel kernel before we attempt compilation.
compilerWarmUpThresholdPar = 1;
SET_DEFAULT(compilerWarmUpThresholdPar, 1);
}
bool

View File

@ -143,42 +143,80 @@ class LSimdSplatX4 : public LInstructionHelper<1, 1, 0>
}
};
// Extracts an element from a given SIMD int32x4 lane.
class LSimdExtractElementI : public LInstructionHelper<1, 1, 0>
class LSimdExtractElementBase : public LInstructionHelper<1, 1, 0>
{
SimdLane lane_;
public:
LIR_HEADER(SimdExtractElementI);
LSimdExtractElementI(const LAllocation &base, SimdLane lane) : lane_(lane) {
protected:
LSimdExtractElementBase(const LAllocation &base) {
setOperand(0, base);
}
public:
const LAllocation *getBase() {
return getOperand(0);
}
SimdLane lane() const {
return lane_;
return mir_->toSimdExtractElement()->lane();
}
};
// Extracts an element from a given SIMD float32x4 lane.
class LSimdExtractElementF : public LInstructionHelper<1, 1, 0>
// Extracts an element from a given SIMD int32x4 lane.
class LSimdExtractElementI : public LSimdExtractElementBase
{
public:
LIR_HEADER(SimdExtractElementI);
LSimdExtractElementI(const LAllocation &base)
: LSimdExtractElementBase(base)
{}
};
// Extracts an element from a given SIMD float32x4 lane.
class LSimdExtractElementF : public LSimdExtractElementBase
{
SimdLane lane_;
public:
LIR_HEADER(SimdExtractElementF);
LSimdExtractElementF(const LAllocation &base)
: LSimdExtractElementBase(base)
{}
};
LSimdExtractElementF(const LAllocation &base, SimdLane lane) : lane_(lane) {
setOperand(0, base);
class LSimdInsertElementBase : public LInstructionHelper<1, 2, 0>
{
protected:
LSimdInsertElementBase(const LAllocation &vec, const LAllocation &val)
{
setOperand(0, vec);
setOperand(1, val);
}
const LAllocation *getBase() {
public:
const LAllocation *vector() {
return getOperand(0);
}
SimdLane lane() const {
return lane_;
const LAllocation *value() {
return getOperand(1);
}
const SimdLane lane() const {
return mir_->toSimdInsertElement()->lane();
}
};
// Replace an element from a given SIMD int32x4 lane with a given value.
class LSimdInsertElementI : public LSimdInsertElementBase
{
public:
LIR_HEADER(SimdInsertElementI);
LSimdInsertElementI(const LAllocation &vec, const LAllocation &val)
: LSimdInsertElementBase(vec, val)
{}
};
// Replace an element from a given SIMD float32x4 lane with a given value.
class LSimdInsertElementF : public LSimdInsertElementBase
{
public:
LIR_HEADER(SimdInsertElementF);
LSimdInsertElementF(const LAllocation &vec, const LAllocation &val)
: LSimdInsertElementBase(vec, val)
{}
};
class LSimdSignMaskX4 : public LInstructionHelper<1, 1, 0>
@ -2656,16 +2694,16 @@ class LThrow : public LCallInstructionHelper<0, BOX_PIECES, 0>
static const size_t Value = 0;
};
class LMinMaxI : public LInstructionHelper<1, 2, 0>
class LMinMaxBase : public LInstructionHelper<1, 2, 0>
{
public:
LIR_HEADER(MinMaxI)
LMinMaxI(const LAllocation &first, const LAllocation &second)
protected:
LMinMaxBase(const LAllocation &first, const LAllocation &second)
{
setOperand(0, first);
setOperand(1, second);
}
public:
const LAllocation *first() {
return this->getOperand(0);
}
@ -2683,31 +2721,28 @@ class LMinMaxI : public LInstructionHelper<1, 2, 0>
}
};
class LMinMaxD : public LInstructionHelper<1, 2, 0>
class LMinMaxI : public LMinMaxBase
{
public:
LIR_HEADER(MinMaxI)
LMinMaxI(const LAllocation &first, const LAllocation &second) : LMinMaxBase(first, second)
{}
};
class LMinMaxD : public LMinMaxBase
{
public:
LIR_HEADER(MinMaxD)
LMinMaxD(const LAllocation &first, const LAllocation &second)
{
setOperand(0, first);
setOperand(1, second);
}
LMinMaxD(const LAllocation &first, const LAllocation &second) : LMinMaxBase(first, second)
{}
};
const LAllocation *first() {
return this->getOperand(0);
}
const LAllocation *second() {
return this->getOperand(1);
}
const LDefinition *output() {
return this->getDef(0);
}
MMinMax *mir() const {
return mir_->toMinMax();
}
const char *extraName() const {
return mir()->isMax() ? "Max" : "Min";
}
class LMinMaxF : public LMinMaxBase
{
public:
LIR_HEADER(MinMaxF)
LMinMaxF(const LAllocation &first, const LAllocation &second) : LMinMaxBase(first, second)
{}
};
// Negative of an integer

View File

@ -21,6 +21,8 @@
_(Float32x4) \
_(SimdExtractElementI) \
_(SimdExtractElementF) \
_(SimdInsertElementI) \
_(SimdInsertElementF) \
_(SimdSignMaskX4) \
_(SimdBinaryCompIx4) \
_(SimdBinaryCompFx4) \
@ -115,6 +117,7 @@
_(EmulatesUndefinedAndBranch) \
_(MinMaxI) \
_(MinMaxD) \
_(MinMaxF) \
_(NegI) \
_(NegD) \
_(NegF) \

View File

@ -1288,6 +1288,12 @@ LIRGenerator::visitMinMax(MMinMax *ins)
return defineReuseInput(lir, ins, 0);
}
if (ins->specialization() == MIRType_Float32) {
LMinMaxF *lir = new(alloc()) LMinMaxF(useRegisterAtStart(first), useRegister(second));
return defineReuseInput(lir, ins, 0);
}
MOZ_ASSERT(ins->specialization() == MIRType_Double);
LMinMaxD *lir = new(alloc()) LMinMaxD(useRegisterAtStart(first), useRegister(second));
return defineReuseInput(lir, ins, 0);
}
@ -3721,17 +3727,32 @@ LIRGenerator::visitSimdExtractElement(MSimdExtractElement *ins)
// Note: there could be int16x8 in the future, which doesn't use the
// same instruction. We either need to pass the arity or create new LIns.
LUse use = useRegisterAtStart(ins->input());
return define(new(alloc()) LSimdExtractElementI(use, ins->lane()), ins);
return define(new(alloc()) LSimdExtractElementI(use), ins);
}
if (ins->input()->type() == MIRType_Float32x4) {
LUse use = useRegisterAtStart(ins->input());
return define(new(alloc()) LSimdExtractElementF(use, ins->lane()), ins);
return define(new(alloc()) LSimdExtractElementF(use), ins);
}
MOZ_CRASH("Unknown SIMD kind when extracting element");
}
bool
LIRGenerator::visitSimdInsertElement(MSimdInsertElement *ins)
{
JS_ASSERT(IsSimdType(ins->type()));
LUse vec = useRegisterAtStart(ins->vector());
LUse val = useRegister(ins->value());
if (ins->type() == MIRType_Int32x4)
return defineReuseInput(new(alloc()) LSimdInsertElementI(vec, val), ins, 0);
if (ins->type() == MIRType_Float32x4)
return defineReuseInput(new(alloc()) LSimdInsertElementF(vec, val), ins, 0);
MOZ_CRASH("Unknown SIMD kind when generating constant");
}
bool
LIRGenerator::visitSimdSignMask(MSimdSignMask *ins)
{

View File

@ -269,6 +269,7 @@ class LIRGenerator : public LIRGeneratorSpecific
bool visitGetDOMMember(MGetDOMMember *ins);
bool visitRecompileCheck(MRecompileCheck *ins);
bool visitSimdExtractElement(MSimdExtractElement *ins);
bool visitSimdInsertElement(MSimdInsertElement *ins);
bool visitSimdSignMask(MSimdSignMask *ins);
bool visitSimdBinaryComp(MSimdBinaryComp *ins);
bool visitSimdBinaryArith(MSimdBinaryArith *ins);

View File

@ -1698,8 +1698,31 @@ MBinaryArithInstruction::trySpecializeFloat32(TempAllocator &alloc)
MDefinition *left = lhs();
MDefinition *right = rhs();
if (!left->canProduceFloat32() || !right->canProduceFloat32()
|| !CheckUsesAreFloat32Consumers(this))
if (!left->canProduceFloat32() || !right->canProduceFloat32() ||
!CheckUsesAreFloat32Consumers(this))
{
if (left->type() == MIRType_Float32)
ConvertDefinitionToDouble<0>(alloc, left, this);
if (right->type() == MIRType_Float32)
ConvertDefinitionToDouble<1>(alloc, right, this);
return;
}
specialization_ = MIRType_Float32;
setResultType(MIRType_Float32);
}
void
MMinMax::trySpecializeFloat32(TempAllocator &alloc)
{
if (specialization_ == MIRType_Int32)
return;
MDefinition *left = lhs();
MDefinition *right = rhs();
if (!(left->canProduceFloat32() || (left->isMinMax() && left->type() == MIRType_Float32)) ||
!(right->canProduceFloat32() || (right->isMinMax() && right->type() == MIRType_Float32)))
{
if (left->type() == MIRType_Float32)
ConvertDefinitionToDouble<0>(alloc, left, this);

View File

@ -1402,6 +1402,50 @@ class MSimdExtractElement : public MUnaryInstruction
}
};
// Replaces the datum in the given lane by a scalar value of the same type.
class MSimdInsertElement : public MBinaryInstruction
{
private:
SimdLane lane_;
MSimdInsertElement(MDefinition *vec, MDefinition *val, MIRType type, SimdLane lane)
: MBinaryInstruction(vec, val), lane_(lane)
{
MOZ_ASSERT(IsSimdType(type) && vec->type() == type);
MOZ_ASSERT(SimdTypeToScalarType(type) == val->type());
setMovable();
setResultType(type);
}
public:
INSTRUCTION_HEADER(SimdInsertElement)
static MSimdInsertElement *NewAsmJS(TempAllocator &alloc, MDefinition *vec, MDefinition *val,
MIRType type, SimdLane lane)
{
return new(alloc) MSimdInsertElement(vec, val, type, lane);
}
MDefinition *vector() {
return getOperand(0);
}
MDefinition *value() {
return getOperand(1);
}
SimdLane lane() const {
return lane_;
}
AliasSet getAliasSet() const {
return AliasSet::None();
}
bool congruentTo(const MDefinition *ins) const {
return binaryCongruentTo(ins) && lane_ == ins->toSimdInsertElement()->lane();
}
};
// Extracts the sign bits from a given vector, returning an MIRType_Int32.
class MSimdSignMask : public MUnaryInstruction
{
@ -4539,7 +4583,7 @@ class MMinMax
: MBinaryInstruction(left, right),
isMax_(isMax)
{
JS_ASSERT(type == MIRType_Double || type == MIRType_Int32);
JS_ASSERT(IsNumberType(type));
setResultType(type);
setMovable();
specialization_ = type;
@ -4574,6 +4618,9 @@ class MMinMax
return true;
}
bool isFloat32Commutative() const { return true; }
void trySpecializeFloat32(TempAllocator &alloc);
ALLOW_CLONE(MMinMax)
};

View File

@ -16,6 +16,7 @@ namespace jit {
_(SimdSplatX4) \
_(SimdConstant) \
_(SimdExtractElement) \
_(SimdInsertElement) \
_(SimdSignMask) \
_(SimdBinaryComp) \
_(SimdBinaryArith) \

View File

@ -115,6 +115,7 @@ class ParallelSafetyVisitor : public MDefinitionVisitor
SAFE_OP(SimdSplatX4)
SAFE_OP(SimdConstant)
SAFE_OP(SimdExtractElement)
SAFE_OP(SimdInsertElement)
SAFE_OP(SimdSignMask)
SAFE_OP(SimdBinaryComp)
SAFE_OP(SimdBinaryArith)

View File

@ -290,6 +290,55 @@ CodeGeneratorARM::visitMinMaxD(LMinMaxD *ins)
return true;
}
bool
CodeGeneratorARM::visitMinMaxF(LMinMaxF *ins)
{
FloatRegister first = ToFloatRegister(ins->first());
FloatRegister second = ToFloatRegister(ins->second());
FloatRegister output = ToFloatRegister(ins->output());
JS_ASSERT(first == output);
Assembler::Condition cond = ins->mir()->isMax()
? Assembler::VFP_LessThanOrEqual
: Assembler::VFP_GreaterThanOrEqual;
Label nan, equal, returnSecond, done;
masm.compareFloat(first, second);
// First or second is NaN, result is NaN.
masm.ma_b(&nan, Assembler::VFP_Unordered);
// Make sure we handle -0 and 0 right.
masm.ma_b(&equal, Assembler::VFP_Equal);
masm.ma_b(&returnSecond, cond);
masm.ma_b(&done);
// Check for zero.
masm.bind(&equal);
masm.compareFloat(first, NoVFPRegister);
// First wasn't 0 or -0, so just return it.
masm.ma_b(&done, Assembler::VFP_NotEqualOrUnordered);
// So now both operands are either -0 or 0.
if (ins->mir()->isMax()) {
// -0 + -0 = -0 and -0 + 0 = 0.
masm.ma_vadd_f32(second, first, first);
} else {
masm.ma_vneg_f32(first, first);
masm.ma_vsub_f32(first, second, first);
masm.ma_vneg_f32(first, first);
}
masm.ma_b(&done);
masm.bind(&nan);
masm.loadConstantFloat32(GenericNaN(), output);
masm.ma_b(&done);
masm.bind(&returnSecond);
masm.ma_vmov_f32(second, output);
masm.bind(&done);
return true;
}
bool
CodeGeneratorARM::visitAbsD(LAbsD *ins)
{

View File

@ -102,6 +102,7 @@ class CodeGeneratorARM : public CodeGeneratorShared
public:
// Instruction visitors.
virtual bool visitMinMaxD(LMinMaxD *ins);
virtual bool visitMinMaxF(LMinMaxF *ins);
virtual bool visitAbsD(LAbsD *ins);
virtual bool visitAbsF(LAbsF *ins);
virtual bool visitSqrtD(LSqrtD *ins);

View File

@ -525,7 +525,7 @@ class AssemblerX86Shared : public AssemblerShared
}
}
// movsd and movss are only provided in load/store form since the
// movsd is only provided in load/store form since the
// register-to-register form has different semantics (it doesn't clobber
// the whole output register) and isn't needed currently.
void movsd(const Address &src, FloatRegister dest) {
@ -540,6 +540,10 @@ class AssemblerX86Shared : public AssemblerShared
void movsd(FloatRegister src, const BaseIndex &dest) {
masm.movsd_rm(src.code(), dest.offset, dest.base.code(), dest.index.code(), dest.scale);
}
// Although movss is not only provided in load/store form (for the same
// reasons as movsd above), the register to register form should be only
// used in contexts where we care about not clearing the higher lanes of
// the FloatRegister.
void movss(const Address &src, FloatRegister dest) {
masm.movss_mr(src.offset, src.base.code(), dest.code());
}
@ -552,6 +556,9 @@ class AssemblerX86Shared : public AssemblerShared
void movss(FloatRegister src, const BaseIndex &dest) {
masm.movss_rm(src.code(), dest.offset, dest.base.code(), dest.index.code(), dest.scale);
}
void movss(FloatRegister src, const FloatRegister &dest) {
masm.movss_rr(src.code(), dest.code());
}
void movdqu(const Operand &src, FloatRegister dest) {
JS_ASSERT(HasSSE2());
switch (src.kind()) {
@ -1412,11 +1419,11 @@ class AssemblerX86Shared : public AssemblerShared
masm.unpcklps_rr(src.code(), dest.code());
}
void pinsrd(unsigned lane, Register src, FloatRegister dest) {
JS_ASSERT(HasSSE2());
JS_ASSERT(HasSSE41());
masm.pinsrd_irr(lane, src.code(), dest.code());
}
void pinsrd(unsigned lane, const Operand &src, FloatRegister dest) {
JS_ASSERT(HasSSE2());
JS_ASSERT(HasSSE41());
switch (src.kind()) {
case Operand::REG:
masm.pinsrd_irr(lane, src.reg(), dest.code());
@ -1931,6 +1938,10 @@ class AssemblerX86Shared : public AssemblerShared
JS_ASSERT(HasSSE2());
masm.orpd_rr(src.code(), dest.code());
}
void orps(FloatRegister src, FloatRegister dest) {
JS_ASSERT(HasSSE2());
masm.orps_rr(src.code(), dest.code());
}
void andpd(FloatRegister src, FloatRegister dest) {
JS_ASSERT(HasSSE2());
masm.andpd_rr(src.code(), dest.code());
@ -1947,18 +1958,29 @@ class AssemblerX86Shared : public AssemblerShared
JS_ASSERT(HasSSE2());
masm.sqrtss_rr(src.code(), dest.code());
}
void roundsd(FloatRegister src, FloatRegister dest,
X86Assembler::RoundingMode mode)
{
void roundsd(FloatRegister src, FloatRegister dest, X86Assembler::RoundingMode mode) {
JS_ASSERT(HasSSE41());
masm.roundsd_rr(src.code(), dest.code(), mode);
}
void roundss(FloatRegister src, FloatRegister dest,
X86Assembler::RoundingMode mode)
{
void roundss(FloatRegister src, FloatRegister dest, X86Assembler::RoundingMode mode) {
JS_ASSERT(HasSSE41());
masm.roundss_rr(src.code(), dest.code(), mode);
}
unsigned insertpsMask(SimdLane sourceLane, SimdLane destLane, unsigned zeroMask = 0)
{
// Note that the sourceLane bits are ignored in the case of a source
// memory operand, and the source is the given 32-bits memory location.
MOZ_ASSERT(zeroMask < 16);
unsigned ret = zeroMask ;
ret |= unsigned(destLane) << 4;
ret |= unsigned(sourceLane) << 6;
MOZ_ASSERT(ret < 256);
return ret;
}
void insertps(FloatRegister src, FloatRegister dest, unsigned mask) {
JS_ASSERT(HasSSE41());
masm.insertps_irr(mask, src.code(), dest.code());
}
void minsd(FloatRegister src, FloatRegister dest) {
JS_ASSERT(HasSSE2());
masm.minsd_rr(src.code(), dest.code());
@ -1976,6 +1998,10 @@ class AssemblerX86Shared : public AssemblerShared
MOZ_CRASH("unexpected operand kind");
}
}
void minss(FloatRegister src, FloatRegister dest) {
JS_ASSERT(HasSSE2());
masm.minss_rr(src.code(), dest.code());
}
void maxsd(FloatRegister src, FloatRegister dest) {
JS_ASSERT(HasSSE2());
masm.maxsd_rr(src.code(), dest.code());
@ -1993,6 +2019,10 @@ class AssemblerX86Shared : public AssemblerShared
MOZ_CRASH("unexpected operand kind");
}
}
void maxss(FloatRegister src, FloatRegister dest) {
JS_ASSERT(HasSSE2());
masm.maxss_rr(src.code(), dest.code());
}
void fisttp(const Operand &dest) {
JS_ASSERT(HasSSE3());
switch (dest.kind()) {

View File

@ -308,10 +308,12 @@ private:
OP2_SUBSD_VsdWsd = 0x5C,
OP2_SUBPS_VpsWps = 0x5C,
OP2_MINSD_VsdWsd = 0x5D,
OP2_MINSS_VssWss = 0x5D,
OP2_MINPS_VpsWps = 0x5D,
OP2_DIVSD_VsdWsd = 0x5E,
OP2_DIVPS_VpsWps = 0x5E,
OP2_MAXSD_VsdWsd = 0x5F,
OP2_MAXSS_VssWss = 0x5F,
OP2_MAXPS_VpsWps = 0x5F,
OP2_SQRTSD_VsdWsd = 0x51,
OP2_SQRTSS_VssWss = 0x51,
@ -350,13 +352,15 @@ private:
OP3_ROUNDSS_VsdWsd = 0x0A,
OP3_ROUNDSD_VsdWsd = 0x0B,
OP3_PTEST_VdVd = 0x17,
OP3_INSERTPS_VpsUps = 0x21,
OP3_PINSRD_VdqEdIb = 0x22
} ThreeByteOpcodeID;
typedef enum {
ESCAPE_PTEST = 0x38,
ESCAPE_PINSRD = 0x3A,
ESCAPE_ROUNDSD = 0x3A
ESCAPE_ROUNDSD = 0x3A,
ESCAPE_INSERTPS = 0x3A
} ThreeByteEscape;
TwoByteOpcodeID jccRel32(Condition cond)
@ -3602,6 +3606,16 @@ public:
m_formatter.immediate8(mode); // modes are the same for roundsd and roundss
}
void insertps_irr(unsigned mask, XMMRegisterID src, XMMRegisterID dst)
{
MOZ_ASSERT(mask < 256);
spew("insertps $%u, %s, %s",
mask, nameFPReg(src), nameFPReg(dst));
m_formatter.prefix(PRE_SSE_66);
m_formatter.threeByteOp(OP3_INSERTPS_VpsUps, ESCAPE_INSERTPS, (RegisterID)dst, (RegisterID)src);
m_formatter.immediate8(uint8_t(mask));
}
void pinsrd_irr(unsigned lane, RegisterID src, XMMRegisterID dst)
{
MOZ_ASSERT(lane < 4);
@ -3639,6 +3653,14 @@ public:
m_formatter.twoByteOp(OP2_MINSD_VsdWsd, (RegisterID)dst, base, offset);
}
void minss_rr(XMMRegisterID src, XMMRegisterID dst)
{
spew("minss %s, %s",
nameFPReg(src), nameFPReg(dst));
m_formatter.prefix(PRE_SSE_F3);
m_formatter.twoByteOp(OP2_MINSS_VssWss, (RegisterID)dst, (RegisterID)src);
}
void maxsd_rr(XMMRegisterID src, XMMRegisterID dst)
{
spew("maxsd %s, %s",
@ -3655,6 +3677,14 @@ public:
m_formatter.twoByteOp(OP2_MAXSD_VsdWsd, (RegisterID)dst, base, offset);
}
void maxss_rr(XMMRegisterID src, XMMRegisterID dst)
{
spew("maxss %s, %s",
nameFPReg(src), nameFPReg(dst));
m_formatter.prefix(PRE_SSE_F3);
m_formatter.twoByteOp(OP2_MAXSS_VssWss, (RegisterID)dst, (RegisterID)src);
}
// Misc instructions:
void int3()

View File

@ -536,6 +536,58 @@ CodeGeneratorX86Shared::visitMinMaxD(LMinMaxD *ins)
return true;
}
bool
CodeGeneratorX86Shared::visitMinMaxF(LMinMaxF *ins)
{
FloatRegister first = ToFloatRegister(ins->first());
FloatRegister second = ToFloatRegister(ins->second());
#ifdef DEBUG
FloatRegister output = ToFloatRegister(ins->output());
JS_ASSERT(first == output);
#endif
Label done, nan, minMaxInst;
// Do a ucomiss to catch equality and NaNs, which both require special
// handling. If the operands are ordered and inequal, we branch straight to
// the min/max instruction. If we wanted, we could also branch for less-than
// or greater-than here instead of using min/max, however these conditions
// will sometimes be hard on the branch predictor.
masm.ucomiss(first, second);
masm.j(Assembler::NotEqual, &minMaxInst);
if (!ins->mir()->range() || ins->mir()->range()->canBeNaN())
masm.j(Assembler::Parity, &nan);
// Ordered and equal. The operands are bit-identical unless they are zero
// and negative zero. These instructions merge the sign bits in that
// case, and are no-ops otherwise.
if (ins->mir()->isMax())
masm.andps(second, first);
else
masm.orps(second, first);
masm.jump(&done);
// x86's min/max are not symmetric; if either operand is a NaN, they return
// the read-only operand. We need to return a NaN if either operand is a
// NaN, so we explicitly check for a NaN in the read-write operand.
if (!ins->mir()->range() || ins->mir()->range()->canBeNaN()) {
masm.bind(&nan);
masm.ucomiss(first, first);
masm.j(Assembler::Parity, &done);
}
// When the values are inequal, or second is NaN, x86's min and max will
// return the value we need.
masm.bind(&minMaxInst);
if (ins->mir()->isMax())
masm.maxss(second, first);
else
masm.minss(second, first);
masm.bind(&done);
return true;
}
bool
CodeGeneratorX86Shared::visitAbsD(LAbsD *ins)
{
@ -2232,6 +2284,63 @@ CodeGeneratorX86Shared::visitSimdExtractElementF(LSimdExtractElementF *ins)
return true;
}
bool
CodeGeneratorX86Shared::visitSimdInsertElementI(LSimdInsertElementI *ins)
{
FloatRegister vector = ToFloatRegister(ins->vector());
Register value = ToRegister(ins->value());
FloatRegister output = ToFloatRegister(ins->output());
MOZ_ASSERT(vector == output); // defineReuseInput(0)
unsigned component = unsigned(ins->lane());
// Note that, contrarily to float32x4, we cannot use movd if the inserted
// value goes into the first component, as movd clears out the higher lanes
// of the output.
if (AssemblerX86Shared::HasSSE41()) {
masm.pinsrd(component, value, output);
return true;
}
masm.reserveStack(Simd128DataSize);
masm.storeAlignedInt32x4(vector, Address(StackPointer, 0));
masm.store32(value, Address(StackPointer, component * sizeof(int32_t)));
masm.loadAlignedInt32x4(Address(StackPointer, 0), output);
masm.freeStack(Simd128DataSize);
return true;
}
bool
CodeGeneratorX86Shared::visitSimdInsertElementF(LSimdInsertElementF *ins)
{
FloatRegister vector = ToFloatRegister(ins->vector());
FloatRegister value = ToFloatRegister(ins->value());
FloatRegister output = ToFloatRegister(ins->output());
MOZ_ASSERT(vector == output); // defineReuseInput(0)
if (ins->lane() == SimdLane::LaneX) {
// As both operands are registers, movss doesn't modify the upper bits
// of the destination operand.
if (value != output)
masm.movss(value, output);
return true;
}
if (AssemblerX86Shared::HasSSE41()) {
// The input value is in the low float32 of the 'value' FloatRegister.
masm.insertps(value, output, masm.insertpsMask(SimdLane::LaneX, ins->lane()));
return true;
}
unsigned component = unsigned(ins->lane());
masm.reserveStack(Simd128DataSize);
masm.storeAlignedFloat32x4(vector, Address(StackPointer, 0));
masm.storeFloat32(value, Address(StackPointer, component * sizeof(int32_t)));
masm.loadAlignedFloat32x4(Address(StackPointer, 0), output);
masm.freeStack(Simd128DataSize);
return true;
}
bool
CodeGeneratorX86Shared::visitSimdSignMaskX4(LSimdSignMaskX4 *ins)
{

View File

@ -151,6 +151,7 @@ class CodeGeneratorX86Shared : public CodeGeneratorShared
virtual bool visitDouble(LDouble *ins);
virtual bool visitFloat32(LFloat32 *ins);
virtual bool visitMinMaxD(LMinMaxD *ins);
virtual bool visitMinMaxF(LMinMaxF *ins);
virtual bool visitAbsD(LAbsD *ins);
virtual bool visitAbsF(LAbsF *ins);
virtual bool visitClzI(LClzI *ins);
@ -213,6 +214,8 @@ class CodeGeneratorX86Shared : public CodeGeneratorShared
bool visitFloat32x4(LFloat32x4 *ins);
bool visitSimdExtractElementI(LSimdExtractElementI *lir);
bool visitSimdExtractElementF(LSimdExtractElementF *lir);
bool visitSimdInsertElementI(LSimdInsertElementI *lir);
bool visitSimdInsertElementF(LSimdInsertElementF *lir);
bool visitSimdSignMaskX4(LSimdSignMaskX4 *ins);
bool visitSimdBinaryCompIx4(LSimdBinaryCompIx4 *lir);
bool visitSimdBinaryCompFx4(LSimdBinaryCompFx4 *lir);

View File

@ -23,7 +23,8 @@ const js::Class OuterWrapperClass =
nullptr, /* outerObject */
js::proxy_innerObject,
nullptr, /* iteratorObject */
false /* isWrappedNative */
false, /* isWrappedNative */
nullptr /* objectMoved */
));
static JSObject *

View File

@ -252,14 +252,14 @@ namespace js {
* allow for potention JSClass extensions.
*/
#define PROXY_MAKE_EXT(outerObject, innerObject, iteratorObject, \
isWrappedNative) \
isWrappedNative, objectMoved) \
{ \
outerObject, \
innerObject, \
iteratorObject, \
isWrappedNative, \
js::proxy_WeakmapKeyDelegate, \
js::proxy_ObjectMoved \
objectMoved \
}
#define PROXY_CLASS_WITH_EXT(name, extraSlots, flags, ext) \
@ -313,7 +313,8 @@ namespace js {
nullptr, /* outerObject */ \
nullptr, /* innerObject */ \
nullptr, /* iteratorObject */ \
false /* isWrappedNative */ \
false, /* isWrappedNative */ \
js::proxy_ObjectMoved \
))
/*

View File

@ -455,6 +455,13 @@ ArenaHeader::checkSynchronizedWithFreeList() const
}
#endif
void
ArenaHeader::unmarkAll()
{
uintptr_t *word = chunk()->bitmap.arenaBits(this);
memset(word, 0, ArenaBitmapWords * sizeof(uintptr_t));
}
/* static */ void
Arena::staticAsserts()
{
@ -2492,6 +2499,9 @@ GCRuntime::releaseRelocatedArenas(ArenaHeader *relocatedList)
ArenaHeader *aheader = relocatedList;
relocatedList = relocatedList->next;
// Clear the mark bits
aheader->unmarkAll();
// Mark arena as empty
AllocKind thingKind = aheader->getAllocKind();
size_t thingSize = aheader->getThingSize();
@ -5642,12 +5652,12 @@ GCRuntime::collect(bool incremental, int64_t budget, JSGCInvocationKind gckind,
JS::gcreason::Reason reason)
{
/* GC shouldn't be running in parallel execution mode */
MOZ_ASSERT(!InParallelSection());
MOZ_ALWAYS_TRUE(!InParallelSection());
JS_AbortIfWrongThread(rt);
/* If we attempt to invoke the GC while we are running in the GC, assert. */
MOZ_ASSERT(!rt->isHeapBusy());
MOZ_ALWAYS_TRUE(!rt->isHeapBusy());
/* The engine never locks across anything that could GC. */
MOZ_ASSERT(!rt->currentThreadHasExclusiveAccess());
@ -6359,8 +6369,33 @@ JS::AutoAssertOnGC::VerifyIsSafeToGC(JSRuntime *rt)
if (rt->gc.isInsideUnsafeRegion())
MOZ_CRASH("[AutoAssertOnGC] possible GC in GC-unsafe region");
}
JS::AutoAssertNoAlloc::AutoAssertNoAlloc(JSRuntime *rt)
: gc(nullptr)
{
disallowAlloc(rt);
}
void JS::AutoAssertNoAlloc::disallowAlloc(JSRuntime *rt)
{
JS_ASSERT(!gc);
gc = &rt->gc;
gc->disallowAlloc();
}
JS::AutoAssertNoAlloc::~AutoAssertNoAlloc()
{
if (gc)
gc->allowAlloc();
}
#endif
JS::AutoAssertGCCallback::AutoAssertGCCallback(JSObject *obj)
: AutoSuppressGCAnalysis()
{
MOZ_ASSERT(obj->runtimeFromMainThread()->isHeapMajorCollecting());
}
#ifdef JSGC_HASH_TABLE_CHECKS
void
js::gc::CheckHashTablesAfterMovingGC(JSRuntime *rt)

View File

@ -695,10 +695,8 @@ class ArenaLists
/* The background finalization must have stopped at this point. */
JS_ASSERT(backgroundFinalizeState[i] == BFS_DONE ||
backgroundFinalizeState[i] == BFS_JUST_FINISHED);
for (ArenaHeader *aheader = arenaLists[i].head(); aheader; aheader = aheader->next) {
uintptr_t *word = aheader->chunk()->bitmap.arenaBits(aheader);
memset(word, 0, ArenaBitmapWords * sizeof(uintptr_t));
}
for (ArenaHeader *aheader = arenaLists[i].head(); aheader; aheader = aheader->next)
aheader->unmarkAll();
}
}

View File

@ -325,37 +325,9 @@ class ZoneCellIterUnderGC : public ZoneCellIterImpl
}
};
/* In debug builds, assert that no allocation occurs. */
class AutoAssertNoAlloc
{
#ifdef JS_DEBUG
GCRuntime *gc;
public:
AutoAssertNoAlloc() : gc(nullptr) {}
explicit AutoAssertNoAlloc(JSRuntime *rt) : gc(nullptr) {
disallowAlloc(rt);
}
void disallowAlloc(JSRuntime *rt) {
JS_ASSERT(!gc);
gc = &rt->gc;
gc->disallowAlloc();
}
~AutoAssertNoAlloc() {
if (gc)
gc->allowAlloc();
}
#else
public:
AutoAssertNoAlloc() {}
explicit AutoAssertNoAlloc(JSRuntime *) {}
void disallowAlloc(JSRuntime *rt) {}
#endif
};
class ZoneCellIter : public ZoneCellIterImpl
{
AutoAssertNoAlloc noAlloc;
JS::AutoAssertNoAlloc noAlloc;
ArenaLists *lists;
AllocKind kind;

View File

@ -663,6 +663,12 @@ JSObject::finish(js::FreeOp *fop)
fop->free_(elements);
}
}
// It's possible that unreachable shapes may be marked whose listp points
// into this object. In case this happens, null out the shape's pointer here
// so that a moving GC will not try to access the dead object.
if (shape_->listp == &shape_)
shape_->listp = nullptr;
}
/* static */ inline bool

View File

@ -71,8 +71,12 @@ WeakMapBase::unmarkCompartment(JSCompartment *c)
void
WeakMapBase::markAll(JSCompartment *c, JSTracer *tracer)
{
for (WeakMapBase *m = c->gcWeakMapList; m; m = m->next)
m->markIteratively(tracer);
JS_ASSERT(tracer->eagerlyTraceWeakMaps() != DoNotTraceWeakMaps);
for (WeakMapBase *m = c->gcWeakMapList; m; m = m->next) {
m->trace(tracer);
if (m->memberOf)
gc::MarkObject(tracer, &m->memberOf, "memberOf");
}
}
bool

View File

@ -91,7 +91,7 @@ class WeakMapBase {
virtual void finish() = 0;
// Object that this weak map is part of, if any.
JSObject *memberOf;
HeapPtrObject memberOf;
// Compartment that this weak map is part of.
JSCompartment *compartment;

View File

@ -772,10 +772,13 @@ RegExpCompartment::sweep(JSRuntime *rt)
bool keep = shared->marked() && !IsStringAboutToBeFinalized(shared->source.unsafeGet());
for (size_t i = 0; i < ArrayLength(shared->compilationArray); i++) {
RegExpShared::RegExpCompilation &compilation = shared->compilationArray[i];
if (keep && compilation.jitCode)
keep = !IsJitCodeAboutToBeFinalized(compilation.jitCode.unsafeGet());
if (compilation.jitCode &&
IsJitCodeAboutToBeFinalized(compilation.jitCode.unsafeGet()))
{
keep = false;
}
}
if (keep) {
if (keep || rt->isHeapCompacting()) {
shared->clearMarked();
} else {
js_delete(shared);

View File

@ -199,7 +199,7 @@ class RegExpShared
void trace(JSTracer *trc);
bool marked() const { return marked_; }
void clearMarked() { JS_ASSERT(marked_); marked_ = false; }
void clearMarked() { marked_ = false; }
size_t sizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf);
};

View File

@ -897,9 +897,6 @@ static const JSFunctionSpec intrinsic_functions[] = {
JS_FNINFO("ClampToUint8",
JSNativeThreadSafeWrapper<js::ClampToUint8>,
&js::ClampToUint8JitInfo, 1, 0),
JS_FNINFO("Memcpy",
JSNativeThreadSafeWrapper<js::Memcpy>,
&js::MemcpyJitInfo, 5, 0),
JS_FN("GetTypedObjectModule", js::GetTypedObjectModule, 0, 0),
JS_FN("GetFloat32x4TypeDescr", js::GetFloat32x4TypeDescr, 0, 0),
JS_FN("GetInt32x4TypeDescr", js::GetInt32x4TypeDescr, 0, 0),

View File

@ -49,6 +49,11 @@ public:
ClearWrapper();
}
void ObjectMoved(JSObject *obj, const JSObject *old)
{
UpdateWrapper(obj, old);
}
private:
virtual ~SandboxPrivate() { }

View File

@ -310,7 +310,7 @@ sandbox_resolve(JSContext *cx, HandleObject obj, HandleId id)
}
static void
sandbox_finalize(JSFreeOp *fop, JSObject *obj)
sandbox_finalize(js::FreeOp *fop, JSObject *obj)
{
nsIScriptObjectPrincipal *sop =
static_cast<nsIScriptObjectPrincipal *>(xpc_GetJSPrivate(obj));
@ -324,6 +324,15 @@ sandbox_finalize(JSFreeOp *fop, JSObject *obj)
DestroyProtoAndIfaceCache(obj);
}
static void
sandbox_moved(JSObject *obj, const JSObject *old)
{
nsIScriptObjectPrincipal *sop =
static_cast<nsIScriptObjectPrincipal *>(xpc_GetJSPrivate(obj));
MOZ_ASSERT(sop);
static_cast<SandboxPrivate *>(sop)->ObjectMoved(obj, old);
}
static bool
sandbox_convert(JSContext *cx, HandleObject obj, JSType type, MutableHandleValue vp)
{
@ -443,22 +452,42 @@ sandbox_addProperty(JSContext *cx, HandleObject obj, HandleId id, MutableHandleV
#define XPCONNECT_SANDBOX_CLASS_METADATA_SLOT (XPCONNECT_GLOBAL_EXTRA_SLOT_OFFSET)
static const JSClass SandboxClass = {
static const js::Class SandboxClass = {
"Sandbox",
XPCONNECT_GLOBAL_FLAGS_WITH_EXTRA_SLOTS(1),
JS_PropertyStub, JS_DeletePropertyStub, JS_PropertyStub, JS_StrictPropertyStub,
sandbox_enumerate, sandbox_resolve, sandbox_convert, sandbox_finalize,
nullptr, nullptr, nullptr, JS_GlobalObjectTraceHook
nullptr, nullptr, nullptr, JS_GlobalObjectTraceHook,
JS_NULL_CLASS_SPEC,
{
nullptr, /* outerObject */
nullptr, /* innerObject */
nullptr, /* iteratorObject */
false, /* isWrappedNative */
nullptr, /* weakmapKeyDelegateOp */
sandbox_moved /* objectMovedOp */
},
JS_NULL_OBJECT_OPS
};
// Note to whomever comes here to remove addProperty hooks: billm has promised
// to do the work for this class.
static const JSClass SandboxWriteToProtoClass = {
static const js::Class SandboxWriteToProtoClass = {
"Sandbox",
XPCONNECT_GLOBAL_FLAGS_WITH_EXTRA_SLOTS(1),
sandbox_addProperty, JS_DeletePropertyStub, JS_PropertyStub, JS_StrictPropertyStub,
sandbox_enumerate, sandbox_resolve, sandbox_convert, sandbox_finalize,
nullptr, nullptr, nullptr, JS_GlobalObjectTraceHook
nullptr, nullptr, nullptr, JS_GlobalObjectTraceHook,
JS_NULL_CLASS_SPEC,
{
nullptr, /* outerObject */
nullptr, /* innerObject */
nullptr, /* iteratorObject */
false, /* isWrappedNative */
nullptr, /* weakmapKeyDelegateOp */
sandbox_moved /* objectMovedOp */
},
JS_NULL_OBJECT_OPS
};
static const JSFunctionSpec SandboxFunctions[] = {
@ -471,7 +500,7 @@ static const JSFunctionSpec SandboxFunctions[] = {
bool
xpc::IsSandbox(JSObject *obj)
{
const JSClass *clasp = GetObjectJSClass(obj);
const Class *clasp = GetObjectClass(obj);
return clasp == &SandboxClass || clasp == &SandboxWriteToProtoClass;
}
@ -861,11 +890,11 @@ xpc::CreateSandboxObject(JSContext *cx, MutableHandleValue vp, nsISupports *prin
compartmentOptions.setAddonId(addonId);
const JSClass *clasp = options.writeToGlobalPrototype
? &SandboxWriteToProtoClass
: &SandboxClass;
const Class *clasp = options.writeToGlobalPrototype
? &SandboxWriteToProtoClass
: &SandboxClass;
RootedObject sandbox(cx, xpc::CreateGlobalObject(cx, clasp,
RootedObject sandbox(cx, xpc::CreateGlobalObject(cx, js::Jsvalify(clasp),
principal, compartmentOptions));
if (!sandbox)
return NS_ERROR_FAILURE;

View File

@ -991,6 +991,20 @@ XPCWrappedNative::FlatJSObjectFinalized()
Release();
}
void
XPCWrappedNative::FlatJSObjectMoved(JSObject *obj, const JSObject *old)
{
JS::AutoAssertGCCallback inCallback(obj);
MOZ_ASSERT(mFlatJSObject == old);
nsWrapperCache *cache = nullptr;
CallQueryInterface(mIdentity, &cache);
if (cache)
cache->UpdateWrapper(obj, old);
mFlatJSObject = obj;
}
void
XPCWrappedNative::SystemIsBeingShutDown()
{

View File

@ -572,6 +572,17 @@ WrappedNativeFinalize(js::FreeOp *fop, JSObject *obj, WNHelperType helperType)
wrapper->FlatJSObjectFinalized();
}
static void
WrappedNativeObjectMoved(JSObject *obj, const JSObject *old)
{
nsISupports* p = static_cast<nsISupports*>(xpc_GetJSPrivate(obj));
if (!p)
return;
XPCWrappedNative* wrapper = static_cast<XPCWrappedNative*>(p);
wrapper->FlatJSObjectMoved(obj, old);
}
static void
XPC_WN_NoHelper_Finalize(js::FreeOp *fop, JSObject *obj)
{
@ -658,7 +669,9 @@ const XPCWrappedNativeJSClass XPC_WN_NoHelper_JSClass = {
nullptr, // outerObject
nullptr, // innerObject
nullptr, // iteratorObject
true, // isWrappedNative
true, // isWrappedNative
nullptr, // weakmapKeyDelegateOp
WrappedNativeObjectMoved
},
// ObjectOps
@ -1165,6 +1178,7 @@ XPCNativeScriptableShared::PopulateJSClass()
mJSClass.base.trace = XPCWrappedNative::Trace;
mJSClass.base.ext.isWrappedNative = true;
mJSClass.base.ext.objectMovedOp = WrappedNativeObjectMoved;
}
/***************************************************************************/

Some files were not shown because too many files have changed in this diff Show More