зеркало из https://github.com/mozilla/gecko-dev.git
Merge m-c to b2ginbound, a=merge
This commit is contained in:
Коммит
e03ead4753
|
@ -738,6 +738,13 @@ DocAccessible::AttributeWillChange(nsIDocument* aDocument,
|
|||
mStateBitWasOn = accessible->Unavailable();
|
||||
}
|
||||
|
||||
void
|
||||
DocAccessible::NativeAnonymousChildListChange(nsIDocument* aDocument,
|
||||
nsIContent* aContent,
|
||||
bool aIsRemove)
|
||||
{
|
||||
}
|
||||
|
||||
void
|
||||
DocAccessible::AttributeChanged(nsIDocument* aDocument,
|
||||
dom::Element* aElement,
|
||||
|
@ -1357,6 +1364,11 @@ DocAccessible::ProcessInvalidationList()
|
|||
continue;
|
||||
}
|
||||
|
||||
if (!child->Parent()) {
|
||||
NS_ERROR("The accessible is in document but doesn't have a parent");
|
||||
continue;
|
||||
}
|
||||
|
||||
// XXX: update context flags
|
||||
{
|
||||
Accessible* oldParent = child->Parent();
|
||||
|
|
Двоичные данные
browser/components/loop/content/shared/img/mozilla-logo.png
Двоичные данные
browser/components/loop/content/shared/img/mozilla-logo.png
Двоичный файл не отображается.
До Ширина: | Высота: | Размер: 2.9 KiB |
|
@ -468,6 +468,13 @@ loop.shared.actions = (function() {
|
|||
// socialShareProviders: Array - Optional.
|
||||
}),
|
||||
|
||||
/**
|
||||
* Notifies if the user agent will handle the room or not.
|
||||
*/
|
||||
UserAgentHandlesRoom: Action.define("userAgentHandlesRoom", {
|
||||
handlesRoom: Boolean
|
||||
}),
|
||||
|
||||
/**
|
||||
* Updates the Social API information when it is received.
|
||||
* XXX: should move to some roomActions module - refs bug 1079284
|
||||
|
@ -483,6 +490,16 @@ loop.shared.actions = (function() {
|
|||
JoinRoom: Action.define("joinRoom", {
|
||||
}),
|
||||
|
||||
/**
|
||||
* A special action for metrics logging to define what type of join
|
||||
* occurred when JoinRoom was activated.
|
||||
* XXX: should move to some roomActions module - refs bug 1079284
|
||||
*/
|
||||
MetricsLogJoinRoom: Action.define("metricsLogJoinRoom", {
|
||||
userAgentHandledRoom: Boolean
|
||||
// ownRoom: Boolean - Optional. Expected if firefoxHandledRoom is true.
|
||||
}),
|
||||
|
||||
/**
|
||||
* Starts the process for the user to join the room.
|
||||
* XXX: should move to some roomActions module - refs bug 1079284
|
||||
|
|
|
@ -132,6 +132,9 @@ loop.store.ActiveRoomStore = (function() {
|
|||
videoMuted: false,
|
||||
remoteVideoEnabled: false,
|
||||
failureReason: undefined,
|
||||
// Whether or not Firefox can handle this room in the conversation
|
||||
// window, rather than us handling it in the standalone.
|
||||
userAgentHandlesRoom: undefined,
|
||||
// Tracks if the room has been used during this
|
||||
// session. 'Used' means at least one call has been placed
|
||||
// with it. Entering and leaving the room without seeing
|
||||
|
@ -237,6 +240,7 @@ loop.store.ActiveRoomStore = (function() {
|
|||
"roomFailure",
|
||||
"retryAfterRoomFailure",
|
||||
"updateRoomInfo",
|
||||
"userAgentHandlesRoom",
|
||||
"gotMediaPermission",
|
||||
"joinRoom",
|
||||
"joinedRoom",
|
||||
|
@ -327,6 +331,9 @@ loop.store.ActiveRoomStore = (function() {
|
|||
* This action is only used for the standalone UI.
|
||||
*
|
||||
* @param {sharedActions.FetchServerData} actionData
|
||||
* @return {Promise} For testing purposes, returns a promise that is resolved
|
||||
* once data is received from the server, and it is determined
|
||||
* if Firefox handles the room or not.
|
||||
*/
|
||||
fetchServerData: function(actionData) {
|
||||
if (actionData.windowType !== "room") {
|
||||
|
@ -342,68 +349,144 @@ loop.store.ActiveRoomStore = (function() {
|
|||
|
||||
this._registerPostSetupActions();
|
||||
|
||||
this._getRoomDataForStandalone(actionData.cryptoKey);
|
||||
var dataPromise = this._getRoomDataForStandalone(actionData.cryptoKey);
|
||||
|
||||
var userAgentHandlesPromise = this._promiseDetectUserAgentHandles();
|
||||
|
||||
return Promise.all([dataPromise, userAgentHandlesPromise]).then(function(results) {
|
||||
results.forEach(function(result) {
|
||||
this.dispatcher.dispatch(result);
|
||||
}.bind(this));
|
||||
}.bind(this));
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets the room data for the standalone, decrypting it as necessary.
|
||||
*
|
||||
* @param {String} roomCryptoKey The crypto key associated to the room.
|
||||
* @return {Promise} A promise that is resolved once the get
|
||||
* and decryption is complete.
|
||||
*/
|
||||
_getRoomDataForStandalone: function(roomCryptoKey) {
|
||||
this._mozLoop.rooms.get(this._storeState.roomToken, function(err, result) {
|
||||
if (err) {
|
||||
this.dispatchAction(new sharedActions.RoomFailure({
|
||||
error: err,
|
||||
failedJoinRequest: false
|
||||
return new Promise(function(resolve, reject) {
|
||||
this._mozLoop.rooms.get(this._storeState.roomToken, function(err, result) {
|
||||
if (err) {
|
||||
resolve(new sharedActions.RoomFailure({
|
||||
error: err,
|
||||
failedJoinRequest: false
|
||||
}));
|
||||
return;
|
||||
}
|
||||
|
||||
var roomInfoData = new sharedActions.UpdateRoomInfo({
|
||||
// If we've got this far, then we want to go to the ready state
|
||||
// regardless of success of failure. This is because failures of
|
||||
// crypto don't stop the user using the room, they just stop
|
||||
// us putting up the information.
|
||||
roomState: ROOM_STATES.READY,
|
||||
roomUrl: result.roomUrl
|
||||
});
|
||||
|
||||
if (!result.context && !result.roomName) {
|
||||
roomInfoData.roomInfoFailure = ROOM_INFO_FAILURES.NO_DATA;
|
||||
resolve(roomInfoData);
|
||||
return;
|
||||
}
|
||||
|
||||
// This handles 'legacy', non-encrypted room names.
|
||||
if (result.roomName && !result.context) {
|
||||
roomInfoData.roomName = result.roomName;
|
||||
resolve(roomInfoData);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!crypto.isSupported()) {
|
||||
roomInfoData.roomInfoFailure = ROOM_INFO_FAILURES.WEB_CRYPTO_UNSUPPORTED;
|
||||
resolve(roomInfoData);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!roomCryptoKey) {
|
||||
roomInfoData.roomInfoFailure = ROOM_INFO_FAILURES.NO_CRYPTO_KEY;
|
||||
resolve(roomInfoData);
|
||||
return;
|
||||
}
|
||||
|
||||
crypto.decryptBytes(roomCryptoKey, result.context.value)
|
||||
.then(function(decryptedResult) {
|
||||
var realResult = JSON.parse(decryptedResult);
|
||||
|
||||
roomInfoData.roomDescription = realResult.description;
|
||||
roomInfoData.roomContextUrls = realResult.urls;
|
||||
roomInfoData.roomName = realResult.roomName;
|
||||
|
||||
resolve(roomInfoData);
|
||||
}, function(error) {
|
||||
roomInfoData.roomInfoFailure = ROOM_INFO_FAILURES.DECRYPT_FAILED;
|
||||
resolve(roomInfoData);
|
||||
});
|
||||
}.bind(this));
|
||||
}.bind(this));
|
||||
},
|
||||
|
||||
/**
|
||||
* If the user agent is Firefox, it sends a message to Firefox to see if
|
||||
* the room can be handled within Firefox rather than the standalone UI.
|
||||
*
|
||||
* @return {Promise} A promise that is resolved once it has been determined
|
||||
* if Firefox can handle the room.
|
||||
*/
|
||||
_promiseDetectUserAgentHandles: function() {
|
||||
return new Promise(function(resolve, reject) {
|
||||
function resolveWithNotHandlingResponse() {
|
||||
resolve(new sharedActions.UserAgentHandlesRoom({
|
||||
handlesRoom: false
|
||||
}));
|
||||
}
|
||||
|
||||
// If we're not Firefox, don't even try to see if it can be handled
|
||||
// in the browser.
|
||||
if (!loop.shared.utils.isFirefox(navigator.userAgent)) {
|
||||
resolveWithNotHandlingResponse();
|
||||
return;
|
||||
}
|
||||
|
||||
var roomInfoData = new sharedActions.UpdateRoomInfo({
|
||||
// If we've got this far, then we want to go to the ready state
|
||||
// regardless of success of failure. This is because failures of
|
||||
// crypto don't stop the user using the room, they just stop
|
||||
// us putting up the information.
|
||||
roomState: ROOM_STATES.READY,
|
||||
roomUrl: result.roomUrl
|
||||
});
|
||||
// Set up a timer in case older versions of Firefox don't give us a response.
|
||||
var timer = setTimeout(resolveWithNotHandlingResponse, 250);
|
||||
var webChannelListenerFunc;
|
||||
|
||||
if (!result.context && !result.roomName) {
|
||||
roomInfoData.roomInfoFailure = ROOM_INFO_FAILURES.NO_DATA;
|
||||
this.dispatcher.dispatch(roomInfoData);
|
||||
return;
|
||||
// Listen for the result.
|
||||
function webChannelListener(e) {
|
||||
if (e.detail.id !== "loop-link-clicker") {
|
||||
return;
|
||||
}
|
||||
|
||||
// Stop the default response.
|
||||
clearTimeout(timer);
|
||||
|
||||
// Remove the listener.
|
||||
window.removeEventListener("WebChannelMessageToContent", webChannelListenerFunc);
|
||||
|
||||
// Resolve with the details of if we're able to handle or not.
|
||||
resolve(new sharedActions.UserAgentHandlesRoom({
|
||||
handlesRoom: !!e.detail.message && e.detail.message.response
|
||||
}));
|
||||
}
|
||||
|
||||
// This handles 'legacy', non-encrypted room names.
|
||||
if (result.roomName && !result.context) {
|
||||
roomInfoData.roomName = result.roomName;
|
||||
this.dispatcher.dispatch(roomInfoData);
|
||||
return;
|
||||
}
|
||||
webChannelListenerFunc = webChannelListener.bind(this);
|
||||
|
||||
if (!crypto.isSupported()) {
|
||||
roomInfoData.roomInfoFailure = ROOM_INFO_FAILURES.WEB_CRYPTO_UNSUPPORTED;
|
||||
this.dispatcher.dispatch(roomInfoData);
|
||||
return;
|
||||
}
|
||||
window.addEventListener("WebChannelMessageToContent", webChannelListenerFunc);
|
||||
|
||||
if (!roomCryptoKey) {
|
||||
roomInfoData.roomInfoFailure = ROOM_INFO_FAILURES.NO_CRYPTO_KEY;
|
||||
this.dispatcher.dispatch(roomInfoData);
|
||||
return;
|
||||
}
|
||||
|
||||
var dispatcher = this.dispatcher;
|
||||
|
||||
crypto.decryptBytes(roomCryptoKey, result.context.value)
|
||||
.then(function(decryptedResult) {
|
||||
var realResult = JSON.parse(decryptedResult);
|
||||
|
||||
roomInfoData.roomDescription = realResult.description;
|
||||
roomInfoData.roomContextUrls = realResult.urls;
|
||||
roomInfoData.roomName = realResult.roomName;
|
||||
|
||||
dispatcher.dispatch(roomInfoData);
|
||||
}, function(error) {
|
||||
roomInfoData.roomInfoFailure = ROOM_INFO_FAILURES.DECRYPT_FAILED;
|
||||
dispatcher.dispatch(roomInfoData);
|
||||
});
|
||||
// Now send a message to the chrome to see if it can handle this room.
|
||||
window.dispatchEvent(new window.CustomEvent("WebChannelMessageToChrome", {
|
||||
detail: {
|
||||
id: "loop-link-clicker",
|
||||
message: {
|
||||
command: "checkWillOpenRoom",
|
||||
roomToken: this._storeState.roomToken
|
||||
}
|
||||
}
|
||||
}));
|
||||
}.bind(this));
|
||||
},
|
||||
|
||||
|
@ -426,6 +509,18 @@ loop.store.ActiveRoomStore = (function() {
|
|||
this.setStoreState(newState);
|
||||
},
|
||||
|
||||
/**
|
||||
* Handles the userAgentHandlesRoom action. Updates the store's data with
|
||||
* the new state.
|
||||
*
|
||||
* @param {sharedActions.userAgentHandlesRoom} actionData
|
||||
*/
|
||||
userAgentHandlesRoom: function(actionData) {
|
||||
this.setStoreState({
|
||||
userAgentHandlesRoom: actionData.handlesRoom
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Handles the updateSocialShareInfo action. Updates the room data with new
|
||||
* Social API info.
|
||||
|
@ -477,14 +572,11 @@ loop.store.ActiveRoomStore = (function() {
|
|||
},
|
||||
|
||||
/**
|
||||
* Handles the action to join to a room.
|
||||
* Checks that there are audio and video devices available, and joins the
|
||||
* room if there are. If there aren't then it will dispatch a ConnectionFailure
|
||||
* action with NO_MEDIA.
|
||||
*/
|
||||
joinRoom: function() {
|
||||
// Reset the failure reason if necessary.
|
||||
if (this.getStoreState().failureReason) {
|
||||
this.setStoreState({failureReason: undefined});
|
||||
}
|
||||
|
||||
_checkDevicesAndJoinRoom: function() {
|
||||
// XXX Ideally we'd do this check before joining a room, but we're waiting
|
||||
// for the UX for that. See bug 1166824. In the meantime this gives us
|
||||
// additional information for analysis.
|
||||
|
@ -501,6 +593,77 @@ loop.store.ActiveRoomStore = (function() {
|
|||
}.bind(this));
|
||||
},
|
||||
|
||||
/**
|
||||
* Hands off the room join to Firefox.
|
||||
*/
|
||||
_handoffRoomJoin: function() {
|
||||
var channelListener;
|
||||
|
||||
function handleRoomJoinResponse(e) {
|
||||
if (e.detail.id !== "loop-link-clicker") {
|
||||
return;
|
||||
}
|
||||
|
||||
window.removeEventListener("WebChannelMessageToContent", channelListener);
|
||||
|
||||
if (!e.detail.message || !e.detail.message.response) {
|
||||
// XXX Firefox didn't handle this, even though it said it could
|
||||
// previously. We should add better user feedback here.
|
||||
console.error("Firefox didn't handle room it said it could.");
|
||||
} else {
|
||||
this.dispatcher.dispatch(new sharedActions.JoinedRoom({
|
||||
apiKey: "",
|
||||
sessionToken: "",
|
||||
sessionId: "",
|
||||
expires: 0
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
channelListener = handleRoomJoinResponse.bind(this);
|
||||
|
||||
window.addEventListener("WebChannelMessageToContent", channelListener);
|
||||
|
||||
// Now we're set up, dispatch an event.
|
||||
window.dispatchEvent(new window.CustomEvent("WebChannelMessageToChrome", {
|
||||
detail: {
|
||||
id: "loop-link-clicker",
|
||||
message: {
|
||||
command: "openRoom",
|
||||
roomToken: this._storeState.roomToken
|
||||
}
|
||||
}
|
||||
}));
|
||||
},
|
||||
|
||||
/**
|
||||
* Handles the action to join to a room.
|
||||
*/
|
||||
joinRoom: function() {
|
||||
// Reset the failure reason if necessary.
|
||||
if (this.getStoreState().failureReason) {
|
||||
this.setStoreState({ failureReason: undefined });
|
||||
}
|
||||
|
||||
// If we're standalone and we know Firefox can handle the room, then hand
|
||||
// it off.
|
||||
if (this._storeState.standalone && this._storeState.userAgentHandlesRoom) {
|
||||
this.dispatcher.dispatch(new sharedActions.MetricsLogJoinRoom({
|
||||
userAgentHandledRoom: true,
|
||||
ownRoom: true
|
||||
}));
|
||||
this._handoffRoomJoin();
|
||||
return;
|
||||
}
|
||||
|
||||
this.dispatcher.dispatch(new sharedActions.MetricsLogJoinRoom({
|
||||
userAgentHandledRoom: false
|
||||
}));
|
||||
|
||||
// Otherwise, we handle the room ourselves.
|
||||
this._checkDevicesAndJoinRoom();
|
||||
},
|
||||
|
||||
/**
|
||||
* Handles the action that signifies when media permission has been
|
||||
* granted and starts joining the room.
|
||||
|
@ -540,6 +703,15 @@ loop.store.ActiveRoomStore = (function() {
|
|||
* @param {sharedActions.JoinedRoom} actionData
|
||||
*/
|
||||
joinedRoom: function(actionData) {
|
||||
// If we're standalone and firefox is handling, then just store the new
|
||||
// state. No need to do anything else.
|
||||
if (this._storeState.standalone && this._storeState.userAgentHandlesRoom) {
|
||||
this.setStoreState({
|
||||
roomState: ROOM_STATES.JOINED
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
this.setStoreState({
|
||||
apiKey: actionData.apiKey,
|
||||
sessionToken: actionData.sessionToken,
|
||||
|
|
|
@ -25,6 +25,27 @@ body,
|
|||
background: #000;
|
||||
}
|
||||
|
||||
/* Logos */
|
||||
|
||||
.loop-logo-text {
|
||||
background: url("../img/hello-logo-text.svg") no-repeat;
|
||||
width: 200px;
|
||||
height: 36px;
|
||||
}
|
||||
|
||||
.loop-logo {
|
||||
background: url("../shared/img/helloicon.svg") no-repeat;
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
}
|
||||
|
||||
.mozilla-logo {
|
||||
background: url("../img/mozilla-logo.svg#logo") no-repeat;
|
||||
background-size: contain;
|
||||
width: 100px;
|
||||
height: 30px;
|
||||
}
|
||||
|
||||
.room-conversation-wrapper > .beta-logo {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
|
@ -43,7 +64,7 @@ body,
|
|||
margin: 0 auto;
|
||||
height: 30px;
|
||||
background-size: contain;
|
||||
background-image: url("../shared/img/mozilla-logo.png");
|
||||
background-image: url("../img/mozilla-logo.svg#logo-white");
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
|
||||
|
@ -138,6 +159,45 @@ html[dir="rtl"] .rooms-footer .footer-logo {
|
|||
line-height: 24px;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle in Firefox views
|
||||
*/
|
||||
|
||||
.handle-user-agent-view-scroller {
|
||||
height: 100%;
|
||||
overflow: scroll;
|
||||
}
|
||||
|
||||
.handle-user-agent-view {
|
||||
margin: 2rem auto;
|
||||
width: 500px;
|
||||
}
|
||||
|
||||
.handle-user-agent-view > .info-panel {
|
||||
padding-bottom: 40px;
|
||||
font-size: 1.6rem;
|
||||
}
|
||||
|
||||
.handle-user-agent-view > p,
|
||||
.handle-user-agent-view > .info-panel > p {
|
||||
margin-top: 0;
|
||||
margin: 2rem auto;
|
||||
}
|
||||
|
||||
.handle-user-agent-view > .info-panel > button {
|
||||
width: 80%;
|
||||
height: 4rem;
|
||||
font-size: 1.6rem;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.handle-user-agent-view > .info-panel > button.disabled {
|
||||
background-color: #EBEBEB;
|
||||
border-color: #EBEBEB;
|
||||
color: #B2B0B3;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
/* Room wrapper layout */
|
||||
|
||||
.room-conversation-wrapper {
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 467.5 76"><path fill="#5D5F64" d="M263.4 28.2c-.2.4-.5.8-.8 1.2-.3.3-.7.6-1.2.8-.5.2-.9.3-1.4.3-.5 0-1-.1-1.4-.3-.5-.2-.8-.4-1.2-.8-.3-.3-.6-.7-.8-1.2-.2-.4-.3-.9-.3-1.4 0-.5.1-1 .3-1.4.2-.4.4-.8.8-1.2.3-.3.7-.6 1.2-.8.4-.2.9-.3 1.4-.3.5 0 1 .1 1.4.3.4.2.8.4 1.2.8.3.3.6.7.8 1.2.2.5.3.9.3 1.4 0 .5-.1 1-.3 1.4zm-.7-2.6c-.2-.4-.4-.7-.6-1-.2-.3-.6-.5-.9-.7-.4-.2-.7-.2-1.1-.2-.4 0-.8.1-1.1.2-.4.2-.7.4-.9.7-.3.3-.5.6-.6 1-.2.4-.2.8-.2 1.2 0 .4.1.8.2 1.2.2.4.4.7.6 1 .3.3.6.5.9.7.3.2.7.2 1.1.2.4 0 .8-.1 1.1-.2.4-.2.7-.4.9-.7.3-.3.5-.6.6-1 .1-.4.2-.8.2-1.2.1-.4 0-.8-.2-1.2zm-2.2 2.6c-.1-.3-.3-.5-.4-.6-.1-.1-.2-.3-.3-.4l-.1-.1h-.2v1.8h-.7v-4.1h1.3c.2 0 .4 0 .6.1.2.1.3.1.4.2.1.1.2.2.2.4 0 .1.1.3.1.5 0 .3-.1.6-.3.8-.2.2-.4.3-.8.3l.1.1.2.2c.1.1.1.2.2.2.1.1.1.2.2.3l.6 1h-.8l-.3-.7zm0-2.8c-.1-.1-.3-.2-.6-.2h-.4v1.3h.8c.1 0 .2-.1.2-.1.1-.1.2-.3.2-.5s0-.3-.2-.5zM35.6 13.9h-24v19h19.5v9.4H11.6v32.1H0V4.3h37.1l-1.5 9.6zM41.7 8.2c0-4.1 3.2-7.5 7.4-7.5 3.9 0 7.3 3.2 7.3 7.5 0 4.1-3.3 7.4-7.5 7.4-4.1 0-7.2-3.4-7.2-7.4zm1.6 66.2V24l11.2-2v52.4H43.3zM89.2 32.9c-1.1-.4-1.9-.7-3.1-.7-4.7 0-8.6 3.4-9.6 7.6v34.6H65.3V38.3c0-6.5-.7-10.6-1.8-13.8l10.2-2.6c1.2 2.3 1.9 5.3 1.9 8.1 4-5.6 8.1-8.2 13.1-8.2 1.6 0 2.6.2 3.9.8l-3.4 10.3zM103.3 51.8v.8c0 7.1 2.6 14.6 12.7 14.6 4.8 0 8.9-1.7 12.7-5.1l4.4 6.8c-5.4 4.6-11.5 6.8-18.4 6.8-14.6 0-23.7-10.4-23.7-26.8 0-9 1.9-15 6.4-20 4.2-4.8 9.2-6.9 15.7-6.9 5.1 0 9.7 1.3 14.1 5.3 4.5 4 6.7 10.3 6.7 22.3v2.3h-30.6zm9.8-21.4c-6.3 0-9.7 5-9.7 13.3h18.9c0-8.4-3.6-13.3-9.2-13.3zM165.5 10c-2.5-1.2-4-1.8-6.2-1.8-3.8 0-6.3 2.6-6.3 7.2v7.8h13.4l-2.8 7.7h-10.4v43.5h-11V30.9h-4.8v-7.7h5s-.3-2.8-.3-7.6C142.1 5 148.5 0 157.6 0c4.4 0 8 .9 11.5 2.9l-3.6 7.1zM210.1 49c0 16.5-8.8 26.7-22.7 26.7-13.9 0-22.6-10.4-22.6-26.8S173.6 22 187.2 22c14.6 0 22.9 10.8 22.9 27zm-32.9-.8c0 14.9 3.7 19.2 10.4 19.2 6.6 0 10.2-5.4 10.2-18.2 0-14.5-4-18.8-10.5-18.8-7 0-10.1 5.3-10.1 17.8z"/><path fill="#5D5F64" d="M243.6 74.4c-1.8-2.9-10.1-17.3-11.1-19.1-1.9 3.8-9.2 16.3-11.1 19.1h-14.1l19-27.8-14.8-22 12-2.4c2.3 3.8 6.9 11.8 9.3 16.6 1.4-3.3 6.6-13.6 8.1-15.7h13l-15.1 23.3 18.7 27.9h-13.9z"/><g fill="#5D5F64"><path d="M280.6 4.5h8.2v29.3h29.5V4.5h8.4v70.1h-8.4V40.7h-29.5v33.9h-8.2V4.5zM371.5 64.3l3.1 5.1c-4.5 4.1-10.6 6.3-17.2 6.3-14.1 0-22.6-10.2-22.6-27.1 0-8.6 1.8-14.1 6.1-19.2 4.1-4.8 9.1-7.1 15.2-7.1 5.5 0 10.3 1.9 13.8 5.5 4.4 4.5 5.6 9.3 5.8 21.5v1.1H344v1.2c0 4.8.6 8.5 2.3 11.1 2.9 4.4 7.6 6.2 12.7 6.2 4.9.2 8.9-1.3 12.5-4.6zM344 44.5h23.3c-.1-5.5-.8-8.9-2.3-11.3-1.7-2.8-5.3-4.5-9.2-4.5-7.3-.1-11.4 5.2-11.8 15.8zM393.5 64.4c0 4 .6 5.1 2.9 5.1.3 0 1-.2 1-.2l1.6 5.2c-2 .9-3 1.1-5.1 1.1-2.5 0-4.5-.7-6-2.1-1.6-1.4-2.5-3.6-2.5-7.3v-54c0-6.6-1.2-10.4-1.2-10.4l8-1.5s1.3 4.3 1.3 12.1v52zM413.9 64.4c0 4 .6 5.1 2.9 5.1.3 0 1-.2 1-.2l1.6 5.2c-2 .9-3 1.1-5.1 1.1-2.5 0-4.5-.7-6-2.1-1.6-1.4-2.5-3.6-2.5-7.3v-54c0-6.6-1.2-10.4-1.2-10.4l8-1.5s1.3 4.3 1.3 12.1v52zM445.3 22.2c8.5 0 14 3.9 17.5 8.9 3.2 4.6 4.7 10.6 4.7 18.9 0 17-9.1 26-21.9 26-14 0-22-10.3-22-27.1 0-16.6 8.3-26.7 21.7-26.7zm-.1 6.5c-4.5 0-8.7 2.1-10.4 5.5-1.6 3.2-2.5 7.3-2.5 13.3 0 7.2 1.2 13.5 3.2 16.7 1.8 3.1 5.9 5.1 10.3 5.1 5.3 0 9.3-2.8 11-7.7 1.1-3.2 1.5-6 1.5-11 0-7.2-.7-12-2.4-15.3-2-4.5-6.5-6.6-10.7-6.6z"/></g></svg>
|
После Ширина: | Высота: | Размер: 3.2 KiB |
|
@ -0,0 +1 @@
|
|||
<svg width="568" height="148" viewBox="0 0 568 148" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><title>Fill 1 Copy</title><style>use:not(:target) { display: none; } use { fill: #383838; } use[id$="-white"] { fill: #fff; }</style><defs><path id="mozilla-logo" d="M23.39 42.294c1.72 2.656 2.478 5.026 3.44 10.024 6.728-6.568 15.025-10.024 24.08-10.024 8.18 0 14.89 2.656 20.085 8.077 1.4 1.36 2.75 3.113 3.913 4.833 9.038-9.257 17.132-12.91 27.976-12.91 7.722 0 15.042 2.31 19.51 6.156 5.583 4.823 7.353 10.622 7.353 24.124v70.244h-25.092V77.606c0-11.82-1.4-14.107-8.13-14.107-4.822 0-11.6 3.29-17.166 8.305v71.013H54.872V78.533c0-12.326-1.787-15.22-8.97-15.22-4.773 0-11.384 2.472-16.95 7.514v71.99H3.694V73.913c0-14.266-.978-20.43-3.693-25.277l23.39-6.34zm152.244 28.92c-1.772 5.228-2.715 12.168-2.715 22.016 0 11.357 1.162 19.89 3.27 24.89 2.327 5.406 8.146 8.104 13.12 8.104 11.197 0 15.986-10.025 15.986-33.372 0-13.324-1.737-22.025-5.193-26.476-2.48-3.255-6.51-5.194-11.164-5.194-6.19 0-11.216 3.844-13.306 10.032zm46.49-14.265c7.893 9.257 11.418 20.057 11.418 36.07 0 16.982-3.895 28.582-12.412 38.212-7.486 8.483-17.352 13.71-32.563 13.71-26.863 0-44.384-20.076-44.384-51.13 0-31.08 17.706-51.738 44.384-51.738 14.08 0 25.077 4.84 33.558 14.875zm94.588-12.935V61.77l-43.64 63.096h45.344l-6.19 17.952h-72.713v-16.012l46.46-64.645H243.39V44.016h73.322zm40.694-2.328v101.13h-25.853V45.75l25.853-4.063zm3.035-24.874c0 8.89-7.066 15.987-16.002 15.987-8.652 0-15.767-7.098-15.767-15.987 0-8.87 7.353-16.03 16.204-16.03 8.67 0 15.567 7.16 15.567 16.03zm44.048 8.89v76.98c0 17.006.203 19.292 1.755 21.99.977 1.753 3.068 2.698 5.227 2.698.927 0 1.483 0 2.883-.354l4.42 15.42c-4.42 1.73-9.832 2.693-15.43 2.693-11.03 0-19.9-5.197-22.97-13.477-1.94-5.024-2.36-8.127-2.36-22.208V35.7c0-12.918-.337-20.81-1.3-29.722L403.157 0c.926 5.397 1.33 11.77 1.33 25.702zm53.625 0v76.98c0 17.006.22 19.292 1.806 21.99.91 1.753 3 2.698 5.16 2.698.977 0 1.567 0 2.933-.354l4.402 15.42c-4.402 1.73-9.816 2.693-15.432 2.693-11.01 0-19.897-5.197-22.983-13.477-1.973-5.024-2.294-8.127-2.294-22.208V35.7c0-12.918-.387-20.81-1.383-29.722L456.73 0c1.064 5.397 1.383 11.77 1.383 25.702zm74.082 73.894c-17.875 0-24.148 3.254-24.148 15.076 0 7.688 4.89 12.9 11.45 12.9 4.806 0 9.664-2.513 13.492-6.746l.42-21.23h-1.215zM498.687 47.69c9.613-4.064 17.878-5.785 26.983-5.785 16.628 0 27.993 6.157 31.888 17.167 1.282 4.05 1.872 7.135 1.755 17.757L558.687 110v1.752c0 10.607 1.755 14.672 9.313 20.255l-13.73 15.85c-6.038-2.53-11.416-6.98-13.93-11.982-1.905 1.948-4.047 3.837-6.003 5.202-4.79 3.476-11.787 5.414-19.883 5.414-22.005 0-33.96-11.215-33.96-30.86 0-23.196 16.053-34.015 47.485-34.015 1.888 0 3.676 0 5.818.22v-4.03c0-11.02-2.142-14.69-11.653-14.69-8.196 0-17.926 4.03-28.517 11.19l-11.012-18.533c5.245-3.29 9.108-5.203 16.07-8.087z"/></defs><use id="logo" xlink:href="#mozilla-logo"/><use id="logo-white" xlink:href="#mozilla-logo"/></svg>
|
После Ширина: | Высота: | Размер: 2.9 KiB |
|
@ -44,7 +44,7 @@ loop.store.StandaloneMetricsStore = (function() {
|
|||
"connectedToSdkServers",
|
||||
"connectionFailure",
|
||||
"gotMediaPermission",
|
||||
"joinRoom",
|
||||
"metricsLogJoinRoom",
|
||||
"joinedRoom",
|
||||
"leaveRoom",
|
||||
"mediaConnected",
|
||||
|
@ -144,10 +144,20 @@ loop.store.StandaloneMetricsStore = (function() {
|
|||
|
||||
/**
|
||||
* Handles the user clicking the join room button.
|
||||
*
|
||||
* @param {sharedActions.MetricsLogJoinRoom} actionData
|
||||
*/
|
||||
joinRoom: function() {
|
||||
this._storeEvent(METRICS_GA_CATEGORY.general, METRICS_GA_ACTIONS.button,
|
||||
"Join the conversation");
|
||||
metricsLogJoinRoom: function(actionData) {
|
||||
var label;
|
||||
|
||||
if (actionData.userAgentHandledRoom) {
|
||||
label = actionData.ownRoom ? "Joined own room in Firefox" :
|
||||
"Joined in Firefox";
|
||||
} else {
|
||||
label = "Join the conversation";
|
||||
}
|
||||
|
||||
this._storeEvent(METRICS_GA_CATEGORY.general, METRICS_GA_ACTIONS.button, label);
|
||||
},
|
||||
|
||||
/**
|
||||
|
|
|
@ -14,6 +14,103 @@ loop.standaloneRoomViews = (function(mozL10n) {
|
|||
var sharedUtils = loop.shared.utils;
|
||||
var sharedViews = loop.shared.views;
|
||||
|
||||
var ToSView = React.createClass({displayName: "ToSView",
|
||||
propTypes: {
|
||||
dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired
|
||||
},
|
||||
|
||||
_getContent: function() {
|
||||
// We use this technique of static markup as it means we get
|
||||
// just one overall string for L10n to define the structure of
|
||||
// the whole item.
|
||||
return mozL10n.get("legal_text_and_links", {
|
||||
"clientShortname": mozL10n.get("clientShortname2"),
|
||||
"terms_of_use_url": React.renderToStaticMarkup(
|
||||
React.createElement("a", {href: loop.config.legalWebsiteUrl, rel: "noreferrer", target: "_blank"},
|
||||
mozL10n.get("terms_of_use_link_text")
|
||||
)
|
||||
),
|
||||
"privacy_notice_url": React.renderToStaticMarkup(
|
||||
React.createElement("a", {href: loop.config.privacyWebsiteUrl, rel: "noreferrer", target: "_blank"},
|
||||
mozL10n.get("privacy_notice_link_text")
|
||||
)
|
||||
)
|
||||
});
|
||||
},
|
||||
|
||||
recordClick: function(event) {
|
||||
// Check for valid href, as this is clicking on the paragraph -
|
||||
// so the user may be clicking on the text rather than the link.
|
||||
if (event.target && event.target.href) {
|
||||
this.props.dispatcher.dispatch(new sharedActions.RecordClick({
|
||||
linkInfo: event.target.href
|
||||
}));
|
||||
}
|
||||
},
|
||||
|
||||
render: function() {
|
||||
return (
|
||||
React.createElement("p", {
|
||||
className: "terms-service",
|
||||
dangerouslySetInnerHTML: {__html: this._getContent()},
|
||||
onClick: this.recordClick})
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
var StandaloneHandleUserAgentView = React.createClass({displayName: "StandaloneHandleUserAgentView",
|
||||
mixins: [
|
||||
loop.store.StoreMixin("activeRoomStore")
|
||||
],
|
||||
|
||||
propTypes: {
|
||||
dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired
|
||||
},
|
||||
|
||||
getInitialState: function() {
|
||||
return this.getStoreState();
|
||||
},
|
||||
|
||||
handleJoinButton: function() {
|
||||
this.props.dispatcher.dispatch(new sharedActions.JoinRoom());
|
||||
},
|
||||
|
||||
render: function() {
|
||||
var buttonMessage = this.state.roomState === ROOM_STATES.JOINED ?
|
||||
mozL10n.get("rooms_room_joined_own_conversation_label") :
|
||||
mozL10n.get("rooms_room_join_label");
|
||||
|
||||
var buttonClasses = React.addons.classSet({
|
||||
btn: true,
|
||||
"btn-info": true,
|
||||
disabled: this.state.roomState === ROOM_STATES.JOINED
|
||||
});
|
||||
|
||||
// The extra scroller div here is for providing a scroll view for shorter
|
||||
// screens, as the common.css specifies overflow:hidden for the body which
|
||||
// we need in some places.
|
||||
return (
|
||||
React.createElement("div", {className: "handle-user-agent-view-scroller"},
|
||||
React.createElement("div", {className: "handle-user-agent-view"},
|
||||
React.createElement("div", {className: "info-panel"},
|
||||
React.createElement("p", {className: "loop-logo-text", title: mozL10n.get("clientShortname2") }),
|
||||
React.createElement("p", {className: "roomName"}, this.state.roomName),
|
||||
React.createElement("p", {className: "loop-logo"}),
|
||||
React.createElement("button", {
|
||||
className: buttonClasses,
|
||||
onClick: this.handleJoinButton},
|
||||
buttonMessage
|
||||
)
|
||||
),
|
||||
React.createElement(ToSView, {
|
||||
dispatcher: this.props.dispatcher}),
|
||||
React.createElement("p", {className: "mozilla-logo"})
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Handles display of failures, determining the correct messages and
|
||||
* displaying the retry button at appropriate times.
|
||||
|
@ -306,41 +403,12 @@ loop.standaloneRoomViews = (function(mozL10n) {
|
|||
dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired
|
||||
},
|
||||
|
||||
_getContent: function() {
|
||||
// We use this technique of static markup as it means we get
|
||||
// just one overall string for L10n to define the structure of
|
||||
// the whole item.
|
||||
return mozL10n.get("legal_text_and_links", {
|
||||
"clientShortname": mozL10n.get("clientShortname2"),
|
||||
"terms_of_use_url": React.renderToStaticMarkup(
|
||||
React.createElement("a", {href: loop.config.legalWebsiteUrl, rel: "noreferrer", target: "_blank"},
|
||||
mozL10n.get("terms_of_use_link_text")
|
||||
)
|
||||
),
|
||||
"privacy_notice_url": React.renderToStaticMarkup(
|
||||
React.createElement("a", {href: loop.config.privacyWebsiteUrl, rel: "noreferrer", target: "_blank"},
|
||||
mozL10n.get("privacy_notice_link_text")
|
||||
)
|
||||
)
|
||||
});
|
||||
},
|
||||
|
||||
recordClick: function(event) {
|
||||
// Check for valid href, as this is clicking on the paragraph -
|
||||
// so the user may be clicking on the text rather than the link.
|
||||
if (event.target && event.target.href) {
|
||||
this.props.dispatcher.dispatch(new sharedActions.RecordClick({
|
||||
linkInfo: event.target.href
|
||||
}));
|
||||
}
|
||||
},
|
||||
|
||||
render: function() {
|
||||
return (
|
||||
React.createElement("footer", {className: "rooms-footer"},
|
||||
React.createElement("div", {className: "footer-logo"}),
|
||||
React.createElement("p", {dangerouslySetInnerHTML: {__html: this._getContent()},
|
||||
onClick: this.recordClick})
|
||||
React.createElement(ToSView, {
|
||||
dispatcher: this.props.dispatcher})
|
||||
)
|
||||
);
|
||||
}
|
||||
|
@ -596,11 +664,50 @@ loop.standaloneRoomViews = (function(mozL10n) {
|
|||
}
|
||||
});
|
||||
|
||||
var StandaloneRoomControllerView = React.createClass({displayName: "StandaloneRoomControllerView",
|
||||
mixins: [
|
||||
loop.store.StoreMixin("activeRoomStore")
|
||||
],
|
||||
|
||||
propTypes: {
|
||||
dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired,
|
||||
isFirefox: React.PropTypes.bool.isRequired
|
||||
},
|
||||
|
||||
getInitialState: function() {
|
||||
return this.getStoreState();
|
||||
},
|
||||
|
||||
render: function() {
|
||||
// If we don't know yet, don't display anything.
|
||||
if (this.state.userAgentHandlesRoom === undefined) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (this.state.userAgentHandlesRoom) {
|
||||
return (
|
||||
React.createElement(StandaloneHandleUserAgentView, {
|
||||
dispatcher: this.props.dispatcher})
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
React.createElement(StandaloneRoomView, {
|
||||
activeRoomStore: this.getStore(),
|
||||
dispatcher: this.props.dispatcher,
|
||||
isFirefox: this.props.isFirefox})
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
StandaloneHandleUserAgentView: StandaloneHandleUserAgentView,
|
||||
StandaloneRoomControllerView: StandaloneRoomControllerView,
|
||||
StandaloneRoomFailureView: StandaloneRoomFailureView,
|
||||
StandaloneRoomFooter: StandaloneRoomFooter,
|
||||
StandaloneRoomHeader: StandaloneRoomHeader,
|
||||
StandaloneRoomInfoArea: StandaloneRoomInfoArea,
|
||||
StandaloneRoomView: StandaloneRoomView
|
||||
StandaloneRoomView: StandaloneRoomView,
|
||||
ToSView: ToSView
|
||||
};
|
||||
})(navigator.mozL10n);
|
||||
|
|
|
@ -14,6 +14,103 @@ loop.standaloneRoomViews = (function(mozL10n) {
|
|||
var sharedUtils = loop.shared.utils;
|
||||
var sharedViews = loop.shared.views;
|
||||
|
||||
var ToSView = React.createClass({
|
||||
propTypes: {
|
||||
dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired
|
||||
},
|
||||
|
||||
_getContent: function() {
|
||||
// We use this technique of static markup as it means we get
|
||||
// just one overall string for L10n to define the structure of
|
||||
// the whole item.
|
||||
return mozL10n.get("legal_text_and_links", {
|
||||
"clientShortname": mozL10n.get("clientShortname2"),
|
||||
"terms_of_use_url": React.renderToStaticMarkup(
|
||||
<a href={loop.config.legalWebsiteUrl} rel="noreferrer" target="_blank">
|
||||
{mozL10n.get("terms_of_use_link_text")}
|
||||
</a>
|
||||
),
|
||||
"privacy_notice_url": React.renderToStaticMarkup(
|
||||
<a href={loop.config.privacyWebsiteUrl} rel="noreferrer" target="_blank">
|
||||
{mozL10n.get("privacy_notice_link_text")}
|
||||
</a>
|
||||
)
|
||||
});
|
||||
},
|
||||
|
||||
recordClick: function(event) {
|
||||
// Check for valid href, as this is clicking on the paragraph -
|
||||
// so the user may be clicking on the text rather than the link.
|
||||
if (event.target && event.target.href) {
|
||||
this.props.dispatcher.dispatch(new sharedActions.RecordClick({
|
||||
linkInfo: event.target.href
|
||||
}));
|
||||
}
|
||||
},
|
||||
|
||||
render: function() {
|
||||
return (
|
||||
<p
|
||||
className="terms-service"
|
||||
dangerouslySetInnerHTML={{__html: this._getContent()}}
|
||||
onClick={this.recordClick}></p>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
var StandaloneHandleUserAgentView = React.createClass({
|
||||
mixins: [
|
||||
loop.store.StoreMixin("activeRoomStore")
|
||||
],
|
||||
|
||||
propTypes: {
|
||||
dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired
|
||||
},
|
||||
|
||||
getInitialState: function() {
|
||||
return this.getStoreState();
|
||||
},
|
||||
|
||||
handleJoinButton: function() {
|
||||
this.props.dispatcher.dispatch(new sharedActions.JoinRoom());
|
||||
},
|
||||
|
||||
render: function() {
|
||||
var buttonMessage = this.state.roomState === ROOM_STATES.JOINED ?
|
||||
mozL10n.get("rooms_room_joined_own_conversation_label") :
|
||||
mozL10n.get("rooms_room_join_label");
|
||||
|
||||
var buttonClasses = React.addons.classSet({
|
||||
btn: true,
|
||||
"btn-info": true,
|
||||
disabled: this.state.roomState === ROOM_STATES.JOINED
|
||||
});
|
||||
|
||||
// The extra scroller div here is for providing a scroll view for shorter
|
||||
// screens, as the common.css specifies overflow:hidden for the body which
|
||||
// we need in some places.
|
||||
return (
|
||||
<div className="handle-user-agent-view-scroller">
|
||||
<div className="handle-user-agent-view">
|
||||
<div className="info-panel">
|
||||
<p className="loop-logo-text" title={ mozL10n.get("clientShortname2") }></p>
|
||||
<p className="roomName">{ this.state.roomName }</p>
|
||||
<p className="loop-logo" />
|
||||
<button
|
||||
className={buttonClasses}
|
||||
onClick={this.handleJoinButton}>
|
||||
{buttonMessage}
|
||||
</button>
|
||||
</div>
|
||||
<ToSView
|
||||
dispatcher={this.props.dispatcher} />
|
||||
<p className="mozilla-logo" />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Handles display of failures, determining the correct messages and
|
||||
* displaying the retry button at appropriate times.
|
||||
|
@ -306,41 +403,12 @@ loop.standaloneRoomViews = (function(mozL10n) {
|
|||
dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired
|
||||
},
|
||||
|
||||
_getContent: function() {
|
||||
// We use this technique of static markup as it means we get
|
||||
// just one overall string for L10n to define the structure of
|
||||
// the whole item.
|
||||
return mozL10n.get("legal_text_and_links", {
|
||||
"clientShortname": mozL10n.get("clientShortname2"),
|
||||
"terms_of_use_url": React.renderToStaticMarkup(
|
||||
<a href={loop.config.legalWebsiteUrl} rel="noreferrer" target="_blank">
|
||||
{mozL10n.get("terms_of_use_link_text")}
|
||||
</a>
|
||||
),
|
||||
"privacy_notice_url": React.renderToStaticMarkup(
|
||||
<a href={loop.config.privacyWebsiteUrl} rel="noreferrer" target="_blank">
|
||||
{mozL10n.get("privacy_notice_link_text")}
|
||||
</a>
|
||||
)
|
||||
});
|
||||
},
|
||||
|
||||
recordClick: function(event) {
|
||||
// Check for valid href, as this is clicking on the paragraph -
|
||||
// so the user may be clicking on the text rather than the link.
|
||||
if (event.target && event.target.href) {
|
||||
this.props.dispatcher.dispatch(new sharedActions.RecordClick({
|
||||
linkInfo: event.target.href
|
||||
}));
|
||||
}
|
||||
},
|
||||
|
||||
render: function() {
|
||||
return (
|
||||
<footer className="rooms-footer">
|
||||
<div className="footer-logo" />
|
||||
<p dangerouslySetInnerHTML={{__html: this._getContent()}}
|
||||
onClick={this.recordClick}></p>
|
||||
<ToSView
|
||||
dispatcher={this.props.dispatcher} />
|
||||
</footer>
|
||||
);
|
||||
}
|
||||
|
@ -596,11 +664,50 @@ loop.standaloneRoomViews = (function(mozL10n) {
|
|||
}
|
||||
});
|
||||
|
||||
var StandaloneRoomControllerView = React.createClass({
|
||||
mixins: [
|
||||
loop.store.StoreMixin("activeRoomStore")
|
||||
],
|
||||
|
||||
propTypes: {
|
||||
dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired,
|
||||
isFirefox: React.PropTypes.bool.isRequired
|
||||
},
|
||||
|
||||
getInitialState: function() {
|
||||
return this.getStoreState();
|
||||
},
|
||||
|
||||
render: function() {
|
||||
// If we don't know yet, don't display anything.
|
||||
if (this.state.userAgentHandlesRoom === undefined) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (this.state.userAgentHandlesRoom) {
|
||||
return (
|
||||
<StandaloneHandleUserAgentView
|
||||
dispatcher={this.props.dispatcher} />
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<StandaloneRoomView
|
||||
activeRoomStore={this.getStore()}
|
||||
dispatcher={this.props.dispatcher}
|
||||
isFirefox={this.props.isFirefox} />
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
StandaloneHandleUserAgentView: StandaloneHandleUserAgentView,
|
||||
StandaloneRoomControllerView: StandaloneRoomControllerView,
|
||||
StandaloneRoomFailureView: StandaloneRoomFailureView,
|
||||
StandaloneRoomFooter: StandaloneRoomFooter,
|
||||
StandaloneRoomHeader: StandaloneRoomHeader,
|
||||
StandaloneRoomInfoArea: StandaloneRoomInfoArea,
|
||||
StandaloneRoomView: StandaloneRoomView
|
||||
StandaloneRoomView: StandaloneRoomView,
|
||||
ToSView: ToSView
|
||||
};
|
||||
})(navigator.mozL10n);
|
||||
|
|
|
@ -153,7 +153,7 @@ loop.webapp = (function(_, OT, mozL10n) {
|
|||
}
|
||||
case "room": {
|
||||
return (
|
||||
React.createElement(loop.standaloneRoomViews.StandaloneRoomView, {
|
||||
React.createElement(loop.standaloneRoomViews.StandaloneRoomControllerView, {
|
||||
activeRoomStore: this.props.activeRoomStore,
|
||||
dispatcher: this.props.dispatcher,
|
||||
isFirefox: this.state.isFirefox})
|
||||
|
|
|
@ -153,7 +153,7 @@ loop.webapp = (function(_, OT, mozL10n) {
|
|||
}
|
||||
case "room": {
|
||||
return (
|
||||
<loop.standaloneRoomViews.StandaloneRoomView
|
||||
<loop.standaloneRoomViews.StandaloneRoomControllerView
|
||||
activeRoomStore={this.props.activeRoomStore}
|
||||
dispatcher={this.props.dispatcher}
|
||||
isFirefox={this.state.isFirefox} />
|
||||
|
|
|
@ -68,6 +68,7 @@ rooms_room_full_call_to_action_nonFx_label=Download {{brandShortname}} to start
|
|||
rooms_room_full_call_to_action_label=Learn more about {{clientShortname}} »
|
||||
rooms_room_joined_label=Someone has joined the conversation!
|
||||
rooms_room_join_label=Join the conversation
|
||||
rooms_room_joined_own_conversation_label=Enjoy your conversation
|
||||
rooms_display_name_guest=Guest
|
||||
rooms_unavailable_notification_message=Sorry, you cannot join this conversation. The link may be expired or invalid.
|
||||
rooms_media_denied_message=We could not get access to your microphone or camera. Please reload the page to try again.
|
||||
|
|
|
@ -6,6 +6,7 @@ describe("loop.store.ActiveRoomStore", function () {
|
|||
|
||||
var expect = chai.expect;
|
||||
var sharedActions = loop.shared.actions;
|
||||
var sharedUtils = loop.shared.utils;
|
||||
var REST_ERRNOS = loop.shared.utils.REST_ERRNOS;
|
||||
var ROOM_STATES = loop.store.ROOM_STATES;
|
||||
var FAILURE_DETAILS = loop.shared.utils.FAILURE_DETAILS;
|
||||
|
@ -434,20 +435,20 @@ describe("loop.store.ActiveRoomStore", function () {
|
|||
sinon.assert.calledOnce(fakeMozLoop.rooms.get);
|
||||
});
|
||||
|
||||
it("should dispatch an UpdateRoomInfo message with 'no data' failure if neither roomName nor context are supplied", function() {
|
||||
it("should dispatch an UpdateRoomInfo message with failure if neither roomName nor context are supplied", function() {
|
||||
fakeMozLoop.rooms.get.callsArgWith(1, null, {
|
||||
roomUrl: "http://invalid"
|
||||
});
|
||||
|
||||
store.fetchServerData(fetchServerAction);
|
||||
|
||||
sinon.assert.calledOnce(dispatcher.dispatch);
|
||||
sinon.assert.calledWithExactly(dispatcher.dispatch,
|
||||
new sharedActions.UpdateRoomInfo({
|
||||
roomInfoFailure: ROOM_INFO_FAILURES.NO_DATA,
|
||||
roomState: ROOM_STATES.READY,
|
||||
roomUrl: "http://invalid"
|
||||
}));
|
||||
return store.fetchServerData(fetchServerAction).then(function() {
|
||||
sinon.assert.called(dispatcher.dispatch);
|
||||
sinon.assert.calledWithExactly(dispatcher.dispatch,
|
||||
new sharedActions.UpdateRoomInfo({
|
||||
roomInfoFailure: ROOM_INFO_FAILURES.NO_DATA,
|
||||
roomState: ROOM_STATES.READY,
|
||||
roomUrl: "http://invalid"
|
||||
}));
|
||||
});
|
||||
});
|
||||
|
||||
describe("mozLoop.rooms.get returns roomName as a separate field (no context)", function() {
|
||||
|
@ -459,13 +460,13 @@ describe("loop.store.ActiveRoomStore", function () {
|
|||
|
||||
fakeMozLoop.rooms.get.callsArgWith(1, null, roomDetails);
|
||||
|
||||
store.fetchServerData(fetchServerAction);
|
||||
|
||||
sinon.assert.calledOnce(dispatcher.dispatch);
|
||||
sinon.assert.calledWithExactly(dispatcher.dispatch,
|
||||
new sharedActions.UpdateRoomInfo(_.extend({
|
||||
roomState: ROOM_STATES.READY
|
||||
}, roomDetails)));
|
||||
return store.fetchServerData(fetchServerAction).then(function() {
|
||||
sinon.assert.called(dispatcher.dispatch);
|
||||
sinon.assert.calledWithExactly(dispatcher.dispatch,
|
||||
new sharedActions.UpdateRoomInfo(_.extend({
|
||||
roomState: ROOM_STATES.READY
|
||||
}, roomDetails)));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -491,25 +492,25 @@ describe("loop.store.ActiveRoomStore", function () {
|
|||
it("should dispatch UpdateRoomInfo message with 'unsupported' failure if WebCrypto is unsupported", function() {
|
||||
loop.crypto.isSupported.returns(false);
|
||||
|
||||
store.fetchServerData(fetchServerAction);
|
||||
|
||||
sinon.assert.calledOnce(dispatcher.dispatch);
|
||||
sinon.assert.calledWithExactly(dispatcher.dispatch,
|
||||
new sharedActions.UpdateRoomInfo(_.extend({
|
||||
roomInfoFailure: ROOM_INFO_FAILURES.WEB_CRYPTO_UNSUPPORTED,
|
||||
roomState: ROOM_STATES.READY
|
||||
}, expectedDetails)));
|
||||
return store.fetchServerData(fetchServerAction).then(function() {
|
||||
sinon.assert.called(dispatcher.dispatch);
|
||||
sinon.assert.calledWithExactly(dispatcher.dispatch,
|
||||
new sharedActions.UpdateRoomInfo(_.extend({
|
||||
roomInfoFailure: ROOM_INFO_FAILURES.WEB_CRYPTO_UNSUPPORTED,
|
||||
roomState: ROOM_STATES.READY
|
||||
}, expectedDetails)));
|
||||
});
|
||||
});
|
||||
|
||||
it("should dispatch UpdateRoomInfo message with 'no crypto key' failure if there is no crypto key", function() {
|
||||
store.fetchServerData(fetchServerAction);
|
||||
|
||||
sinon.assert.calledOnce(dispatcher.dispatch);
|
||||
sinon.assert.calledWithExactly(dispatcher.dispatch,
|
||||
new sharedActions.UpdateRoomInfo(_.extend({
|
||||
roomInfoFailure: ROOM_INFO_FAILURES.NO_CRYPTO_KEY,
|
||||
roomState: ROOM_STATES.READY
|
||||
}, expectedDetails)));
|
||||
return store.fetchServerData(fetchServerAction).then(function() {
|
||||
sinon.assert.called(dispatcher.dispatch);
|
||||
sinon.assert.calledWithExactly(dispatcher.dispatch,
|
||||
new sharedActions.UpdateRoomInfo(_.extend({
|
||||
roomInfoFailure: ROOM_INFO_FAILURES.NO_CRYPTO_KEY,
|
||||
roomState: ROOM_STATES.READY
|
||||
}, expectedDetails)));
|
||||
});
|
||||
});
|
||||
|
||||
it("should dispatch UpdateRoomInfo message with 'decrypt failed' failure if decryption failed", function() {
|
||||
|
@ -525,14 +526,14 @@ describe("loop.store.ActiveRoomStore", function () {
|
|||
};
|
||||
});
|
||||
|
||||
store.fetchServerData(fetchServerAction);
|
||||
|
||||
sinon.assert.calledOnce(dispatcher.dispatch);
|
||||
sinon.assert.calledWithExactly(dispatcher.dispatch,
|
||||
new sharedActions.UpdateRoomInfo(_.extend({
|
||||
roomInfoFailure: ROOM_INFO_FAILURES.DECRYPT_FAILED,
|
||||
roomState: ROOM_STATES.READY
|
||||
}, expectedDetails)));
|
||||
return store.fetchServerData(fetchServerAction).then(function() {
|
||||
sinon.assert.called(dispatcher.dispatch);
|
||||
sinon.assert.calledWithExactly(dispatcher.dispatch,
|
||||
new sharedActions.UpdateRoomInfo(_.extend({
|
||||
roomInfoFailure: ROOM_INFO_FAILURES.DECRYPT_FAILED,
|
||||
roomState: ROOM_STATES.READY
|
||||
}, expectedDetails)));
|
||||
});
|
||||
});
|
||||
|
||||
it("should dispatch UpdateRoomInfo message with the context if decryption was successful", function() {
|
||||
|
@ -558,18 +559,175 @@ describe("loop.store.ActiveRoomStore", function () {
|
|||
};
|
||||
});
|
||||
|
||||
store.fetchServerData(fetchServerAction);
|
||||
return store.fetchServerData(fetchServerAction).then(function() {
|
||||
var expectedData = _.extend({
|
||||
roomContextUrls: roomContext.urls,
|
||||
roomDescription: roomContext.description,
|
||||
roomName: roomContext.roomName,
|
||||
roomState: ROOM_STATES.READY
|
||||
}, expectedDetails);
|
||||
|
||||
var expectedData = _.extend({
|
||||
roomContextUrls: roomContext.urls,
|
||||
roomDescription: roomContext.description,
|
||||
roomName: roomContext.roomName,
|
||||
roomState: ROOM_STATES.READY
|
||||
}, expectedDetails);
|
||||
sinon.assert.called(dispatcher.dispatch);
|
||||
sinon.assert.calledWithExactly(dispatcher.dispatch,
|
||||
new sharedActions.UpdateRoomInfo(expectedData));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
sinon.assert.calledOnce(dispatcher.dispatch);
|
||||
sinon.assert.calledWithExactly(dispatcher.dispatch,
|
||||
new sharedActions.UpdateRoomInfo(expectedData));
|
||||
describe("User Agent Room Handling", function() {
|
||||
var channelListener, roomDetails;
|
||||
|
||||
beforeEach(function() {
|
||||
sandbox.stub(sharedUtils, "isFirefox").returns(true);
|
||||
|
||||
roomDetails = {
|
||||
roomName: "fakeName",
|
||||
roomUrl: "http://invalid"
|
||||
};
|
||||
fakeMozLoop.rooms.get.callsArgWith(1, null, roomDetails);
|
||||
|
||||
sandbox.stub(window, "addEventListener", function(eventName, listener) {
|
||||
if (eventName === "WebChannelMessageToContent") {
|
||||
channelListener = listener;
|
||||
}
|
||||
});
|
||||
sandbox.stub(window, "removeEventListener", function(eventName, listener) {
|
||||
if (eventName === "WebChannelMessageToContent" &&
|
||||
listener === channelListener) {
|
||||
channelListener = null;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it("should dispatch UserAgentHandlesRoom with false if the user agent is not Firefox", function() {
|
||||
sharedUtils.isFirefox.returns(false);
|
||||
|
||||
return store.fetchServerData(fetchServerAction).then(function() {
|
||||
sinon.assert.called(dispatcher.dispatch);
|
||||
sinon.assert.calledWithExactly(dispatcher.dispatch,
|
||||
new sharedActions.UserAgentHandlesRoom({
|
||||
handlesRoom: false
|
||||
}));
|
||||
});
|
||||
});
|
||||
|
||||
it("should dispatch with false after a timeout if there is no response from the channel", function() {
|
||||
// When the dispatchEvent is called, we know the setup code has run, so
|
||||
// advance the timer.
|
||||
sandbox.stub(window, "dispatchEvent", function() {
|
||||
sandbox.clock.tick(250);
|
||||
});
|
||||
|
||||
return store.fetchServerData(fetchServerAction).then(function() {
|
||||
sinon.assert.called(dispatcher.dispatch);
|
||||
sinon.assert.calledWithExactly(dispatcher.dispatch,
|
||||
new sharedActions.UserAgentHandlesRoom({
|
||||
handlesRoom: false
|
||||
}));
|
||||
});
|
||||
});
|
||||
|
||||
it("should not dispatch if a message is returned not for the link-clicker", function() {
|
||||
// When the dispatchEvent is called, we know the setup code has run, so
|
||||
// advance the timer.
|
||||
sandbox.stub(window, "dispatchEvent", function() {
|
||||
// We call the listener twice, but the first time with an invalid id.
|
||||
// Hence we should only get the dispatch once.
|
||||
channelListener({
|
||||
detail: {
|
||||
id: "invalid-id",
|
||||
message: null
|
||||
}
|
||||
});
|
||||
channelListener({
|
||||
detail: {
|
||||
id: "loop-link-clicker",
|
||||
message: null
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
return store.fetchServerData(fetchServerAction).then(function() {
|
||||
// Although this is only called once for the UserAgentHandlesRoom,
|
||||
// it gets called twice due to the UpdateRoomInfo. Therefore,
|
||||
// we test both results here.
|
||||
sinon.assert.calledTwice(dispatcher.dispatch);
|
||||
sinon.assert.calledWithExactly(dispatcher.dispatch,
|
||||
new sharedActions.UserAgentHandlesRoom({
|
||||
handlesRoom: false
|
||||
}));
|
||||
sinon.assert.calledWithExactly(dispatcher.dispatch,
|
||||
new sharedActions.UpdateRoomInfo(_.extend({
|
||||
roomState: ROOM_STATES.READY
|
||||
}, roomDetails)));
|
||||
});
|
||||
});
|
||||
|
||||
it("should dispatch with false if the user agent does not understand the message", function() {
|
||||
// When the dispatchEvent is called, we know the setup code has run, so
|
||||
// advance the timer.
|
||||
sandbox.stub(window, "dispatchEvent", function() {
|
||||
channelListener({
|
||||
detail: {
|
||||
id: "loop-link-clicker",
|
||||
message: null
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
return store.fetchServerData(fetchServerAction).then(function() {
|
||||
sinon.assert.called(dispatcher.dispatch);
|
||||
sinon.assert.calledWithExactly(dispatcher.dispatch,
|
||||
new sharedActions.UserAgentHandlesRoom({
|
||||
handlesRoom: false
|
||||
}));
|
||||
});
|
||||
});
|
||||
|
||||
it("should dispatch with false if the user agent cannot handle the message", function() {
|
||||
// When the dispatchEvent is called, we know the setup code has run, so
|
||||
// advance the timer.
|
||||
sandbox.stub(window, "dispatchEvent", function() {
|
||||
channelListener({
|
||||
detail: {
|
||||
id: "loop-link-clicker",
|
||||
message: {
|
||||
response: false
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
return store.fetchServerData(fetchServerAction).then(function() {
|
||||
sinon.assert.called(dispatcher.dispatch);
|
||||
sinon.assert.calledWithExactly(dispatcher.dispatch,
|
||||
new sharedActions.UserAgentHandlesRoom({
|
||||
handlesRoom: false
|
||||
}));
|
||||
});
|
||||
});
|
||||
|
||||
it("should dispatch with true if the user agent can handle the message", function() {
|
||||
// When the dispatchEvent is called, we know the setup code has run, so
|
||||
// advance the timer.
|
||||
sandbox.stub(window, "dispatchEvent", function() {
|
||||
channelListener({
|
||||
detail: {
|
||||
id: "loop-link-clicker",
|
||||
message: {
|
||||
response: true
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
return store.fetchServerData(fetchServerAction).then(function() {
|
||||
sinon.assert.called(dispatcher.dispatch);
|
||||
sinon.assert.calledWithExactly(dispatcher.dispatch,
|
||||
new sharedActions.UserAgentHandlesRoom({
|
||||
handlesRoom: true
|
||||
}));
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -624,6 +782,20 @@ describe("loop.store.ActiveRoomStore", function () {
|
|||
});
|
||||
});
|
||||
|
||||
describe("#userAgentHandlesRoom", function() {
|
||||
it("should update the store state", function() {
|
||||
store.setStoreState({
|
||||
UserAgentHandlesRoom: false
|
||||
});
|
||||
|
||||
store.userAgentHandlesRoom(new sharedActions.UserAgentHandlesRoom({
|
||||
handlesRoom: true
|
||||
}));
|
||||
|
||||
expect(store.getStoreState().userAgentHandlesRoom).eql(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe("#updateSocialShareInfo", function() {
|
||||
var fakeSocialShareInfo;
|
||||
|
||||
|
@ -659,32 +831,138 @@ describe("loop.store.ActiveRoomStore", function () {
|
|||
expect(store.getStoreState().failureReason).eql(undefined);
|
||||
});
|
||||
|
||||
it("should set the state to MEDIA_WAIT if media devices are present", function() {
|
||||
sandbox.stub(loop.shared.utils, "hasAudioOrVideoDevices").callsArgWith(0, true);
|
||||
describe("Standalone Handles Room", function() {
|
||||
it("should dispatch a MetricsLogJoinRoom action", function() {
|
||||
store.joinRoom();
|
||||
|
||||
store.joinRoom();
|
||||
sinon.assert.calledOnce(dispatcher.dispatch);
|
||||
sinon.assert.calledWithExactly(dispatcher.dispatch,
|
||||
new sharedActions.MetricsLogJoinRoom({
|
||||
userAgentHandledRoom: false
|
||||
}));
|
||||
});
|
||||
|
||||
expect(store.getStoreState().roomState).eql(ROOM_STATES.MEDIA_WAIT);
|
||||
it("should set the state to MEDIA_WAIT if media devices are present", function() {
|
||||
sandbox.stub(loop.shared.utils, "hasAudioOrVideoDevices").callsArgWith(0, true);
|
||||
|
||||
store.joinRoom();
|
||||
|
||||
expect(store.getStoreState().roomState).eql(ROOM_STATES.MEDIA_WAIT);
|
||||
});
|
||||
|
||||
it("should not set the state to MEDIA_WAIT if no media devices are present", function() {
|
||||
sandbox.stub(loop.shared.utils, "hasAudioOrVideoDevices").callsArgWith(0, false);
|
||||
|
||||
store.joinRoom();
|
||||
|
||||
expect(store.getStoreState().roomState).eql(ROOM_STATES.READY);
|
||||
});
|
||||
|
||||
it("should dispatch `ConnectionFailure` if no media devices are present", function() {
|
||||
sandbox.stub(loop.shared.utils, "hasAudioOrVideoDevices").callsArgWith(0, false);
|
||||
|
||||
store.joinRoom();
|
||||
|
||||
sinon.assert.called(dispatcher.dispatch);
|
||||
sinon.assert.calledWithExactly(dispatcher.dispatch,
|
||||
new sharedActions.ConnectionFailure({
|
||||
reason: FAILURE_DETAILS.NO_MEDIA
|
||||
}));
|
||||
});
|
||||
});
|
||||
|
||||
it("should not set the state to MEDIA_WAIT if no media devices are present", function() {
|
||||
sandbox.stub(loop.shared.utils, "hasAudioOrVideoDevices").callsArgWith(0, false);
|
||||
describe("Firefox Handles Room", function() {
|
||||
var channelListener;
|
||||
|
||||
store.joinRoom();
|
||||
beforeEach(function() {
|
||||
store.setStoreState({
|
||||
userAgentHandlesRoom: true,
|
||||
roomToken: "fakeToken",
|
||||
standalone: true
|
||||
});
|
||||
|
||||
expect(store.getStoreState().roomState).eql(ROOM_STATES.READY);
|
||||
});
|
||||
sandbox.stub(window, "addEventListener", function(eventName, listener) {
|
||||
if (eventName === "WebChannelMessageToContent") {
|
||||
channelListener = listener;
|
||||
}
|
||||
});
|
||||
sandbox.stub(window, "removeEventListener", function(eventName, listener) {
|
||||
if (eventName === "WebChannelMessageToContent" &&
|
||||
listener === channelListener) {
|
||||
channelListener = null;
|
||||
}
|
||||
});
|
||||
|
||||
it("should dispatch `ConnectionFailure` if no media devices are present", function() {
|
||||
sandbox.stub(loop.shared.utils, "hasAudioOrVideoDevices").callsArgWith(0, false);
|
||||
sandbox.stub(console, "error");
|
||||
});
|
||||
|
||||
store.joinRoom();
|
||||
it("should dispatch a MetricsLogJoinRoom action", function() {
|
||||
store.joinRoom();
|
||||
|
||||
sinon.assert.calledOnce(dispatcher.dispatch);
|
||||
sinon.assert.calledWithExactly(dispatcher.dispatch,
|
||||
new sharedActions.ConnectionFailure({
|
||||
reason: FAILURE_DETAILS.NO_MEDIA
|
||||
sinon.assert.calledOnce(dispatcher.dispatch);
|
||||
sinon.assert.calledWithExactly(dispatcher.dispatch,
|
||||
new sharedActions.MetricsLogJoinRoom({
|
||||
userAgentHandledRoom: true,
|
||||
ownRoom: true
|
||||
}));
|
||||
});
|
||||
|
||||
it("should dispatch an event to Firefox", function() {
|
||||
sandbox.stub(window, "dispatchEvent");
|
||||
|
||||
store.joinRoom();
|
||||
|
||||
sinon.assert.calledOnce(window.dispatchEvent);
|
||||
sinon.assert.calledWithExactly(window.dispatchEvent, new window.CustomEvent(
|
||||
"WebChannelMessageToChrome", {
|
||||
detail: {
|
||||
id: "loop-link-clicker",
|
||||
message: {
|
||||
command: "openRoom",
|
||||
roomToken: "fakeToken"
|
||||
}
|
||||
}
|
||||
}));
|
||||
});
|
||||
|
||||
it("should log an error if Firefox doesn't handle the room", function() {
|
||||
// Start the join.
|
||||
store.joinRoom();
|
||||
|
||||
// Pretend Firefox calls back.
|
||||
channelListener({
|
||||
detail: {
|
||||
id: "loop-link-clicker",
|
||||
message: null
|
||||
}
|
||||
});
|
||||
|
||||
sinon.assert.calledOnce(console.error);
|
||||
});
|
||||
|
||||
it("should dispatch a JoinedRoom action if the room was successfully opened", function() {
|
||||
// Start the join.
|
||||
store.joinRoom();
|
||||
|
||||
// Pretend Firefox calls back.
|
||||
channelListener({
|
||||
detail: {
|
||||
id: "loop-link-clicker",
|
||||
message: {
|
||||
response: true
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
sinon.assert.called(dispatcher.dispatch);
|
||||
sinon.assert.calledWithExactly(dispatcher.dispatch,
|
||||
new sharedActions.JoinedRoom({
|
||||
apiKey: "",
|
||||
sessionToken: "",
|
||||
sessionId: "",
|
||||
expires: 0
|
||||
}));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -762,6 +1040,17 @@ describe("loop.store.ActiveRoomStore", function () {
|
|||
expect(store._storeState.roomState).eql(ROOM_STATES.JOINED);
|
||||
});
|
||||
|
||||
it("should set the state to `JOINED` when Firefox handles the room", function() {
|
||||
store.setStoreState({
|
||||
userAgentHandlesRoom: true,
|
||||
standalone: true
|
||||
});
|
||||
|
||||
store.joinedRoom(new sharedActions.JoinedRoom(fakeJoinedData));
|
||||
|
||||
expect(store._storeState.roomState).eql(ROOM_STATES.JOINED);
|
||||
});
|
||||
|
||||
it("should store the session and api values", function() {
|
||||
store.joinedRoom(new sharedActions.JoinedRoom(fakeJoinedData));
|
||||
|
||||
|
@ -771,6 +1060,20 @@ describe("loop.store.ActiveRoomStore", function () {
|
|||
expect(state.sessionId).eql(fakeJoinedData.sessionId);
|
||||
});
|
||||
|
||||
it("should not store the session and api values when Firefox handles the room", function() {
|
||||
store.setStoreState({
|
||||
userAgentHandlesRoom: true,
|
||||
standalone: true
|
||||
});
|
||||
|
||||
store.joinedRoom(new sharedActions.JoinedRoom(fakeJoinedData));
|
||||
|
||||
var state = store.getStoreState();
|
||||
expect(state.apiKey).eql(undefined);
|
||||
expect(state.sessionToken).eql(undefined);
|
||||
expect(state.sessionId).eql(undefined);
|
||||
});
|
||||
|
||||
it("should start the session connection with the sdk", function() {
|
||||
var actionData = new sharedActions.JoinedRoom(fakeJoinedData);
|
||||
|
||||
|
|
|
@ -86,15 +86,6 @@ describe("loop.store.StandaloneMetricsStore", function() {
|
|||
"Media granted");
|
||||
});
|
||||
|
||||
it("should log an event on JoinRoom", function() {
|
||||
store.joinRoom();
|
||||
|
||||
sinon.assert.calledOnce(window.ga);
|
||||
sinon.assert.calledWithExactly(window.ga,
|
||||
"send", "event", METRICS_GA_CATEGORY.general, METRICS_GA_ACTIONS.button,
|
||||
"Join the conversation");
|
||||
});
|
||||
|
||||
it("should log an event on JoinedRoom", function() {
|
||||
store.joinedRoom();
|
||||
|
||||
|
@ -150,6 +141,43 @@ describe("loop.store.StandaloneMetricsStore", function() {
|
|||
"send", "event", METRICS_GA_CATEGORY.general, METRICS_GA_ACTIONS.button,
|
||||
"Retry failed room");
|
||||
});
|
||||
|
||||
describe("MetricsLogJoinRoom", function() {
|
||||
it("should log a 'Join the conversation' event if not joined by Firefox", function() {
|
||||
store.metricsLogJoinRoom({
|
||||
userAgentHandledRoom: false
|
||||
});
|
||||
|
||||
sinon.assert.calledOnce(window.ga);
|
||||
sinon.assert.calledWithExactly(window.ga,
|
||||
"send", "event", METRICS_GA_CATEGORY.general, METRICS_GA_ACTIONS.button,
|
||||
"Join the conversation");
|
||||
});
|
||||
|
||||
it("should log a 'Joined own room in Firefox' event if joining the own room in Firefox", function() {
|
||||
store.metricsLogJoinRoom({
|
||||
userAgentHandledRoom: true,
|
||||
ownRoom: true
|
||||
});
|
||||
|
||||
sinon.assert.calledOnce(window.ga);
|
||||
sinon.assert.calledWithExactly(window.ga,
|
||||
"send", "event", METRICS_GA_CATEGORY.general, METRICS_GA_ACTIONS.button,
|
||||
"Joined own room in Firefox");
|
||||
});
|
||||
|
||||
it("should log a 'Joined in Firefox' event if joining a non-own room in Firefox", function() {
|
||||
store.metricsLogJoinRoom({
|
||||
userAgentHandledRoom: true,
|
||||
ownRoom: false
|
||||
});
|
||||
|
||||
sinon.assert.calledOnce(window.ga);
|
||||
sinon.assert.calledWithExactly(window.ga,
|
||||
"send", "event", METRICS_GA_CATEGORY.general, METRICS_GA_ACTIONS.button,
|
||||
"Joined in Firefox");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("Store Change Handlers", function() {
|
||||
|
|
|
@ -49,6 +49,8 @@ describe("loop.standaloneRoomViews", function() {
|
|||
switch(key) {
|
||||
case "standalone_title_with_room_name":
|
||||
return args.roomName + " — " + args.clientShortname;
|
||||
case "legal_text_and_links":
|
||||
return args.terms_of_use_url + " " + args.privacy_notice_url;
|
||||
default:
|
||||
return key;
|
||||
}
|
||||
|
@ -66,6 +68,123 @@ describe("loop.standaloneRoomViews", function() {
|
|||
view = null;
|
||||
});
|
||||
|
||||
|
||||
describe("TosView", function() {
|
||||
var origConfig, node;
|
||||
|
||||
function mountTestComponent() {
|
||||
return TestUtils.renderIntoDocument(
|
||||
React.createElement(
|
||||
loop.standaloneRoomViews.ToSView, {
|
||||
dispatcher: dispatcher
|
||||
}));
|
||||
}
|
||||
|
||||
beforeEach(function() {
|
||||
origConfig = loop.config;
|
||||
loop.config = {
|
||||
legalWebsiteUrl: "http://fakelegal/",
|
||||
privacyWebsiteUrl: "http://fakeprivacy/"
|
||||
};
|
||||
|
||||
view = mountTestComponent();
|
||||
node = view.getDOMNode();
|
||||
});
|
||||
|
||||
afterEach(function() {
|
||||
loop.config = origConfig;
|
||||
});
|
||||
|
||||
it("should dispatch a link click action when the ToS link is clicked", function() {
|
||||
// [0] is the first link, the legal one.
|
||||
var link = node.querySelectorAll("a")[0];
|
||||
|
||||
TestUtils.Simulate.click(node, { target: link });
|
||||
|
||||
sinon.assert.calledOnce(dispatcher.dispatch);
|
||||
sinon.assert.calledWithExactly(dispatcher.dispatch,
|
||||
new sharedActions.RecordClick({
|
||||
linkInfo: loop.config.legalWebsiteUrl
|
||||
}));
|
||||
});
|
||||
|
||||
it("should dispatch a link click action when the Privacy link is clicked", function() {
|
||||
// [0] is the first link, the legal one.
|
||||
var link = node.querySelectorAll("a")[1];
|
||||
|
||||
TestUtils.Simulate.click(node, { target: link });
|
||||
|
||||
sinon.assert.calledOnce(dispatcher.dispatch);
|
||||
sinon.assert.calledWithExactly(dispatcher.dispatch,
|
||||
new sharedActions.RecordClick({
|
||||
linkInfo: loop.config.privacyWebsiteUrl
|
||||
}));
|
||||
});
|
||||
|
||||
it("should not dispatch an action when the text is clicked", function() {
|
||||
TestUtils.Simulate.click(node, { target: node });
|
||||
|
||||
sinon.assert.notCalled(dispatcher.dispatch);
|
||||
});
|
||||
});
|
||||
|
||||
describe("StandaloneHandleUserAgentView", function() {
|
||||
function mountTestComponent() {
|
||||
return TestUtils.renderIntoDocument(
|
||||
React.createElement(
|
||||
loop.standaloneRoomViews.StandaloneHandleUserAgentView, {
|
||||
dispatcher: dispatcher
|
||||
}));
|
||||
}
|
||||
|
||||
it("should display a join room button if the state is not ROOM_JOINED", function() {
|
||||
activeRoomStore.setStoreState({
|
||||
roomState: ROOM_STATES.READY
|
||||
});
|
||||
|
||||
view = mountTestComponent();
|
||||
var button = view.getDOMNode().querySelector(".info-panel > button");
|
||||
|
||||
expect(button.textContent).eql("rooms_room_join_label");
|
||||
});
|
||||
|
||||
it("should dispatch a JoinRoom action when the join room button is clicked", function() {
|
||||
activeRoomStore.setStoreState({
|
||||
roomState: ROOM_STATES.READY
|
||||
});
|
||||
|
||||
view = mountTestComponent();
|
||||
var button = view.getDOMNode().querySelector(".info-panel > button");
|
||||
|
||||
TestUtils.Simulate.click(button);
|
||||
|
||||
sinon.assert.calledOnce(dispatcher.dispatch);
|
||||
sinon.assert.calledWithExactly(dispatcher.dispatch, new sharedActions.JoinRoom());
|
||||
});
|
||||
|
||||
it("should display a enjoy your conversation button if the state is ROOM_JOINED", function() {
|
||||
activeRoomStore.setStoreState({
|
||||
roomState: ROOM_STATES.JOINED
|
||||
});
|
||||
|
||||
view = mountTestComponent();
|
||||
var button = view.getDOMNode().querySelector(".info-panel > button");
|
||||
|
||||
expect(button.textContent).eql("rooms_room_joined_own_conversation_label");
|
||||
});
|
||||
|
||||
it("should disable the enjoy your conversation button if the state is ROOM_JOINED", function() {
|
||||
activeRoomStore.setStoreState({
|
||||
roomState: ROOM_STATES.JOINED
|
||||
});
|
||||
|
||||
view = mountTestComponent();
|
||||
var button = view.getDOMNode().querySelector(".info-panel > button");
|
||||
|
||||
expect(button.classList.contains("disabled")).eql(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe("StandaloneRoomHeader", function() {
|
||||
function mountTestComponent() {
|
||||
return TestUtils.renderIntoDocument(
|
||||
|
@ -804,4 +923,47 @@ describe("loop.standaloneRoomViews", function() {
|
|||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("StandaloneRoomControllerView", function() {
|
||||
function mountTestComponent() {
|
||||
return TestUtils.renderIntoDocument(
|
||||
React.createElement(
|
||||
loop.standaloneRoomViews.StandaloneRoomControllerView, {
|
||||
dispatcher: dispatcher,
|
||||
isFirefox: true
|
||||
}));
|
||||
}
|
||||
|
||||
it("should not display anything if it is not known if Firefox can handle the room", function() {
|
||||
activeRoomStore.setStoreState({
|
||||
userAgentHandlesRoom: undefined
|
||||
});
|
||||
|
||||
view = mountTestComponent();
|
||||
|
||||
expect(view.getDOMNode()).eql(null);
|
||||
});
|
||||
|
||||
it("should render StandaloneHandleUserAgentView if Firefox can handle the room", function() {
|
||||
activeRoomStore.setStoreState({
|
||||
userAgentHandlesRoom: true
|
||||
});
|
||||
|
||||
view = mountTestComponent();
|
||||
|
||||
TestUtils.findRenderedComponentWithType(view,
|
||||
loop.standaloneRoomViews.StandaloneHandleUserAgentView);
|
||||
});
|
||||
|
||||
it("should render StandaloneRoomView if Firefox cannot handle the room", function() {
|
||||
activeRoomStore.setStoreState({
|
||||
userAgentHandlesRoom: false
|
||||
});
|
||||
|
||||
view = mountTestComponent();
|
||||
|
||||
TestUtils.findRenderedComponentWithType(view,
|
||||
loop.standaloneRoomViews.StandaloneRoomView);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -119,14 +119,14 @@ describe("loop.webapp", function() {
|
|||
loop.webapp.UnsupportedBrowserView);
|
||||
});
|
||||
|
||||
it("should display the StandaloneRoomView for `room` window type",
|
||||
it("should display the StandaloneRoomControllerView for `room` window type",
|
||||
function() {
|
||||
standaloneAppStore.setStoreState({windowType: "room", isFirefox: true});
|
||||
|
||||
var webappRootView = mountTestComponent();
|
||||
|
||||
TestUtils.findRenderedComponentWithType(webappRootView,
|
||||
loop.standaloneRoomViews.StandaloneRoomView);
|
||||
loop.standaloneRoomViews.StandaloneRoomControllerView);
|
||||
});
|
||||
|
||||
it("should display the HomeView for `home` window type", function() {
|
||||
|
|
|
@ -36,6 +36,7 @@
|
|||
var UnsupportedBrowserView = loop.webapp.UnsupportedBrowserView;
|
||||
var UnsupportedDeviceView = loop.webapp.UnsupportedDeviceView;
|
||||
var StandaloneRoomView = loop.standaloneRoomViews.StandaloneRoomView;
|
||||
var StandaloneHandleUserAgentView = loop.standaloneRoomViews.StandaloneHandleUserAgentView;
|
||||
|
||||
// 3. Shared components
|
||||
var ConversationToolbar = loop.shared.views.ConversationToolbar;
|
||||
|
@ -1515,6 +1516,21 @@
|
|||
)
|
||||
),
|
||||
|
||||
React.createElement(Section, {name: "StandaloneHandleUserAgentView"},
|
||||
React.createElement(FramedExample, {
|
||||
cssClass: "standalone",
|
||||
dashed: true,
|
||||
height: 483,
|
||||
summary: "Standalone Room Handle Join in Firefox",
|
||||
width: 644},
|
||||
React.createElement("div", {className: "standalone"},
|
||||
React.createElement(StandaloneHandleUserAgentView, {
|
||||
activeRoomStore: readyRoomStore,
|
||||
dispatcher: dispatcher})
|
||||
)
|
||||
)
|
||||
),
|
||||
|
||||
React.createElement(Section, {name: "StandaloneRoomView"},
|
||||
React.createElement(FramedExample, {cssClass: "standalone",
|
||||
dashed: true,
|
||||
|
|
|
@ -36,6 +36,7 @@
|
|||
var UnsupportedBrowserView = loop.webapp.UnsupportedBrowserView;
|
||||
var UnsupportedDeviceView = loop.webapp.UnsupportedDeviceView;
|
||||
var StandaloneRoomView = loop.standaloneRoomViews.StandaloneRoomView;
|
||||
var StandaloneHandleUserAgentView = loop.standaloneRoomViews.StandaloneHandleUserAgentView;
|
||||
|
||||
// 3. Shared components
|
||||
var ConversationToolbar = loop.shared.views.ConversationToolbar;
|
||||
|
@ -1515,6 +1516,21 @@
|
|||
</FramedExample>
|
||||
</Section>
|
||||
|
||||
<Section name="StandaloneHandleUserAgentView">
|
||||
<FramedExample
|
||||
cssClass="standalone"
|
||||
dashed={true}
|
||||
height={483}
|
||||
summary="Standalone Room Handle Join in Firefox"
|
||||
width={644} >
|
||||
<div className="standalone">
|
||||
<StandaloneHandleUserAgentView
|
||||
activeRoomStore={readyRoomStore}
|
||||
dispatcher={dispatcher} />
|
||||
</div>
|
||||
</FramedExample>
|
||||
</Section>
|
||||
|
||||
<Section name="StandaloneRoomView">
|
||||
<FramedExample cssClass="standalone"
|
||||
dashed={true}
|
||||
|
|
|
@ -352,7 +352,7 @@ nsGNOMEShellService::GetShouldSkipCheckDefaultBrowser(bool* aResult)
|
|||
if (NS_FAILED(rv)) {
|
||||
return rv;
|
||||
}
|
||||
if (defaultBrowserCheckCount < 3) {
|
||||
if (defaultBrowserCheckCount < 4) {
|
||||
*aResult = false;
|
||||
return prefs->SetIntPref(PREF_DEFAULTBROWSERCHECKCOUNT,
|
||||
defaultBrowserCheckCount + 1);
|
||||
|
|
|
@ -130,7 +130,7 @@ nsMacShellService::GetShouldSkipCheckDefaultBrowser(bool* aResult)
|
|||
if (NS_FAILED(rv)) {
|
||||
return rv;
|
||||
}
|
||||
if (defaultBrowserCheckCount < 3) {
|
||||
if (defaultBrowserCheckCount < 4) {
|
||||
*aResult = false;
|
||||
return prefs->SetIntPref(PREF_DEFAULTBROWSERCHECKCOUNT,
|
||||
defaultBrowserCheckCount + 1);
|
||||
|
|
|
@ -1011,7 +1011,7 @@ nsWindowsShellService::GetShouldSkipCheckDefaultBrowser(bool* aResult)
|
|||
if (NS_FAILED(rv)) {
|
||||
return rv;
|
||||
}
|
||||
if (defaultBrowserCheckCount < 3) {
|
||||
if (defaultBrowserCheckCount < 4) {
|
||||
*aResult = false;
|
||||
return prefs->SetIntPref(PREF_DEFAULTBROWSERCHECKCOUNT,
|
||||
defaultBrowserCheckCount + 1);
|
||||
|
|
|
@ -285,4 +285,22 @@
|
|||
:root[devtoolstheme="dark"] #titlebar-close {
|
||||
list-style-image: url(chrome://browser/skin/caption-buttons.svg#close-white);
|
||||
}
|
||||
|
||||
/* ... and normal ones for the light theme on Windows 10 */
|
||||
:root[devtoolstheme="light"] #titlebar-min {
|
||||
list-style-image: url(chrome://browser/skin/caption-buttons.svg#minimize);
|
||||
}
|
||||
:root[devtoolstheme="light"] #titlebar-max {
|
||||
list-style-image: url(chrome://browser/skin/caption-buttons.svg#maximize);
|
||||
}
|
||||
#main-window[devtoolstheme="light"][sizemode="maximized"] #titlebar-max {
|
||||
list-style-image: url(chrome://browser/skin/caption-buttons.svg#restore);
|
||||
}
|
||||
:root[devtoolstheme="light"] #titlebar-close {
|
||||
list-style-image: url(chrome://browser/skin/caption-buttons.svg#close);
|
||||
}
|
||||
|
||||
:root[devtoolstheme="light"] #titlebar-close:hover {
|
||||
list-style-image: url(chrome://browser/skin/caption-buttons.svg#close-white);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -529,7 +529,8 @@ class Automation(object):
|
|||
xrePath = None, certPath = None,
|
||||
debuggerInfo = None, symbolsPath = None,
|
||||
timeout = -1, maxTime = None, onLaunch = None,
|
||||
detectShutdownLeaks = False, screenshotOnFail=False, testPath=None, bisectChunk=None):
|
||||
detectShutdownLeaks = False, screenshotOnFail=False, testPath=None, bisectChunk=None,
|
||||
valgrindPath=None, valgrindArgs=None, valgrindSuppFiles=None):
|
||||
"""
|
||||
Run the app, log the duration it took to execute, return the status code.
|
||||
Kills the app if it runs for longer than |maxTime| seconds, or outputs nothing for |timeout| seconds.
|
||||
|
|
|
@ -191,15 +191,15 @@ public class FennecNativeActions implements Actions {
|
|||
}
|
||||
|
||||
public void sendPreferencesGetEvent(int requestId, String[] prefNames) {
|
||||
GeckoAppShell.sendEventToGecko(GeckoEvent.createPreferencesGetEvent(requestId, prefNames));
|
||||
PrefsHelper.getPrefsById(requestId, prefNames, /* observe */ false);
|
||||
}
|
||||
|
||||
public void sendPreferencesObserveEvent(int requestId, String[] prefNames) {
|
||||
GeckoAppShell.sendEventToGecko(GeckoEvent.createPreferencesObserveEvent(requestId, prefNames));
|
||||
PrefsHelper.getPrefsById(requestId, prefNames, /* observe */ true);
|
||||
}
|
||||
|
||||
public void sendPreferencesRemoveObserversEvent(int requestId) {
|
||||
GeckoAppShell.sendEventToGecko(GeckoEvent.createPreferencesRemoveObserversEvent(requestId));
|
||||
PrefsHelper.removePrefsObserver(requestId);
|
||||
}
|
||||
|
||||
class PaintExpecter implements RepeatedEventExpecter {
|
||||
|
|
|
@ -7641,7 +7641,6 @@ nsDocShell::EndPageLoad(nsIWebProgress* aProgress,
|
|||
aStatus == NS_ERROR_UNWANTED_URI ||
|
||||
aStatus == NS_ERROR_UNSAFE_CONTENT_TYPE ||
|
||||
aStatus == NS_ERROR_REMOTE_XUL ||
|
||||
aStatus == NS_ERROR_OFFLINE ||
|
||||
aStatus == NS_ERROR_INTERCEPTION_FAILED ||
|
||||
aStatus == NS_ERROR_OPAQUE_INTERCEPTION_DISABLED ||
|
||||
aStatus == NS_ERROR_BAD_OPAQUE_INTERCEPTION_REQUEST_MODE ||
|
||||
|
|
|
@ -332,6 +332,13 @@ nsSHEntryShared::AttributeWillChange(nsIDocument* aDocument,
|
|||
{
|
||||
}
|
||||
|
||||
void
|
||||
nsSHEntryShared::NativeAnonymousChildListChange(nsIDocument* aDocument,
|
||||
nsIContent* aContent,
|
||||
bool aIsRemove)
|
||||
{
|
||||
}
|
||||
|
||||
void
|
||||
nsSHEntryShared::AttributeChanged(nsIDocument* aDocument,
|
||||
dom::Element* aElement,
|
||||
|
|
|
@ -1631,6 +1631,9 @@ Element::BindToTree(nsIDocument* aDocument, nsIContent* aParent,
|
|||
}
|
||||
|
||||
nsNodeUtils::ParentChainChanged(this);
|
||||
if (!hadParent && IsRootOfNativeAnonymousSubtree()) {
|
||||
nsNodeUtils::NativeAnonymousChildListChange(this, false);
|
||||
}
|
||||
|
||||
if (HasID()) {
|
||||
AddToIdTable(DoGetID());
|
||||
|
@ -1745,6 +1748,10 @@ Element::UnbindFromTree(bool aDeep, bool aNullParent)
|
|||
}
|
||||
}
|
||||
|
||||
if (this->IsRootOfNativeAnonymousSubtree()) {
|
||||
nsNodeUtils::NativeAnonymousChildListChange(this, true);
|
||||
}
|
||||
|
||||
if (GetParent()) {
|
||||
nsRefPtr<nsINode> p;
|
||||
p.swap(mParent);
|
||||
|
|
|
@ -114,6 +114,38 @@ nsMutationReceiver::Disconnect(bool aRemoveFromObserver)
|
|||
}
|
||||
}
|
||||
|
||||
void
|
||||
nsMutationReceiver::NativeAnonymousChildListChange(nsIDocument* aDocument,
|
||||
nsIContent* aContent,
|
||||
bool aIsRemove) {
|
||||
if (!NativeAnonymousChildList()) {
|
||||
return;
|
||||
}
|
||||
|
||||
nsINode* parent = aContent->GetParentNode();
|
||||
if (!parent ||
|
||||
(!Subtree() && Target() != parent) ||
|
||||
(Subtree() && RegisterTarget()->SubtreeRoot() != parent->SubtreeRoot())) {
|
||||
return;
|
||||
}
|
||||
|
||||
nsDOMMutationRecord* m =
|
||||
Observer()->CurrentRecord(nsGkAtoms::nativeAnonymousChildList);
|
||||
|
||||
if (m->mTarget) {
|
||||
return;
|
||||
}
|
||||
m->mTarget = parent;
|
||||
|
||||
if (aIsRemove) {
|
||||
m->mRemovedNodes = new nsSimpleContentList(parent);
|
||||
m->mRemovedNodes->AppendElement(aContent);
|
||||
} else {
|
||||
m->mAddedNodes = new nsSimpleContentList(parent);
|
||||
m->mAddedNodes->AppendElement(aContent);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
nsMutationReceiver::AttributeWillChange(nsIDocument* aDocument,
|
||||
mozilla::dom::Element* aElement,
|
||||
|
@ -586,6 +618,8 @@ nsDOMMutationObserver::Observe(nsINode& aTarget,
|
|||
bool attributeOldValue =
|
||||
aOptions.mAttributeOldValue.WasPassed() &&
|
||||
aOptions.mAttributeOldValue.Value();
|
||||
bool nativeAnonymousChildList = aOptions.mNativeAnonymousChildList &&
|
||||
nsContentUtils::ThreadsafeIsCallerChrome();
|
||||
bool characterDataOldValue =
|
||||
aOptions.mCharacterDataOldValue.WasPassed() &&
|
||||
aOptions.mCharacterDataOldValue.Value();
|
||||
|
@ -605,7 +639,8 @@ nsDOMMutationObserver::Observe(nsINode& aTarget,
|
|||
characterData = true;
|
||||
}
|
||||
|
||||
if (!(childList || attributes || characterData || animations)) {
|
||||
if (!(childList || attributes || characterData || animations ||
|
||||
nativeAnonymousChildList)) {
|
||||
aRv.Throw(NS_ERROR_DOM_TYPE_ERR);
|
||||
return;
|
||||
}
|
||||
|
@ -655,6 +690,7 @@ nsDOMMutationObserver::Observe(nsINode& aTarget,
|
|||
r->SetSubtree(subtree);
|
||||
r->SetAttributeOldValue(attributeOldValue);
|
||||
r->SetCharacterDataOldValue(characterDataOldValue);
|
||||
r->SetNativeAnonymousChildList(nativeAnonymousChildList);
|
||||
r->SetAttributeFilter(filters);
|
||||
r->SetAllAttributes(allAttrs);
|
||||
r->SetAnimations(animations);
|
||||
|
@ -715,6 +751,7 @@ nsDOMMutationObserver::GetObservingInfo(
|
|||
info.mSubtree = mr->Subtree();
|
||||
info.mAttributeOldValue.Construct(mr->AttributeOldValue());
|
||||
info.mCharacterDataOldValue.Construct(mr->CharacterDataOldValue());
|
||||
info.mNativeAnonymousChildList = mr->NativeAnonymousChildList();
|
||||
info.mAnimations.Construct(mr->Animations());
|
||||
nsCOMArray<nsIAtom>& filters = mr->AttributeFilter();
|
||||
if (filters.Count()) {
|
||||
|
|
|
@ -172,6 +172,16 @@ public:
|
|||
mCharacterDataOldValue = aOldValue;
|
||||
}
|
||||
|
||||
bool NativeAnonymousChildList()
|
||||
{
|
||||
return mParent ? mParent->NativeAnonymousChildList() : mNativeAnonymousChildList;
|
||||
}
|
||||
void SetNativeAnonymousChildList(bool aOldValue)
|
||||
{
|
||||
NS_ASSERTION(!mParent, "Shouldn't have parent");
|
||||
mNativeAnonymousChildList = aOldValue;
|
||||
}
|
||||
|
||||
bool Attributes() { return mParent ? mParent->Attributes() : mAttributes; }
|
||||
void SetAttributes(bool aAttributes)
|
||||
{
|
||||
|
@ -298,6 +308,7 @@ private:
|
|||
bool mChildList;
|
||||
bool mCharacterData;
|
||||
bool mCharacterDataOldValue;
|
||||
bool mNativeAnonymousChildList;
|
||||
bool mAttributes;
|
||||
bool mAllAttributes;
|
||||
bool mAttributeOldValue;
|
||||
|
@ -362,6 +373,7 @@ public:
|
|||
NS_DECL_ISUPPORTS
|
||||
|
||||
NS_DECL_NSIMUTATIONOBSERVER_ATTRIBUTEWILLCHANGE
|
||||
NS_DECL_NSIMUTATIONOBSERVER_NATIVEANONYMOUSCHILDLISTCHANGE
|
||||
NS_DECL_NSIMUTATIONOBSERVER_CHARACTERDATAWILLCHANGE
|
||||
NS_DECL_NSIMUTATIONOBSERVER_CONTENTAPPENDED
|
||||
NS_DECL_NSIMUTATIONOBSERVER_CONTENTINSERTED
|
||||
|
|
|
@ -514,6 +514,8 @@ nsGenericDOMDataNode::BindToTree(nsIDocument* aDocument, nsIContent* aParent,
|
|||
}
|
||||
}
|
||||
|
||||
bool hadParent = !!GetParentNode();
|
||||
|
||||
// Set parent
|
||||
if (aParent) {
|
||||
if (!GetParent()) {
|
||||
|
@ -548,6 +550,9 @@ nsGenericDOMDataNode::BindToTree(nsIDocument* aDocument, nsIContent* aParent,
|
|||
}
|
||||
|
||||
nsNodeUtils::ParentChainChanged(this);
|
||||
if (!hadParent && IsRootOfNativeAnonymousSubtree()) {
|
||||
nsNodeUtils::NativeAnonymousChildListChange(this, false);
|
||||
}
|
||||
|
||||
UpdateEditableState(false);
|
||||
|
||||
|
@ -570,6 +575,9 @@ nsGenericDOMDataNode::UnbindFromTree(bool aDeep, bool aNullParent)
|
|||
HasFlag(NODE_FORCE_XBL_BINDINGS) ? OwnerDoc() : GetComposedDoc();
|
||||
|
||||
if (aNullParent) {
|
||||
if (this->IsRootOfNativeAnonymousSubtree()) {
|
||||
nsNodeUtils::NativeAnonymousChildListChange(this, true);
|
||||
}
|
||||
if (GetParent()) {
|
||||
NS_RELEASE(mParent);
|
||||
} else {
|
||||
|
|
|
@ -633,6 +633,7 @@ GK_ATOM(_namespace, "namespace")
|
|||
GK_ATOM(namespaceAlias, "namespace-alias")
|
||||
GK_ATOM(namespaceUri, "namespace-uri")
|
||||
GK_ATOM(NaN, "NaN")
|
||||
GK_ATOM(nativeAnonymousChildList, "nativeAnonymousChildList")
|
||||
GK_ATOM(nav, "nav")
|
||||
GK_ATOM(negate, "negate")
|
||||
GK_ATOM(never, "never")
|
||||
|
|
|
@ -22,8 +22,8 @@ class Element;
|
|||
} // namespace mozilla
|
||||
|
||||
#define NS_IMUTATION_OBSERVER_IID \
|
||||
{ 0xdd74f0cc, 0x2849, 0x4d05, \
|
||||
{ 0x9c, 0xe3, 0xb0, 0x95, 0x3e, 0xc2, 0xfd, 0x44 } }
|
||||
{ 0x6d674c17, 0x0fbc, 0x4633, \
|
||||
{ 0x8f, 0x46, 0x73, 0x4e, 0x87, 0xeb, 0xf0, 0xc7 } }
|
||||
|
||||
/**
|
||||
* Information details about a characterdata change. Basically, we
|
||||
|
@ -200,6 +200,18 @@ public:
|
|||
int32_t aModType,
|
||||
const nsAttrValue* aOldValue) = 0;
|
||||
|
||||
/**
|
||||
* Notification that the root of a native anonymous has been added
|
||||
* or removed.
|
||||
*
|
||||
* @param aDocument Owner doc of aContent
|
||||
* @param aContent Anonymous node that's been added or removed
|
||||
* @param aIsRemove True if it's a removal, false if an addition
|
||||
*/
|
||||
virtual void NativeAnonymousChildListChange(nsIDocument* aDocument,
|
||||
nsIContent* aContent,
|
||||
bool aIsRemove) {}
|
||||
|
||||
/**
|
||||
* Notification that an attribute of an element has been
|
||||
* set to the value it already had.
|
||||
|
@ -346,6 +358,11 @@ NS_DEFINE_STATIC_IID_ACCESSOR(nsIMutationObserver, NS_IMUTATION_OBSERVER_IID)
|
|||
int32_t aModType, \
|
||||
const nsAttrValue* aNewValue) override;
|
||||
|
||||
#define NS_DECL_NSIMUTATIONOBSERVER_NATIVEANONYMOUSCHILDLISTCHANGE \
|
||||
virtual void NativeAnonymousChildListChange(nsIDocument* aDocument, \
|
||||
nsIContent* aContent, \
|
||||
bool aIsRemove) override;
|
||||
|
||||
#define NS_DECL_NSIMUTATIONOBSERVER_ATTRIBUTECHANGED \
|
||||
virtual void AttributeChanged(nsIDocument* aDocument, \
|
||||
mozilla::dom::Element* aElement, \
|
||||
|
@ -383,6 +400,7 @@ NS_DEFINE_STATIC_IID_ACCESSOR(nsIMutationObserver, NS_IMUTATION_OBSERVER_IID)
|
|||
NS_DECL_NSIMUTATIONOBSERVER_CHARACTERDATAWILLCHANGE \
|
||||
NS_DECL_NSIMUTATIONOBSERVER_CHARACTERDATACHANGED \
|
||||
NS_DECL_NSIMUTATIONOBSERVER_ATTRIBUTEWILLCHANGE \
|
||||
NS_DECL_NSIMUTATIONOBSERVER_NATIVEANONYMOUSCHILDLISTCHANGE \
|
||||
NS_DECL_NSIMUTATIONOBSERVER_ATTRIBUTECHANGED \
|
||||
NS_DECL_NSIMUTATIONOBSERVER_CONTENTAPPENDED \
|
||||
NS_DECL_NSIMUTATIONOBSERVER_CONTENTINSERTED \
|
||||
|
@ -419,6 +437,12 @@ _class::AttributeWillChange(nsIDocument* aDocument, \
|
|||
{ \
|
||||
} \
|
||||
void \
|
||||
_class::NativeAnonymousChildListChange(nsIDocument* aDocument, \
|
||||
nsIContent* aContent, \
|
||||
bool aIsRemove) \
|
||||
{ \
|
||||
} \
|
||||
void \
|
||||
_class::AttributeChanged(nsIDocument* aDocument, \
|
||||
mozilla::dom::Element* aElement, \
|
||||
int32_t aNameSpaceID, \
|
||||
|
|
|
@ -167,6 +167,15 @@ nsNodeUtils::ContentAppended(nsIContent* aContainer,
|
|||
aNewIndexInContainer));
|
||||
}
|
||||
|
||||
void
|
||||
nsNodeUtils::NativeAnonymousChildListChange(nsIContent* aContent,
|
||||
bool aIsRemove)
|
||||
{
|
||||
nsIDocument* doc = aContent->OwnerDoc();
|
||||
IMPL_MUTATION_NOTIFICATION(NativeAnonymousChildListChange, aContent,
|
||||
(doc, aContent, aIsRemove));
|
||||
}
|
||||
|
||||
void
|
||||
nsNodeUtils::ContentInserted(nsINode* aContainer,
|
||||
nsIContent* aChild,
|
||||
|
|
|
@ -96,6 +96,15 @@ public:
|
|||
nsIContent* aFirstNewContent,
|
||||
int32_t aNewIndexInContainer);
|
||||
|
||||
/**
|
||||
* Send NativeAnonymousChildList notifications to nsIMutationObservers
|
||||
* @param aContent Anonymous node that's been added or removed
|
||||
* @param aIsRemove True if it's a removal, false if an addition
|
||||
* @see nsIMutationObserver::NativeAnonymousChildListChange
|
||||
*/
|
||||
static void NativeAnonymousChildListChange(nsIContent* aContent,
|
||||
bool aIsRemove);
|
||||
|
||||
/**
|
||||
* Send ContentInserted notifications to nsIMutationObservers
|
||||
* @param aContainer Node into which new child was inserted
|
||||
|
|
|
@ -65,6 +65,7 @@ skip-if = buildapp == 'mulet'
|
|||
[test_cpows.xul]
|
||||
skip-if = buildapp == 'mulet'
|
||||
[test_document_register.xul]
|
||||
[test_mutationobserver_anonymous.html]
|
||||
[test_registerElement_content.xul]
|
||||
[test_registerElement_ep.xul]
|
||||
[test_domparsing.xul]
|
||||
|
|
|
@ -0,0 +1,251 @@
|
|||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<!--
|
||||
https://bugzilla.mozilla.org/show_bug.cgi?id=1034110
|
||||
-->
|
||||
<head>
|
||||
<title>Test for Bug 1034110</title>
|
||||
<script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
|
||||
<script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/ChromeUtils.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"/>
|
||||
</head>
|
||||
<body>
|
||||
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1034110">Mozilla Bug 1034110</a>
|
||||
<style type="text/css">
|
||||
#pseudo.before::before { content: "before"; }
|
||||
#pseudo.after::after { content: "after"; }
|
||||
</style>
|
||||
<div id="pseudo"></div>
|
||||
<video id="video"></video>
|
||||
<p id="display"></p>
|
||||
<div id="content" style="display: none">
|
||||
|
||||
</div>
|
||||
|
||||
<pre id="test">
|
||||
<script type="application/javascript;version=1.7">
|
||||
|
||||
/** Test for Bug 1034110 **/
|
||||
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
const {Cc, Ci, Cu} = SpecialPowers;
|
||||
|
||||
function getWalker(node) {
|
||||
let walker = Cc["@mozilla.org/inspector/deep-tree-walker;1"].createInstance(Ci.inIDeepTreeWalker);
|
||||
walker.showAnonymousContent = true;
|
||||
walker.init(node.ownerDocument, Ci.nsIDOMNodeFilter.SHOW_ALL);
|
||||
walker.currentNode = node;
|
||||
return walker;
|
||||
}
|
||||
|
||||
function getFirstChild(parent) {
|
||||
return SpecialPowers.unwrap(getWalker(parent).firstChild());
|
||||
}
|
||||
|
||||
function getLastChild(parent) {
|
||||
return SpecialPowers.unwrap(getWalker(parent).lastChild());
|
||||
}
|
||||
|
||||
function assertSamePseudoElement(which, node1, node2) {
|
||||
is(node1.nodeName, "_moz_generated_content_" + which,
|
||||
"Correct pseudo element type");
|
||||
is(node1, node2,
|
||||
"Referencing the same ::after element");
|
||||
}
|
||||
|
||||
window.onload = function () {
|
||||
testOneAdded();
|
||||
};
|
||||
|
||||
function testOneAdded() {
|
||||
let parent = document.getElementById("pseudo");
|
||||
var m = new MutationObserver(function(records, observer) {
|
||||
is(records.length, 1, "Correct number of records");
|
||||
is(records[0].type, "nativeAnonymousChildList", "Correct record type");
|
||||
is(records[0].target, parent, "Correct target");
|
||||
|
||||
is(records[0].addedNodes.length, 1, "Should have got addedNodes");
|
||||
assertSamePseudoElement("before", records[0].addedNodes[0], getFirstChild(parent));
|
||||
is(records[0].removedNodes.length, 0, "Shouldn't have got removedNodes");
|
||||
|
||||
observer.disconnect();
|
||||
testAddedAndRemoved();
|
||||
});
|
||||
|
||||
m.observe(parent, { nativeAnonymousChildList: true});
|
||||
parent.className = "before";
|
||||
}
|
||||
|
||||
function testAddedAndRemoved() {
|
||||
let parent = document.getElementById("pseudo");
|
||||
let originalBeforeElement = getFirstChild(parent);
|
||||
var m = new MutationObserver(function(records, observer) {
|
||||
is(records.length, 2, "Correct number of records");
|
||||
is(records[0].type, "nativeAnonymousChildList", "Correct record type (1)");
|
||||
is(records[1].type, "nativeAnonymousChildList", "Correct record type (2)");
|
||||
is(records[0].target, parent, "Correct target (1)");
|
||||
is(records[1].target, parent, "Correct target (2)");
|
||||
|
||||
// Two records are sent - one for removed and one for added.
|
||||
is(records[0].addedNodes.length, 0, "Shouldn't have got addedNodes");
|
||||
is(records[0].removedNodes.length, 1, "Should have got removedNodes");
|
||||
assertSamePseudoElement("before", records[0].removedNodes[0], originalBeforeElement);
|
||||
|
||||
is(records[1].addedNodes.length, 1, "Should have got addedNodes");
|
||||
assertSamePseudoElement("after", records[1].addedNodes[0], getLastChild(parent));
|
||||
is(records[1].removedNodes.length, 0, "Shouldn't have got removedNodes");
|
||||
|
||||
observer.disconnect();
|
||||
testRemoved();
|
||||
});
|
||||
|
||||
m.observe(parent, { nativeAnonymousChildList: true});
|
||||
parent.className = "after";
|
||||
}
|
||||
|
||||
function testRemoved() {
|
||||
let parent = document.getElementById("pseudo");
|
||||
let originalAfterElement = getLastChild(parent);
|
||||
var m = new MutationObserver(function(records, observer) {
|
||||
is(records.length, 1, "Correct number of records");
|
||||
is(records[0].type, "nativeAnonymousChildList", "Correct record type");
|
||||
is(records[0].target, parent, "Correct target");
|
||||
|
||||
is(records[0].addedNodes.length, 0, "Shouldn't have got addedNodes");
|
||||
is(records[0].removedNodes.length, 1, "Should have got removedNodes");
|
||||
assertSamePseudoElement("after", records[0].removedNodes[0], originalAfterElement);
|
||||
|
||||
observer.disconnect();
|
||||
testMultipleAdded();
|
||||
});
|
||||
|
||||
m.observe(parent, { nativeAnonymousChildList: true });
|
||||
parent.className = "";
|
||||
}
|
||||
|
||||
function testMultipleAdded() {
|
||||
let parent = document.getElementById("pseudo");
|
||||
var m = new MutationObserver(function(records, observer) {
|
||||
is(records.length, 2, "Correct number of records");
|
||||
is(records[0].type, "nativeAnonymousChildList", "Correct record type (1)");
|
||||
is(records[1].type, "nativeAnonymousChildList", "Correct record type (2)");
|
||||
is(records[0].target, parent, "Correct target (1)");
|
||||
is(records[1].target, parent, "Correct target (2)");
|
||||
|
||||
is(records[0].addedNodes.length, 1, "Should have got addedNodes");
|
||||
assertSamePseudoElement("before", records[0].addedNodes[0], getFirstChild(parent));
|
||||
is(records[0].removedNodes.length, 0, "Shouldn't have got removedNodes");
|
||||
|
||||
is(records[1].addedNodes.length, 1, "Should have got addedNodes");
|
||||
assertSamePseudoElement("after", records[1].addedNodes[0], getLastChild(parent));
|
||||
is(records[1].removedNodes.length, 0, "Shouldn't have got removedNodes");
|
||||
|
||||
observer.disconnect();
|
||||
testRemovedDueToDisplay();
|
||||
});
|
||||
|
||||
m.observe(parent, { nativeAnonymousChildList: true });
|
||||
parent.className = "before after";
|
||||
}
|
||||
|
||||
function testRemovedDueToDisplay() {
|
||||
let parent = document.getElementById("pseudo");
|
||||
let originalBeforeElement = getFirstChild(parent);
|
||||
let originalAfterElement = getLastChild(parent);
|
||||
var m = new MutationObserver(function(records, observer) {
|
||||
is(records.length, 2, "Correct number of records");
|
||||
is(records[0].type, "nativeAnonymousChildList", "Correct record type (1)");
|
||||
is(records[1].type, "nativeAnonymousChildList", "Correct record type (2)");
|
||||
is(records[0].target, parent, "Correct target (1)");
|
||||
is(records[1].target, parent, "Correct target (2)");
|
||||
|
||||
is(records[0].addedNodes.length, 0, "Shouldn't have got addedNodes");
|
||||
is(records[0].removedNodes.length, 1, "Should have got removedNodes");
|
||||
assertSamePseudoElement("before", records[0].removedNodes[0], originalBeforeElement);
|
||||
|
||||
is(records[1].addedNodes.length, 0, "Shouldn't have got addedNodes");
|
||||
is(records[1].removedNodes.length, 1, "Should have got removedNodes");
|
||||
assertSamePseudoElement("after", records[1].removedNodes[0], originalAfterElement);
|
||||
|
||||
observer.disconnect();
|
||||
testAddedDueToDisplay();
|
||||
});
|
||||
|
||||
m.observe(parent, { nativeAnonymousChildList: true });
|
||||
parent.style.display = "none";
|
||||
}
|
||||
|
||||
function testAddedDueToDisplay() {
|
||||
let parent = document.getElementById("pseudo");
|
||||
var m = new MutationObserver(function(records, observer) {
|
||||
is(records.length, 2, "Correct number of records");
|
||||
is(records[0].type, "nativeAnonymousChildList", "Correct record type (1)");
|
||||
is(records[1].type, "nativeAnonymousChildList", "Correct record type (2)");
|
||||
is(records[0].target, parent, "Correct target (1)");
|
||||
is(records[1].target, parent, "Correct target (2)");
|
||||
|
||||
is(records[0].addedNodes.length, 1, "Should have got addedNodes");
|
||||
assertSamePseudoElement("before", records[0].addedNodes[0], getFirstChild(parent));
|
||||
is(records[0].removedNodes.length, 0, "Shouldn't have got removedNodes");
|
||||
|
||||
is(records[1].addedNodes.length, 1, "Should have got addedNodes");
|
||||
assertSamePseudoElement("after", records[1].addedNodes[0], getLastChild(parent));
|
||||
is(records[1].removedNodes.length, 0, "Shouldn't have got removedNodes");
|
||||
|
||||
observer.disconnect();
|
||||
testDifferentTargetNoSubtree();
|
||||
});
|
||||
|
||||
m.observe(parent, { nativeAnonymousChildList: true });
|
||||
parent.style.display = "block";
|
||||
}
|
||||
|
||||
function testDifferentTargetNoSubtree() {
|
||||
let parent = document.getElementById("pseudo");
|
||||
var m = new MutationObserver(function(records, observer) {
|
||||
ok(false,
|
||||
"No mutation should fire when observing on a parent without subtree option.");
|
||||
});
|
||||
m.observe(document, { nativeAnonymousChildList: true });
|
||||
parent.style.display = "none";
|
||||
|
||||
// Wait for the actual mutation to come through, making sure that
|
||||
// the original observer never fires.
|
||||
var m2 = new MutationObserver(function(records, observer) {
|
||||
ok(!getFirstChild(parent), "Pseudo element has been removed, but no mutation");
|
||||
ok(!getLastChild(parent), "Pseudo element has been removed, but no mutation");
|
||||
observer.disconnect();
|
||||
testSubtree();
|
||||
});
|
||||
m2.observe(parent, { nativeAnonymousChildList: true });
|
||||
}
|
||||
|
||||
function testSubtree() {
|
||||
let parent = document.getElementById("pseudo");
|
||||
var m = new MutationObserver(function(records, observer) {
|
||||
is(records.length, 2, "Correct number of records");
|
||||
is(records[0].type, "nativeAnonymousChildList", "Correct record type (1)");
|
||||
is(records[1].type, "nativeAnonymousChildList", "Correct record type (2)");
|
||||
is(records[0].target, parent, "Correct target (1)");
|
||||
is(records[1].target, parent, "Correct target (2)");
|
||||
|
||||
is(records[0].addedNodes.length, 1, "Should have got addedNodes");
|
||||
assertSamePseudoElement("before", records[0].addedNodes[0], getFirstChild(parent));
|
||||
is(records[0].removedNodes.length, 0, "Shouldn't have got removedNodes");
|
||||
|
||||
is(records[1].addedNodes.length, 1, "Should have got addedNodes");
|
||||
assertSamePseudoElement("after", records[1].addedNodes[0], getLastChild(parent));
|
||||
is(records[1].removedNodes.length, 0, "Shouldn't have got removedNodes");
|
||||
|
||||
observer.disconnect();
|
||||
SimpleTest.finish();
|
||||
});
|
||||
m.observe(document, { nativeAnonymousChildList: true, subtree: true });
|
||||
parent.style.display = "block";
|
||||
}
|
||||
|
||||
</script>
|
||||
</pre>
|
||||
</body>
|
||||
</html>
|
|
@ -909,6 +909,19 @@ function testAttributeRecordMerging4() {
|
|||
m.disconnect();
|
||||
div.innerHTML = "";
|
||||
div.removeAttribute("foo");
|
||||
then(testChromeOnly);
|
||||
}
|
||||
|
||||
function testChromeOnly() {
|
||||
// Content can't access nativeAnonymousChildList
|
||||
try {
|
||||
var mo = new M(function(records, observer) { });
|
||||
mo.observe(div, { nativeAnonymousChildList: true });
|
||||
ok(false, "Should have thrown when trying to observe with chrome-only init");
|
||||
} catch (e) {
|
||||
ok(true, "Throws when trying to observe with chrome-only init");
|
||||
}
|
||||
|
||||
then();
|
||||
}
|
||||
|
||||
|
|
|
@ -409,7 +409,7 @@ public:
|
|||
NS_IMETHOD GetItemId(nsAString& aId) final override {
|
||||
nsString id;
|
||||
GetItemId(id);
|
||||
aId.Assign(aId);
|
||||
aId.Assign(id);
|
||||
return NS_OK;
|
||||
}
|
||||
NS_IMETHOD SetItemId(const nsAString& aId) final override {
|
||||
|
|
|
@ -169,6 +169,10 @@ ScreenManagerParent::RecvScreenForBrowser(const TabId& aTabId,
|
|||
bool
|
||||
ScreenManagerParent::ExtractScreenDetails(nsIScreen* aScreen, ScreenDetails &aDetails)
|
||||
{
|
||||
if (!aScreen) {
|
||||
return false;
|
||||
}
|
||||
|
||||
uint32_t id;
|
||||
nsresult rv = aScreen->GetId(&id);
|
||||
NS_ENSURE_SUCCESS(rv, false);
|
||||
|
|
|
@ -9,11 +9,11 @@
|
|||
#include <stdint.h>
|
||||
|
||||
#include "MP4Demuxer.h"
|
||||
#include "mp4_demuxer/Index.h"
|
||||
#include "mp4_demuxer/MoofParser.h"
|
||||
#include "mp4_demuxer/MP4Metadata.h"
|
||||
#include "mp4_demuxer/ResourceStream.h"
|
||||
#include "mp4_demuxer/BufferStream.h"
|
||||
#include "mp4_demuxer/Index.h"
|
||||
|
||||
// Used for telemetry
|
||||
#include "mozilla/Telemetry.h"
|
||||
|
@ -30,6 +30,51 @@ PRLogModuleInfo* GetDemuxerLog() {
|
|||
|
||||
namespace mozilla {
|
||||
|
||||
class MP4TrackDemuxer : public MediaTrackDemuxer
|
||||
{
|
||||
public:
|
||||
MP4TrackDemuxer(MP4Demuxer* aParent,
|
||||
UniquePtr<TrackInfo>&& aInfo,
|
||||
const nsTArray<mp4_demuxer::Index::Indice>& indices);
|
||||
|
||||
virtual UniquePtr<TrackInfo> GetInfo() const override;
|
||||
|
||||
virtual nsRefPtr<SeekPromise> Seek(media::TimeUnit aTime) override;
|
||||
|
||||
virtual nsRefPtr<SamplesPromise> GetSamples(int32_t aNumSamples = 1) override;
|
||||
|
||||
virtual void Reset() override;
|
||||
|
||||
virtual nsresult GetNextRandomAccessPoint(media::TimeUnit* aTime) override;
|
||||
|
||||
nsRefPtr<SkipAccessPointPromise> SkipToNextRandomAccessPoint(media::TimeUnit aTimeThreshold) override;
|
||||
|
||||
virtual media::TimeIntervals GetBuffered() override;
|
||||
|
||||
virtual void BreakCycles() override;
|
||||
|
||||
private:
|
||||
friend class MP4Demuxer;
|
||||
void NotifyDataArrived();
|
||||
void UpdateSamples(nsTArray<nsRefPtr<MediaRawData>>& aSamples);
|
||||
void EnsureUpToDateIndex();
|
||||
void SetNextKeyFrameTime();
|
||||
nsRefPtr<MP4Demuxer> mParent;
|
||||
nsRefPtr<mp4_demuxer::ResourceStream> mStream;
|
||||
UniquePtr<TrackInfo> mInfo;
|
||||
// We do not actually need a monitor, however MoofParser (in mIndex) will
|
||||
// assert if a monitor isn't held.
|
||||
Monitor mMonitor;
|
||||
nsRefPtr<mp4_demuxer::Index> mIndex;
|
||||
UniquePtr<mp4_demuxer::SampleIterator> mIterator;
|
||||
Maybe<media::TimeUnit> mNextKeyframeTime;
|
||||
// Queued samples extracted by the demuxer, but not yet returned.
|
||||
nsRefPtr<MediaRawData> mQueuedSample;
|
||||
bool mNeedReIndex;
|
||||
bool mNeedSPSForTelemetry;
|
||||
};
|
||||
|
||||
|
||||
// Returns true if no SPS was found and search for it should continue.
|
||||
bool
|
||||
AccumulateSPSTelemetry(const MediaByteBuffer* aExtradata)
|
||||
|
@ -120,8 +165,15 @@ MP4Demuxer::GetTrackDemuxer(TrackInfo::TrackType aType, uint32_t aTrackNumber)
|
|||
if (mMetadata->GetNumberTracks(aType) <= aTrackNumber) {
|
||||
return nullptr;
|
||||
}
|
||||
nsRefPtr<MP4TrackDemuxer> e =
|
||||
new MP4TrackDemuxer(this, aType, aTrackNumber);
|
||||
UniquePtr<TrackInfo> info = mMetadata->GetTrackInfo(aType, aTrackNumber);
|
||||
if (!info) {
|
||||
return nullptr;
|
||||
}
|
||||
FallibleTArray<mp4_demuxer::Index::Indice> indices;
|
||||
if (!mMetadata->ReadTrackIndex(indices, info->mTrackId)) {
|
||||
return nullptr;
|
||||
}
|
||||
nsRefPtr<MP4TrackDemuxer> e = new MP4TrackDemuxer(this, Move(info), indices);
|
||||
mDemuxers.AppendElement(e);
|
||||
|
||||
return e.forget();
|
||||
|
@ -174,27 +226,20 @@ MP4Demuxer::GetCrypto()
|
|||
}
|
||||
|
||||
MP4TrackDemuxer::MP4TrackDemuxer(MP4Demuxer* aParent,
|
||||
TrackInfo::TrackType aType,
|
||||
uint32_t aTrackNumber)
|
||||
UniquePtr<TrackInfo>&& aInfo,
|
||||
const nsTArray<mp4_demuxer::Index::Indice>& indices)
|
||||
: mParent(aParent)
|
||||
, mStream(new mp4_demuxer::ResourceStream(mParent->mResource))
|
||||
, mNeedReIndex(true)
|
||||
, mInfo(Move(aInfo))
|
||||
, mMonitor("MP4TrackDemuxer")
|
||||
{
|
||||
mInfo = mParent->mMetadata->GetTrackInfo(aType, aTrackNumber);
|
||||
|
||||
MOZ_ASSERT(mInfo);
|
||||
|
||||
FallibleTArray<mp4_demuxer::Index::Indice> indices;
|
||||
if (!mParent->mMetadata->ReadTrackIndex(indices, mInfo->mTrackId)) {
|
||||
MOZ_ASSERT(false);
|
||||
}
|
||||
mIndex = new mp4_demuxer::Index(indices,
|
||||
, mIndex(new mp4_demuxer::Index(indices,
|
||||
mStream,
|
||||
mInfo->mTrackId,
|
||||
mInfo->IsAudio(),
|
||||
&mMonitor);
|
||||
mIterator = MakeUnique<mp4_demuxer::SampleIterator>(mIndex);
|
||||
&mMonitor))
|
||||
, mIterator(MakeUnique<mp4_demuxer::SampleIterator>(mIndex))
|
||||
, mNeedReIndex(true)
|
||||
{
|
||||
EnsureUpToDateIndex(); // Force update of index
|
||||
|
||||
// Collect telemetry from h264 AVCC SPS.
|
||||
|
|
|
@ -13,7 +13,6 @@
|
|||
#include "MediaResource.h"
|
||||
|
||||
namespace mp4_demuxer {
|
||||
class Index;
|
||||
class MP4Metadata;
|
||||
class ResourceStream;
|
||||
class SampleIterator;
|
||||
|
@ -54,51 +53,6 @@ private:
|
|||
nsTArray<nsRefPtr<MP4TrackDemuxer>> mDemuxers;
|
||||
};
|
||||
|
||||
class MP4TrackDemuxer : public MediaTrackDemuxer
|
||||
{
|
||||
public:
|
||||
MP4TrackDemuxer(MP4Demuxer* aParent,
|
||||
TrackInfo::TrackType aType,
|
||||
uint32_t aTrackNumber);
|
||||
|
||||
virtual UniquePtr<TrackInfo> GetInfo() const override;
|
||||
|
||||
virtual nsRefPtr<SeekPromise> Seek(media::TimeUnit aTime) override;
|
||||
|
||||
virtual nsRefPtr<SamplesPromise> GetSamples(int32_t aNumSamples = 1) override;
|
||||
|
||||
virtual void Reset() override;
|
||||
|
||||
virtual nsresult GetNextRandomAccessPoint(media::TimeUnit* aTime) override;
|
||||
|
||||
nsRefPtr<SkipAccessPointPromise> SkipToNextRandomAccessPoint(media::TimeUnit aTimeThreshold) override;
|
||||
|
||||
virtual media::TimeIntervals GetBuffered() override;
|
||||
|
||||
virtual void BreakCycles() override;
|
||||
|
||||
private:
|
||||
friend class MP4Demuxer;
|
||||
void NotifyDataArrived();
|
||||
void UpdateSamples(nsTArray<nsRefPtr<MediaRawData>>& aSamples);
|
||||
void EnsureUpToDateIndex();
|
||||
void SetNextKeyFrameTime();
|
||||
nsRefPtr<MP4Demuxer> mParent;
|
||||
nsRefPtr<mp4_demuxer::Index> mIndex;
|
||||
UniquePtr<mp4_demuxer::SampleIterator> mIterator;
|
||||
UniquePtr<TrackInfo> mInfo;
|
||||
nsRefPtr<mp4_demuxer::ResourceStream> mStream;
|
||||
Maybe<media::TimeUnit> mNextKeyframeTime;
|
||||
// Queued samples extracted by the demuxer, but not yet returned.
|
||||
nsRefPtr<MediaRawData> mQueuedSample;
|
||||
bool mNeedReIndex;
|
||||
bool mNeedSPSForTelemetry;
|
||||
|
||||
// We do not actually need a monitor, however MoofParser will assert
|
||||
// if a monitor isn't held.
|
||||
Monitor mMonitor;
|
||||
};
|
||||
|
||||
} // namespace mozilla
|
||||
|
||||
#endif
|
||||
|
|
|
@ -172,10 +172,6 @@ skip-if = (toolkit == 'gonk' && !debug) || android_version == '10' || android_ve
|
|||
[test_stereoPanningWithGain.html]
|
||||
[test_waveDecoder.html]
|
||||
[test_waveShaper.html]
|
||||
skip-if = os == 'win' && debug #Bug 1202564
|
||||
[test_waveShaperNoCurve.html]
|
||||
skip-if = os == 'win' && debug #Bug 1202565
|
||||
[test_waveShaperPassThrough.html]
|
||||
skip-if = os == 'win' && debug #Bug 1196084
|
||||
[test_waveShaperInvalidLengthCurve.html]
|
||||
skip-if = os == 'win' && debug #Bug 1202567
|
||||
|
|
|
@ -971,7 +971,7 @@ NPP_New(NPMIMEType pluginType, NPP instance, uint16_t mode, int16_t argc, char*
|
|||
NPError err = pluginInstanceInit(instanceData);
|
||||
if (err != NPERR_NO_ERROR) {
|
||||
NPN_ReleaseObject(scriptableObject);
|
||||
free(instanceData);
|
||||
delete instanceData;
|
||||
return err;
|
||||
}
|
||||
|
||||
|
|
|
@ -105,7 +105,7 @@ PresentationResponderLoadingCallback::Init(nsIDocShell* aDocShell)
|
|||
return rv;
|
||||
}
|
||||
|
||||
if ((busyFlags & nsIDocShell::BUSY_FLAGS_NONE) ||
|
||||
if ((busyFlags == nsIDocShell::BUSY_FLAGS_NONE) ||
|
||||
(busyFlags & nsIDocShell::BUSY_FLAGS_PAGE_LOADING)) {
|
||||
// The docshell has finished loading or is receiving data (|STATE_TRANSFERRING|
|
||||
// has already been fired), so the page is ready for presentation use.
|
||||
|
|
|
@ -301,15 +301,15 @@ private:
|
|||
// List of scopes having data, for optimization purposes only
|
||||
nsTHashtable<nsCStringHashKey> mScopesHavingData;
|
||||
|
||||
StatementCache mWorkerStatements;
|
||||
StatementCache mReaderStatements;
|
||||
|
||||
// Connection used by the worker thread for all read and write ops
|
||||
nsCOMPtr<mozIStorageConnection> mWorkerConnection;
|
||||
|
||||
// Connection used only on the main thread for sync read operations
|
||||
nsCOMPtr<mozIStorageConnection> mReaderConnection;
|
||||
|
||||
StatementCache mWorkerStatements;
|
||||
StatementCache mReaderStatements;
|
||||
|
||||
// Time the first pending operation has been added to the pending operations
|
||||
// list
|
||||
PRIntervalTime mDirtyEpoch;
|
||||
|
|
|
@ -61,6 +61,8 @@ dictionary MutationObserverInit {
|
|||
boolean attributeOldValue;
|
||||
boolean characterDataOldValue;
|
||||
// [ChromeOnly]
|
||||
boolean nativeAnonymousChildList = false;
|
||||
// [ChromeOnly]
|
||||
boolean animations;
|
||||
sequence<DOMString> attributeFilter;
|
||||
};
|
||||
|
|
|
@ -2873,7 +2873,7 @@ nsHTMLEditRules::MoveBlock(nsIDOMNode *aLeftBlock, nsIDOMNode *aRightBlock, int3
|
|||
// GetNodesFromPoint is the workhorse that figures out what we wnat to move.
|
||||
nsresult res = GetNodesFromPoint(::DOMPoint(aRightBlock,aRightOffset),
|
||||
EditAction::makeList, arrayOfNodes,
|
||||
TouchContent::no);
|
||||
TouchContent::yes);
|
||||
NS_ENSURE_SUCCESS(res, res);
|
||||
for (auto& curNode : arrayOfNodes) {
|
||||
// get the node to act on
|
||||
|
@ -5614,6 +5614,27 @@ nsHTMLEditRules::GetPromotedPoint(RulesEndpoint aWhere, nsIDOMNode* aNode,
|
|||
if (mHTMLEditor->IsVisBreak(nextNode->AsDOMNode())) {
|
||||
break;
|
||||
}
|
||||
|
||||
// Check for newlines in pre-formatted text nodes.
|
||||
bool isPRE;
|
||||
mHTMLEditor->IsPreformatted(nextNode->AsDOMNode(), &isPRE);
|
||||
if (isPRE) {
|
||||
nsCOMPtr<nsIDOMText> textNode = do_QueryInterface(nextNode);
|
||||
if (textNode) {
|
||||
nsAutoString tempString;
|
||||
textNode->GetData(tempString);
|
||||
int32_t newlinePos = tempString.FindChar(nsCRT::LF);
|
||||
if (newlinePos >= 0) {
|
||||
if ((uint32_t)newlinePos + 1 == tempString.Length()) {
|
||||
// No need for special processing if the newline is at the end.
|
||||
break;
|
||||
}
|
||||
*outNode = nextNode->AsDOMNode();
|
||||
*outOffset = newlinePos + 1;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
NS_ENSURE_TRUE(mHTMLEditor, /* void */);
|
||||
nextNode = mHTMLEditor->GetNextHTMLNode(node, offset, true);
|
||||
}
|
||||
|
@ -5781,6 +5802,38 @@ nsHTMLEditRules::GetNodesForOperation(nsTArray<nsRefPtr<nsRange>>& aArrayOfRange
|
|||
int32_t rangeCount = aArrayOfRanges.Length();
|
||||
nsresult res = NS_OK;
|
||||
|
||||
if (aTouchContent == TouchContent::yes) {
|
||||
// Split text nodes. This is necessary, since GetPromotedPoint() may return a
|
||||
// range ending in a text node in case where part of a pre-formatted
|
||||
// elements needs to be moved.
|
||||
for (int32_t i = 0; i < rangeCount; i++) {
|
||||
nsRefPtr<nsRange> r = aArrayOfRanges[i];
|
||||
nsCOMPtr<nsIContent> endParent = do_QueryInterface(r->GetEndParent());
|
||||
if (!mHTMLEditor->IsTextNode(endParent)) {
|
||||
continue;
|
||||
}
|
||||
nsCOMPtr<nsIDOMText> textNode = do_QueryInterface(endParent);
|
||||
if (textNode) {
|
||||
int32_t offset = r->EndOffset();
|
||||
nsAutoString tempString;
|
||||
textNode->GetData(tempString);
|
||||
|
||||
if (0 < offset && offset < (int32_t)(tempString.Length())) {
|
||||
// Split the text node.
|
||||
nsCOMPtr<nsIDOMNode> tempNode;
|
||||
res = mHTMLEditor->SplitNode(endParent->AsDOMNode(), offset,
|
||||
getter_AddRefs(tempNode));
|
||||
NS_ENSURE_SUCCESS(res, res);
|
||||
|
||||
// Correct the range.
|
||||
// The new end parent becomes the parent node of the text.
|
||||
nsCOMPtr<nsIContent> newParent = endParent->GetParent();
|
||||
r->SetEnd(newParent, newParent->IndexOf(endParent));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Bust up any inlines that cross our range endpoints, but only if we are
|
||||
// allowed to touch content.
|
||||
|
||||
|
|
|
@ -130,6 +130,7 @@ skip-if = toolkit == 'android' || e10s
|
|||
[test_bug757371.html]
|
||||
[test_bug757771.html]
|
||||
[test_bug767684.html]
|
||||
[test_bug772796.html]
|
||||
[test_bug773262.html]
|
||||
[test_bug780035.html]
|
||||
[test_bug787432.html]
|
||||
|
|
|
@ -0,0 +1,223 @@
|
|||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<!--
|
||||
https://bugzilla.mozilla.org/show_bug.cgi?id=772796
|
||||
-->
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Test for Bug 772796</title>
|
||||
<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
|
||||
<style> .pre { white-space: pre } </style>
|
||||
</head>
|
||||
<body>
|
||||
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=772796">Mozilla Bug 772796</a>
|
||||
<p id="display"></p>
|
||||
<div id="content" style="display: none">
|
||||
</div>
|
||||
|
||||
<div id="editable" contenteditable></div>
|
||||
|
||||
<pre id="test">
|
||||
|
||||
<script type="application/javascript">
|
||||
var tests = [
|
||||
/*00*/[ "<div>test</div><pre>foobar\nbaz</pre>", "<div>testfoobar\n</div><pre>baz</pre>" ],
|
||||
/*01*/[ "<div>test</div><pre><b>foobar\nbaz</b></pre>", "<div>test<b>foobar\n</b></div><pre><b>baz</b></pre>" ],
|
||||
/*02*/[ "<div>test</div><pre><b>foo</b>bar\nbaz</pre>", "<div>test<b>foo</b>bar\n</div><pre>baz</pre>" ],
|
||||
/*03*/[ "<div>test</div><pre><b>foo</b>\nbar</pre>", "<div>test<b>foo</b>\n</div><pre>bar</pre>" ],
|
||||
/*04*/[ "<div>test</div><pre><b>foo\n</b>bar\nbaz</pre>", "<div>test<b>foo\n</b></div><pre>bar\nbaz</pre>" ],
|
||||
/* The <br> after the foobar is unfortunate but is behaviour that hasn't changed in bug 772796. */
|
||||
/*05*/[ "<div>test</div><pre>foobar<br>baz</pre>", "<div>testfoobar<br></div><pre>baz</pre>" ],
|
||||
/*06*/[ "<div>test</div><pre><b>foobar<br>baz</b></pre>", "<div>test<b>foobar</b><br></div><pre><b>baz</b></pre>" ],
|
||||
|
||||
/*
|
||||
* Some tests with block elements.
|
||||
* Tests 07, 09 and 11 don't use "MoveBlock", they use "JoinNodesSmart".
|
||||
* Test 11 is a pain: <div>foo\bar</div> is be joined to "test", losing the visible line break.
|
||||
*/
|
||||
/*07*/[ "<div>test</div><pre><div>foobar</div>baz</pre>", "<div>testfoobar</div><pre>baz</pre>" ],
|
||||
/*08*/[ "<div>test</div><pre>foobar<div>baz</div></pre>", "<div>testfoobar</div><pre><div>baz</div></pre>" ],
|
||||
/*09*/[ "<div>test</div><pre><div>foobar</div>baz\nfred</pre>", "<div>testfoobar</div><pre>baz\nfred</pre>" ],
|
||||
/*10*/[ "<div>test</div><pre>foobar<div>baz</div>\nfred</pre>", "<div>testfoobar</div><pre><div>baz</div>\nfred</pre>" ],
|
||||
/*11*/[ "<div>test</div><pre><div>foo\nbar</div>baz\nfred</pre>", "<div>testfoo\nbar</div><pre>baz\nfred</pre>" ], // BAD
|
||||
/*12*/[ "<div>test</div><pre>foo<div>bar</div>baz\nfred</pre>", "<div>testfoo</div><pre><div>bar</div>baz\nfred</pre>" ],
|
||||
|
||||
/*
|
||||
* Repeating all tests above with the <pre> on a new line.
|
||||
* We know that backspace doesn't work (bug 1190161). Third argument shows the current outcome.
|
||||
*/
|
||||
/*13-00*/[ "<div>test</div>\n<pre>foobar\nbaz</pre>", "<div>testfoobar\n</div><pre>baz</pre>",
|
||||
"<div>test</div>foobar\n<pre>baz</pre>" ],
|
||||
/*14-01*/[ "<div>test</div>\n<pre><b>foobar\nbaz</b></pre>", "<div>test<b>foobar\n</b></div><pre><b>baz</b></pre>",
|
||||
"<div>test</div><b>foobar\n</b><pre><b>baz</b></pre>" ],
|
||||
/*15-02*/[ "<div>test</div>\n<pre><b>foo</b>bar\nbaz</pre>", "<div>test<b>foo</b>bar\n</div><pre>baz</pre>",
|
||||
"<div>test</div><b>foo</b>bar\n<pre>baz</pre>" ],
|
||||
/*16-03*/[ "<div>test</div>\n<pre><b>foo</b>\nbar</pre>", "<div>test<b>foo</b>\n</div><pre>bar</pre>",
|
||||
"<div>test</div><b>foo</b>\n<pre>bar</pre>" ],
|
||||
/*17-04*/[ "<div>test</div>\n<pre><b>foo\n</b>bar\nbaz</pre>", "<div>test<b>foo\n</b></div><pre>bar\nbaz</pre>",
|
||||
"<div>test</div><b>foo\n</b><pre>bar\nbaz</pre>" ],
|
||||
/*18-05*/[ "<div>test</div>\n<pre>foobar<br>baz</pre>", "<div>testfoobar<br></div><pre>baz</pre>",
|
||||
"<div>test</div>foobar<br><pre>baz</pre>" ],
|
||||
/*19-06*/[ "<div>test</div>\n<pre><b>foobar<br>baz</b></pre>", "<div>test<b>foobar</b><br></div><pre><b>baz</b></pre>",
|
||||
"<div>test</div><b>foobar</b><br><pre><b>baz</b></pre>" ],
|
||||
/*20-07*/[ "<div>test</div>\n<pre><div>foobar</div>baz</pre>", "<div>testfoobar</div><pre>baz</pre>",
|
||||
"<div>test</div>foobar<pre>baz</pre>" ],
|
||||
/*21-08*/[ "<div>test</div>\n<pre>foobar<div>baz</div></pre>", "<div>testfoobar</div><pre><div>baz</div></pre>",
|
||||
"<div>test</div>foobar<pre><div>baz</div></pre>" ],
|
||||
/*22-09*/[ "<div>test</div>\n<pre><div>foobar</div>baz\nfred</pre>", "<div>testfoobar</div><pre>baz\nfred</pre>",
|
||||
"<div>test</div>foobar<pre>baz\nfred</pre>" ],
|
||||
/*23-10*/[ "<div>test</div>\n<pre>foobar<div>baz</div>\nfred</pre>", "<div>testfoobar</div><pre><div>baz</div>\nfred</pre>",
|
||||
"<div>test</div>foobar<pre><div>baz</div>\nfred</pre>" ],
|
||||
/*24-11*/[ "<div>test</div>\n<pre><div>foo\nbar</div>baz\nfred</pre>", "<div>testfoo\nbar</div><pre>baz\nfred</pre>", // BAD
|
||||
"<div>test</div>foo\n<pre><div>bar</div>baz\nfred</pre>" ],
|
||||
/*25-12*/[ "<div>test</div>\n<pre>foo<div>bar</div>baz\nfred</pre>", "<div>testfoo</div><pre><div>bar</div>baz\nfred</pre>",
|
||||
"<div>test</div>foo<pre><div>bar</div>baz\nfred</pre>" ],
|
||||
|
||||
/* Some tests without <div>. These exercise the MoveBlock "right in left" */
|
||||
/*26-00*/[ "test<pre>foobar\nbaz</pre>", "testfoobar\n<pre>baz</pre>" ],
|
||||
/*27-01*/[ "test<pre><b>foobar\nbaz</b></pre>", "test<b>foobar\n</b><pre><b>baz</b></pre>" ],
|
||||
/*28-02*/[ "test<pre><b>foo</b>bar\nbaz</pre>", "test<b>foo</b>bar\n<pre>baz</pre>" ],
|
||||
/*29-03*/[ "test<pre><b>foo</b>\nbar</pre>", "test<b>foo</b>\n<pre>bar</pre>" ],
|
||||
/*30-04*/[ "test<pre><b>foo\n</b>bar\nbaz</pre>", "test<b>foo\n</b><pre>bar\nbaz</pre>" ],
|
||||
/*31-05*/[ "test<pre>foobar<br>baz</pre>", "testfoobar<br><pre>baz</pre>" ],
|
||||
/*32-06*/[ "test<pre><b>foobar<br>baz</b></pre>", "test<b>foobar</b><br><pre><b>baz</b></pre>" ],
|
||||
/*33-07*/[ "test<pre><div>foobar</div>baz</pre>", "testfoobar<pre>baz</pre>" ],
|
||||
/*34-08*/[ "test<pre>foobar<div>baz</div></pre>", "testfoobar<pre><div>baz</div></pre>" ],
|
||||
/*35-09*/[ "test<pre><div>foobar</div>baz\nfred</pre>", "testfoobar<pre>baz\nfred</pre>" ],
|
||||
/*36-10*/[ "test<pre>foobar<div>baz</div>\nfred</pre>", "testfoobar<pre><div>baz</div>\nfred</pre>" ],
|
||||
/*37-11*/[ "test<pre><div>foo\nbar</div>baz\nfred</pre>", "testfoo\n<pre><div>bar</div>baz\nfred</pre>" ],
|
||||
/*38-12*/[ "test<pre>foo<div>bar</div>baz\nfred</pre>", "testfoo<pre><div>bar</div>baz\nfred</pre>" ],
|
||||
|
||||
/*
|
||||
* Some tests with <span class="pre">. Again 07, 09 and 11 use "JoinNodesSmart".
|
||||
* All these exercise MoveBlock "left in right". The "right" is the surrounding "contenteditable" div.
|
||||
*/
|
||||
/*39-00*/[ "<div>test</div><span class=\"pre\">foobar\nbaz</span>", "<div>test<span class=\"pre\">foobar\n</span></div><span class=\"pre\">baz</span>" ],
|
||||
/*40-01*/[ "<div>test</div><span class=\"pre\"><b>foobar\nbaz</b></span>", "<div>test<span class=\"pre\"><b>foobar\n</b></span></div><span class=\"pre\"><b>baz</b></span>" ],
|
||||
/*41-02*/[ "<div>test</div><span class=\"pre\"><b>foo</b>bar\nbaz</span>", "<div>test<span class=\"pre\"><b>foo</b>bar\n</span></div><span class=\"pre\">baz</span>" ],
|
||||
/*42-03*/[ "<div>test</div><span class=\"pre\"><b>foo</b>\nbar</span>", "<div>test<span class=\"pre\"><b>foo</b>\n</span></div><span class=\"pre\">bar</span>" ],
|
||||
/*43-04*/[ "<div>test</div><span class=\"pre\"><b>foo\n</b>bar\nbaz</span>", "<div>test<span class=\"pre\"><b>foo\n</b></span></div><span class=\"pre\">bar\nbaz</span>" ],
|
||||
/*44-05*/[ "<div>test</div><span class=\"pre\">foobar<br>baz</span>", "<div>test<span class=\"pre\">foobar</span><br><span class=\"pre\"></span></div><span class=\"pre\">baz</span>" ],
|
||||
/*45-06*/[ "<div>test</div><span class=\"pre\"><b>foobar<br>baz</b></span>", "<div>test<span class=\"pre\"><b>foobar</b></span><br><span class=\"pre\"></span></div><span class=\"pre\"><b>baz</b></span>" ],
|
||||
/*46-07*/[ "<div>test</div><span class=\"pre\"><div>foobar</div>baz</span>", "<div>testfoobar</div><span class=\"pre\">baz</span>" ],
|
||||
/*47-08*/[ "<div>test</div><span class=\"pre\">foobar<div>baz</div></span>", "<div>test<span class=\"pre\">foobar</span></div><span class=\"pre\"><div>baz</div></span>" ],
|
||||
/*48-09*/[ "<div>test</div><span class=\"pre\"><div>foobar</div>baz\nfred</span>", "<div>testfoobar</div><span class=\"pre\">baz\nfred</span>" ],
|
||||
/*49-10*/[ "<div>test</div><span class=\"pre\">foobar<div>baz</div>\nfred</span>", "<div>test<span class=\"pre\">foobar</span></div><span class=\"pre\"><div>baz</div>\nfred</span>" ],
|
||||
/*50-11*/[ "<div>test</div><span class=\"pre\"><div>foo\nbar</div>baz\nfred</span>", "<div>testfoo\nbar</div><span class=\"pre\">baz\nfred</span>" ], // BAD
|
||||
/*51-12*/[ "<div>test</div><span class=\"pre\">foo<div>bar</div>baz\nfred</span>", "<div>test<span class=\"pre\">foo</span></div><span class=\"pre\"><div>bar</div>baz\nfred</span>" ],
|
||||
|
||||
/* Some tests with <div class="pre">. */
|
||||
/*
|
||||
* The results are pretty ugly, since joining two <divs> sadly carrys the properties of the right to the left,
|
||||
* but not in all cases: 07, 09, 11 are actually right. All cases use "JoinNodesSmart".
|
||||
* Here we merely document the ugly behaviour. See bug 1191875 for more information.
|
||||
*/
|
||||
/*52-00*/[ "<div>test</div><div class=\"pre\">foobar\nbaz</div>", "<div class=\"pre\">testfoobar\nbaz</div>" ],
|
||||
/*53-01*/[ "<div>test</div><div class=\"pre\"><b>foobar\nbaz</b></div>", "<div class=\"pre\">test<b>foobar\nbaz</b></div>" ],
|
||||
/*54-02*/[ "<div>test</div><div class=\"pre\"><b>foo</b>bar\nbaz</div>", "<div class=\"pre\">test<b>foo</b>bar\nbaz</div>" ],
|
||||
/*55-03*/[ "<div>test</div><div class=\"pre\"><b>foo</b>\nbar</div>", "<div class=\"pre\">test<b>foo</b>\nbar</div>" ],
|
||||
/*56-04*/[ "<div>test</div><div class=\"pre\"><b>foo\n</b>bar\nbaz</div>", "<div class=\"pre\">test<b>foo\n</b>bar\nbaz</div>" ],
|
||||
/*57-05*/[ "<div>test</div><div class=\"pre\">foobar<br>baz</div>", "<div class=\"pre\">testfoobar<br>baz</div>" ],
|
||||
/*58-06*/[ "<div>test</div><div class=\"pre\"><b>foobar<br>baz</b></div>", "<div class=\"pre\">test<b>foobar<br>baz</b></div>" ],
|
||||
/*59-07*/[ "<div>test</div><div class=\"pre\"><div>foobar</div>baz</div>", "<div>testfoobar</div><div class=\"pre\">baz</div>" ],
|
||||
/*60-08*/[ "<div>test</div><div class=\"pre\">foobar<div>baz</div></div>", "<div class=\"pre\">testfoobar<div>baz</div></div>" ],
|
||||
/*61-09*/[ "<div>test</div><div class=\"pre\"><div>foobar</div>baz\nfred</div>", "<div>testfoobar</div><div class=\"pre\">baz\nfred</div>" ],
|
||||
/*62-10*/[ "<div>test</div><div class=\"pre\">foobar<div>baz</div>\nfred</div>", "<div class=\"pre\">testfoobar<div>baz</div>\nfred</div>" ],
|
||||
/*63-11*/[ "<div>test</div><div class=\"pre\"><div>foo\nbar</div>baz\nfred</div>", "<div>testfoo\nbar</div><div class=\"pre\">baz\nfred</div>" ], // BAD
|
||||
/*64-12*/[ "<div>test</div><div class=\"pre\">foo<div>bar</div>baz\nfred</div>", "<div class=\"pre\">testfoo<div>bar</div>baz\nfred</div>" ],
|
||||
|
||||
/* Some tests with lists. These exercise the MoveBlock "left in right". */
|
||||
/*65*/[ "<ul><pre><li>test</li>foobar\nbaz</pre></ul>", "<ul><pre><li>testfoobar\n</li>baz</pre></ul>" ],
|
||||
/*66*/[ "<ul><pre><li>test</li><b>foobar\nbaz</b></pre></ul>", "<ul><pre><li>test<b>foobar\n</b></li><b>baz</b></pre></ul>" ],
|
||||
/*67*/[ "<ul><pre><li>test</li><b>foo</b>bar\nbaz</pre></ul>", "<ul><pre><li>test<b>foo</b>bar\n</li>baz</pre></ul>" ],
|
||||
/*68*/[ "<ul><pre><li>test</li><b>foo</b>\nbar</pre></ul>", "<ul><pre><li>test<b>foo</b>\n</li>bar</pre></ul>" ],
|
||||
/*69*/[ "<ul><pre><li>test</li><b>foo\n</b>bar\nbaz</pre></ul>", "<ul><pre><li>test<b>foo\n</b></li>bar\nbaz</pre></ul>" ],
|
||||
|
||||
/* Last not least, some simple edge case tests. */
|
||||
/*70*/[ "<div>test</div><pre>foobar\n</pre>baz", "<div>testfoobar\n</div>baz" ],
|
||||
/*71*/[ "<div>test</div><pre>\nfoo\nbar</pre>", "<div>testfoo\n</div><pre>bar</pre>" ],
|
||||
/*72*/[ "<div>test</div><pre>\n\nfoo\nbar</pre>", "<div>test\n</div><pre>foo\nbar</pre>" ],
|
||||
];
|
||||
|
||||
/** Test for Bug 772796 **/
|
||||
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
|
||||
SimpleTest.waitForFocus(function() {
|
||||
|
||||
var sel = window.getSelection();
|
||||
var theEdit = document.getElementById("editable");
|
||||
var testName;
|
||||
var theDiv;
|
||||
|
||||
for (i = 0; i < tests.length; i++) {
|
||||
testName = "test" + i.toString();
|
||||
dump (testName+"\n");
|
||||
dump (tests[i][0]+"\n");
|
||||
|
||||
/* Set up the selection. */
|
||||
theEdit.innerHTML = "<div id=\"" + testName + "\">" + tests[i][0] + "</div>";
|
||||
theDiv = document.getElementById(testName);
|
||||
theDiv.focus();
|
||||
sel.collapse(theDiv, 0);
|
||||
synthesizeMouse(theDiv, 100, 2, {}); /* click behind and down */
|
||||
|
||||
/** First round: Forward delete. **/
|
||||
synthesizeKey("VK_DELETE", {});
|
||||
is(theDiv.innerHTML, tests[i][1], "delete(collapsed): inner HTML for " + testName);
|
||||
|
||||
/* Set up the selection. */
|
||||
theEdit.innerHTML = "<div id=\"" + testName + "\">" + tests[i][0] + "</div>";
|
||||
theDiv = document.getElementById(testName);
|
||||
theDiv.focus();
|
||||
sel.collapse(theDiv, 0);
|
||||
synthesizeMouse(theDiv, 100, 2, {}); /* click behind and down */
|
||||
|
||||
/** Second round: Backspace. **/
|
||||
synthesizeKey("VK_RIGHT", {});
|
||||
synthesizeKey("VK_BACK_SPACE", {});
|
||||
if (tests[i].length == 2) {
|
||||
is(theDiv.innerHTML, tests[i][1], "backspace: inner HTML for " + testName);
|
||||
} else {
|
||||
todo_is(theDiv.innerHTML, tests[i][1], "backspace(should be): inner HTML for " + testName);
|
||||
is(theDiv.innerHTML, tests[i][2], "backspace(currently is): inner HTML for " + testName);
|
||||
}
|
||||
|
||||
/* Set up the selection. */
|
||||
theEdit.innerHTML = "<div id=\"" + testName + "\">" + tests[i][0] + "</div>";
|
||||
theDiv = document.getElementById(testName);
|
||||
theDiv.focus();
|
||||
sel.collapse(theDiv, 0);
|
||||
synthesizeMouse(theDiv, 100, 2, {}); /* click behind and down */
|
||||
|
||||
/** Third round: Delete with non-collapsed selection. **/
|
||||
if (i == 72) {
|
||||
// This test doesn't work, since we can't select only one newline using the right arrow key.
|
||||
continue;
|
||||
}
|
||||
synthesizeKey("VK_LEFT", {});
|
||||
/* Strangely enough we need to hit "right arrow" three times to select two characters. */
|
||||
synthesizeKey("VK_RIGHT", {shiftKey:true});
|
||||
synthesizeKey("VK_RIGHT", {shiftKey:true});
|
||||
synthesizeKey("VK_RIGHT", {shiftKey:true});
|
||||
synthesizeKey("VK_DELETE", {});
|
||||
|
||||
/* We always expect to the delete the "tf" in "testfoo". */
|
||||
var expected = tests[i][1].replace("testfoo", "tesoo")
|
||||
.replace("test<b>foo", "tes<b>oo")
|
||||
.replace("test<span class=\"pre\">foo", "tes<span class=\"pre\">oo")
|
||||
.replace("test<span class=\"pre\"><b>foo", "tes<span class=\"pre\"><b>oo");
|
||||
is(theDiv.innerHTML, expected, "delete(non-collapsed): inner HTML for " + testName);
|
||||
}
|
||||
|
||||
SimpleTest.finish();
|
||||
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
</pre>
|
||||
</body>
|
||||
</html>
|
|
@ -16,6 +16,7 @@
|
|||
#include "nsContentUtils.h"
|
||||
#include "nsIScriptSecurityManager.h"
|
||||
#include "nsJSPrincipals.h"
|
||||
#include "nsIScriptError.h"
|
||||
#include "jswrapper.h"
|
||||
|
||||
extern PRLogModuleInfo *MCD;
|
||||
|
@ -106,7 +107,18 @@ nsresult EvaluateAdminConfigScript(const char *js_buffer, size_t length,
|
|||
|
||||
nsAutoCString script(js_buffer, length);
|
||||
JS::RootedValue v(cx);
|
||||
rv = xpc->EvalInSandboxObject(NS_ConvertUTF8toUTF16(script), filename, cx,
|
||||
|
||||
nsString convertedScript = NS_ConvertUTF8toUTF16(script);
|
||||
if (convertedScript.Length() == 0) {
|
||||
nsContentUtils::ReportToConsoleNonLocalized(
|
||||
NS_LITERAL_STRING("Your AutoConfig file is ASCII. Please convert it to UTF-8."),
|
||||
nsIScriptError::warningFlag,
|
||||
NS_LITERAL_CSTRING("autoconfig"),
|
||||
nullptr);
|
||||
/* If the length is 0, the conversion failed. Fallback to ASCII */
|
||||
convertedScript = NS_ConvertASCIItoUTF16(script);
|
||||
}
|
||||
rv = xpc->EvalInSandboxObject(convertedScript, filename, cx,
|
||||
autoconfigSb, JSVERSION_LATEST, &v);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
|
|
|
@ -7,7 +7,6 @@
|
|||
include('/ipc/chromium/chromium-config.mozbuild')
|
||||
SOURCES += [
|
||||
'mozEnglishWordUtils.cpp',
|
||||
'mozGenericWordUtils.cpp',
|
||||
'mozInlineSpellChecker.cpp',
|
||||
'mozInlineSpellWordUtil.cpp',
|
||||
'mozPersonalDictionary.cpp',
|
||||
|
|
|
@ -1,40 +0,0 @@
|
|||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* 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 "mozGenericWordUtils.h"
|
||||
|
||||
NS_IMPL_ISUPPORTS(mozGenericWordUtils, mozISpellI18NUtil)
|
||||
|
||||
// do something sensible but generic ... eventually. For now whine.
|
||||
|
||||
mozGenericWordUtils::mozGenericWordUtils()
|
||||
{
|
||||
/* member initializers and constructor code */
|
||||
}
|
||||
|
||||
mozGenericWordUtils::~mozGenericWordUtils()
|
||||
{
|
||||
/* destructor code */
|
||||
}
|
||||
|
||||
NS_IMETHODIMP mozGenericWordUtils::GetLanguage(char16_t * *aLanguage)
|
||||
{
|
||||
return NS_ERROR_NOT_IMPLEMENTED;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP mozGenericWordUtils::GetRootForm(const char16_t *word, uint32_t type, char16_t ***words, uint32_t *count)
|
||||
{
|
||||
return NS_ERROR_NOT_IMPLEMENTED;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP mozGenericWordUtils::FromRootForm(const char16_t *word, const char16_t **iwords, uint32_t icount, char16_t ***owords, uint32_t *ocount)
|
||||
{
|
||||
return NS_ERROR_NOT_IMPLEMENTED;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP mozGenericWordUtils::FindNextWord(const char16_t *word, uint32_t length, uint32_t offset, int32_t *begin, int32_t *end)
|
||||
{
|
||||
return NS_ERROR_NOT_IMPLEMENTED;
|
||||
}
|
||||
|
|
@ -1,24 +0,0 @@
|
|||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* 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 mozGenericWordUtils_h__
|
||||
#define mozGenericWordUtils_h__
|
||||
|
||||
#include "nsCOMPtr.h"
|
||||
#include "mozISpellI18NUtil.h"
|
||||
#include "nsCycleCollectionParticipant.h"
|
||||
|
||||
class mozGenericWordUtils : public mozISpellI18NUtil
|
||||
{
|
||||
protected:
|
||||
virtual ~mozGenericWordUtils();
|
||||
public:
|
||||
NS_DECL_ISUPPORTS
|
||||
NS_DECL_MOZISPELLI18NUTIL
|
||||
|
||||
mozGenericWordUtils();
|
||||
};
|
||||
|
||||
#endif
|
|
@ -5,36 +5,28 @@
|
|||
|
||||
#include "mozSpellI18NManager.h"
|
||||
#include "mozEnglishWordUtils.h"
|
||||
#include "mozGenericWordUtils.h"
|
||||
#include "nsString.h"
|
||||
#include "mozilla/nsRefPtr.h"
|
||||
|
||||
NS_IMPL_ISUPPORTS(mozSpellI18NManager, mozISpellI18NManager)
|
||||
|
||||
mozSpellI18NManager::mozSpellI18NManager()
|
||||
{
|
||||
/* member initializers and constructor code */
|
||||
}
|
||||
|
||||
mozSpellI18NManager::~mozSpellI18NManager()
|
||||
{
|
||||
/* destructor code */
|
||||
}
|
||||
|
||||
NS_IMETHODIMP mozSpellI18NManager::GetUtil(const char16_t *aLanguage, mozISpellI18NUtil **_retval)
|
||||
{
|
||||
if( nullptr == _retval) {
|
||||
if (!_retval) {
|
||||
return NS_ERROR_NULL_POINTER;
|
||||
}
|
||||
*_retval = nullptr;
|
||||
nsAutoString lang;
|
||||
lang.Assign(aLanguage);
|
||||
if(lang.EqualsLiteral("en")){
|
||||
*_retval = new mozEnglishWordUtils;
|
||||
}
|
||||
else{
|
||||
*_retval = new mozEnglishWordUtils;
|
||||
}
|
||||
|
||||
NS_IF_ADDREF(*_retval);
|
||||
// XXX TODO Actually handle multiple languages.
|
||||
nsRefPtr<mozEnglishWordUtils> utils = new mozEnglishWordUtils;
|
||||
utils.forget(_retval);
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,80 @@
|
|||
/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
|
||||
* 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_GFX_CRITICALSECTION_H_
|
||||
#define MOZILLA_GFX_CRITICALSECTION_H_
|
||||
|
||||
#ifdef WIN32
|
||||
#include <windows.h>
|
||||
#else
|
||||
#include <pthread.h>
|
||||
#include "mozilla/DebugOnly.h"
|
||||
#endif
|
||||
|
||||
namespace mozilla {
|
||||
namespace gfx {
|
||||
|
||||
#ifdef WIN32
|
||||
|
||||
class CriticalSection {
|
||||
public:
|
||||
CriticalSection() { ::InitializeCriticalSection(&mCriticalSection); }
|
||||
|
||||
~CriticalSection() { ::DeleteCriticalSection(&mCriticalSection); }
|
||||
|
||||
void Enter() { ::EnterCriticalSection(&mCriticalSection); }
|
||||
|
||||
void Leave() { ::LeaveCriticalSection(&mCriticalSection); }
|
||||
|
||||
protected:
|
||||
CRITICAL_SECTION mCriticalSection;
|
||||
};
|
||||
|
||||
#else
|
||||
// posix
|
||||
|
||||
class PosixCondvar;
|
||||
class CriticalSection {
|
||||
public:
|
||||
CriticalSection() {
|
||||
DebugOnly<int> err = pthread_mutex_init(&mMutex, nullptr);
|
||||
MOZ_ASSERT(!err);
|
||||
}
|
||||
|
||||
~CriticalSection() {
|
||||
DebugOnly<int> err = pthread_mutex_destroy(&mMutex);
|
||||
MOZ_ASSERT(!err);
|
||||
}
|
||||
|
||||
void Enter() {
|
||||
DebugOnly<int> err = pthread_mutex_lock(&mMutex);
|
||||
MOZ_ASSERT(!err);
|
||||
}
|
||||
|
||||
void Leave() {
|
||||
DebugOnly<int> err = pthread_mutex_unlock(&mMutex);
|
||||
MOZ_ASSERT(!err);
|
||||
}
|
||||
|
||||
protected:
|
||||
pthread_mutex_t mMutex;
|
||||
friend class PosixCondVar;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
/// RAII helper.
|
||||
struct CriticalSectionAutoEnter {
|
||||
explicit CriticalSectionAutoEnter(CriticalSection* aSection) : mSection(aSection) { mSection->Enter(); }
|
||||
~CriticalSectionAutoEnter() { mSection->Leave(); }
|
||||
protected:
|
||||
CriticalSection* mSection;
|
||||
};
|
||||
|
||||
|
||||
} // namespace
|
||||
} // namespace
|
||||
|
||||
#endif
|
|
@ -6,6 +6,10 @@
|
|||
#ifndef MOZILLA_GFX_DRAWCOMMAND_H_
|
||||
#define MOZILLA_GFX_DRAWCOMMAND_H_
|
||||
|
||||
|
||||
#define _USE_MATH_DEFINES
|
||||
#include <math.h>
|
||||
|
||||
#include "2D.h"
|
||||
#include "Filters.h"
|
||||
#include <vector>
|
||||
|
@ -31,7 +35,8 @@ enum class CommandType : int8_t {
|
|||
PUSHCLIP,
|
||||
PUSHCLIPRECT,
|
||||
POPCLIP,
|
||||
SETTRANSFORM
|
||||
SETTRANSFORM,
|
||||
FLUSH
|
||||
};
|
||||
|
||||
class DrawingCommand
|
||||
|
@ -39,7 +44,9 @@ class DrawingCommand
|
|||
public:
|
||||
virtual ~DrawingCommand() {}
|
||||
|
||||
virtual void ExecuteOnDT(DrawTarget* aDT, const Matrix& aTransform) = 0;
|
||||
virtual void ExecuteOnDT(DrawTarget* aDT, const Matrix* aTransform = nullptr) const = 0;
|
||||
|
||||
virtual bool GetAffectedRect(Rect& aDeviceRect, const Matrix& aTransform) const { return false; }
|
||||
|
||||
protected:
|
||||
explicit DrawingCommand(CommandType aType)
|
||||
|
@ -130,7 +137,7 @@ public:
|
|||
{
|
||||
}
|
||||
|
||||
virtual void ExecuteOnDT(DrawTarget* aDT, const Matrix&)
|
||||
virtual void ExecuteOnDT(DrawTarget* aDT, const Matrix*) const
|
||||
{
|
||||
aDT->DrawSurface(mSurface, mDest, mSource, mSurfOptions, mOptions);
|
||||
}
|
||||
|
@ -154,7 +161,7 @@ public:
|
|||
{
|
||||
}
|
||||
|
||||
virtual void ExecuteOnDT(DrawTarget* aDT, const Matrix&)
|
||||
virtual void ExecuteOnDT(DrawTarget* aDT, const Matrix*) const
|
||||
{
|
||||
aDT->DrawFilter(mFilter, mSourceRect, mDestPoint, mOptions);
|
||||
}
|
||||
|
@ -175,7 +182,7 @@ public:
|
|||
{
|
||||
}
|
||||
|
||||
virtual void ExecuteOnDT(DrawTarget* aDT, const Matrix&)
|
||||
virtual void ExecuteOnDT(DrawTarget* aDT, const Matrix*) const
|
||||
{
|
||||
aDT->ClearRect(mRect);
|
||||
}
|
||||
|
@ -197,11 +204,13 @@ public:
|
|||
{
|
||||
}
|
||||
|
||||
virtual void ExecuteOnDT(DrawTarget* aDT, const Matrix& aTransform)
|
||||
virtual void ExecuteOnDT(DrawTarget* aDT, const Matrix* aTransform) const
|
||||
{
|
||||
MOZ_ASSERT(!aTransform.HasNonIntegerTranslation());
|
||||
MOZ_ASSERT(!aTransform || !aTransform->HasNonIntegerTranslation());
|
||||
Point dest(Float(mDestination.x), Float(mDestination.y));
|
||||
dest = aTransform * dest;
|
||||
if (aTransform) {
|
||||
dest = (*aTransform) * dest;
|
||||
}
|
||||
aDT->CopySurface(mSurface, mSourceRect, IntPoint(uint32_t(dest.x), uint32_t(dest.y)));
|
||||
}
|
||||
|
||||
|
@ -224,11 +233,17 @@ public:
|
|||
{
|
||||
}
|
||||
|
||||
virtual void ExecuteOnDT(DrawTarget* aDT, const Matrix&)
|
||||
virtual void ExecuteOnDT(DrawTarget* aDT, const Matrix*) const
|
||||
{
|
||||
aDT->FillRect(mRect, mPattern, mOptions);
|
||||
}
|
||||
|
||||
bool GetAffectedRect(Rect& aDeviceRect, const Matrix& aTransform) const
|
||||
{
|
||||
aDeviceRect = aTransform.TransformBounds(mRect);
|
||||
return true;
|
||||
}
|
||||
|
||||
private:
|
||||
Rect mRect;
|
||||
StoredPattern mPattern;
|
||||
|
@ -248,9 +263,14 @@ public:
|
|||
, mStrokeOptions(aStrokeOptions)
|
||||
, mOptions(aOptions)
|
||||
{
|
||||
if (aStrokeOptions.mDashLength) {
|
||||
mDashes.resize(aStrokeOptions.mDashLength);
|
||||
mStrokeOptions.mDashPattern = &mDashes.front();
|
||||
memcpy(&mDashes.front(), aStrokeOptions.mDashPattern, mStrokeOptions.mDashLength * sizeof(Float));
|
||||
}
|
||||
}
|
||||
|
||||
virtual void ExecuteOnDT(DrawTarget* aDT, const Matrix&)
|
||||
virtual void ExecuteOnDT(DrawTarget* aDT, const Matrix*) const
|
||||
{
|
||||
aDT->StrokeRect(mRect, mPattern, mStrokeOptions, mOptions);
|
||||
}
|
||||
|
@ -260,6 +280,7 @@ private:
|
|||
StoredPattern mPattern;
|
||||
StrokeOptions mStrokeOptions;
|
||||
DrawOptions mOptions;
|
||||
std::vector<Float> mDashes;
|
||||
};
|
||||
|
||||
class StrokeLineCommand : public DrawingCommand
|
||||
|
@ -279,7 +300,7 @@ public:
|
|||
{
|
||||
}
|
||||
|
||||
virtual void ExecuteOnDT(DrawTarget* aDT, const Matrix&)
|
||||
virtual void ExecuteOnDT(DrawTarget* aDT, const Matrix*) const
|
||||
{
|
||||
aDT->StrokeLine(mStart, mEnd, mPattern, mStrokeOptions, mOptions);
|
||||
}
|
||||
|
@ -305,17 +326,58 @@ public:
|
|||
{
|
||||
}
|
||||
|
||||
virtual void ExecuteOnDT(DrawTarget* aDT, const Matrix&)
|
||||
virtual void ExecuteOnDT(DrawTarget* aDT, const Matrix*) const
|
||||
{
|
||||
aDT->Fill(mPath, mPattern, mOptions);
|
||||
}
|
||||
|
||||
bool GetAffectedRect(Rect& aDeviceRect, const Matrix& aTransform) const
|
||||
{
|
||||
aDeviceRect = mPath->GetBounds(aTransform);
|
||||
return true;
|
||||
}
|
||||
|
||||
private:
|
||||
RefPtr<Path> mPath;
|
||||
StoredPattern mPattern;
|
||||
DrawOptions mOptions;
|
||||
};
|
||||
|
||||
#ifndef M_SQRT2
|
||||
#define M_SQRT2 1.41421356237309504880
|
||||
#endif
|
||||
|
||||
#ifndef M_SQRT1_2
|
||||
#define M_SQRT1_2 0.707106781186547524400844362104849039
|
||||
#endif
|
||||
|
||||
// The logic for this comes from _cairo_stroke_style_max_distance_from_path
|
||||
static Rect
|
||||
PathExtentsToMaxStrokeExtents(const StrokeOptions &aStrokeOptions,
|
||||
const Rect &aRect,
|
||||
const Matrix &aTransform)
|
||||
{
|
||||
double styleExpansionFactor = 0.5f;
|
||||
|
||||
if (aStrokeOptions.mLineCap == CapStyle::SQUARE) {
|
||||
styleExpansionFactor = M_SQRT1_2;
|
||||
}
|
||||
|
||||
if (aStrokeOptions.mLineJoin == JoinStyle::MITER &&
|
||||
styleExpansionFactor < M_SQRT2 * aStrokeOptions.mMiterLimit) {
|
||||
styleExpansionFactor = M_SQRT2 * aStrokeOptions.mMiterLimit;
|
||||
}
|
||||
|
||||
styleExpansionFactor *= aStrokeOptions.mLineWidth;
|
||||
|
||||
double dx = styleExpansionFactor * hypot(aTransform._11, aTransform._21);
|
||||
double dy = styleExpansionFactor * hypot(aTransform._22, aTransform._12);
|
||||
|
||||
Rect result = aRect;
|
||||
result.Inflate(dx, dy);
|
||||
return result;
|
||||
}
|
||||
|
||||
class StrokeCommand : public DrawingCommand
|
||||
{
|
||||
public:
|
||||
|
@ -329,18 +391,30 @@ public:
|
|||
, mStrokeOptions(aStrokeOptions)
|
||||
, mOptions(aOptions)
|
||||
{
|
||||
if (aStrokeOptions.mDashLength) {
|
||||
mDashes.resize(aStrokeOptions.mDashLength);
|
||||
mStrokeOptions.mDashPattern = &mDashes.front();
|
||||
memcpy(&mDashes.front(), aStrokeOptions.mDashPattern, mStrokeOptions.mDashLength * sizeof(Float));
|
||||
}
|
||||
}
|
||||
|
||||
virtual void ExecuteOnDT(DrawTarget* aDT, const Matrix&)
|
||||
virtual void ExecuteOnDT(DrawTarget* aDT, const Matrix*) const
|
||||
{
|
||||
aDT->Stroke(mPath, mPattern, mStrokeOptions, mOptions);
|
||||
}
|
||||
|
||||
bool GetAffectedRect(Rect& aDeviceRect, const Matrix& aTransform) const
|
||||
{
|
||||
aDeviceRect = PathExtentsToMaxStrokeExtents(mStrokeOptions, mPath->GetBounds(aTransform), aTransform);
|
||||
return true;
|
||||
}
|
||||
|
||||
private:
|
||||
RefPtr<Path> mPath;
|
||||
StoredPattern mPattern;
|
||||
StrokeOptions mStrokeOptions;
|
||||
DrawOptions mOptions;
|
||||
std::vector<Float> mDashes;
|
||||
};
|
||||
|
||||
class FillGlyphsCommand : public DrawingCommand
|
||||
|
@ -361,7 +435,7 @@ public:
|
|||
memcpy(&mGlyphs.front(), aBuffer.mGlyphs, sizeof(Glyph) * aBuffer.mNumGlyphs);
|
||||
}
|
||||
|
||||
virtual void ExecuteOnDT(DrawTarget* aDT, const Matrix&)
|
||||
virtual void ExecuteOnDT(DrawTarget* aDT, const Matrix*) const
|
||||
{
|
||||
GlyphBuffer buf;
|
||||
buf.mNumGlyphs = mGlyphs.size();
|
||||
|
@ -390,7 +464,7 @@ public:
|
|||
{
|
||||
}
|
||||
|
||||
virtual void ExecuteOnDT(DrawTarget* aDT, const Matrix&)
|
||||
virtual void ExecuteOnDT(DrawTarget* aDT, const Matrix*) const
|
||||
{
|
||||
aDT->Mask(mSource, mMask, mOptions);
|
||||
}
|
||||
|
@ -416,7 +490,7 @@ public:
|
|||
{
|
||||
}
|
||||
|
||||
virtual void ExecuteOnDT(DrawTarget* aDT, const Matrix&)
|
||||
virtual void ExecuteOnDT(DrawTarget* aDT, const Matrix*) const
|
||||
{
|
||||
aDT->MaskSurface(mSource, mMask, mOffset, mOptions);
|
||||
}
|
||||
|
@ -437,7 +511,7 @@ public:
|
|||
{
|
||||
}
|
||||
|
||||
virtual void ExecuteOnDT(DrawTarget* aDT, const Matrix&)
|
||||
virtual void ExecuteOnDT(DrawTarget* aDT, const Matrix*) const
|
||||
{
|
||||
aDT->PushClip(mPath);
|
||||
}
|
||||
|
@ -455,7 +529,7 @@ public:
|
|||
{
|
||||
}
|
||||
|
||||
virtual void ExecuteOnDT(DrawTarget* aDT, const Matrix&)
|
||||
virtual void ExecuteOnDT(DrawTarget* aDT, const Matrix*) const
|
||||
{
|
||||
aDT->PushClipRect(mRect);
|
||||
}
|
||||
|
@ -472,7 +546,7 @@ public:
|
|||
{
|
||||
}
|
||||
|
||||
virtual void ExecuteOnDT(DrawTarget* aDT, const Matrix&)
|
||||
virtual void ExecuteOnDT(DrawTarget* aDT, const Matrix*) const
|
||||
{
|
||||
aDT->PopClip();
|
||||
}
|
||||
|
@ -487,17 +561,33 @@ public:
|
|||
{
|
||||
}
|
||||
|
||||
virtual void ExecuteOnDT(DrawTarget* aDT, const Matrix& aMatrix)
|
||||
virtual void ExecuteOnDT(DrawTarget* aDT, const Matrix* aMatrix) const
|
||||
{
|
||||
Matrix transform = mTransform;
|
||||
transform *= aMatrix;
|
||||
aDT->SetTransform(transform);
|
||||
if (aMatrix) {
|
||||
aDT->SetTransform(mTransform * (*aMatrix));
|
||||
} else {
|
||||
aDT->SetTransform(mTransform);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
Matrix mTransform;
|
||||
};
|
||||
|
||||
class FlushCommand : public DrawingCommand
|
||||
{
|
||||
public:
|
||||
explicit FlushCommand()
|
||||
: DrawingCommand(CommandType::FLUSH)
|
||||
{
|
||||
}
|
||||
|
||||
virtual void ExecuteOnDT(DrawTarget* aDT, const Matrix*) const
|
||||
{
|
||||
aDT->Flush();
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace gfx
|
||||
|
||||
} // namespace mozilla
|
||||
|
|
|
@ -188,7 +188,7 @@ DrawTargetCaptureImpl::ReplayToDrawTarget(DrawTarget* aDT, const Matrix& aTransf
|
|||
uint8_t* current = start;
|
||||
|
||||
while (current < start + mDrawCommandStorage.size()) {
|
||||
reinterpret_cast<DrawingCommand*>(current + sizeof(uint32_t))->ExecuteOnDT(aDT, aTransform);
|
||||
reinterpret_cast<DrawingCommand*>(current + sizeof(uint32_t))->ExecuteOnDT(aDT, &aTransform);
|
||||
current += *(uint32_t*)current;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,120 @@
|
|||
/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
|
||||
* 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 "DrawingJob.h"
|
||||
#include "JobScheduler.h"
|
||||
#include "mozilla/gfx/2D.h"
|
||||
|
||||
namespace mozilla {
|
||||
namespace gfx {
|
||||
|
||||
DrawingJobBuilder::DrawingJobBuilder()
|
||||
{}
|
||||
|
||||
DrawingJobBuilder::~DrawingJobBuilder()
|
||||
{
|
||||
MOZ_ASSERT(!mDrawTarget);
|
||||
}
|
||||
|
||||
void
|
||||
DrawingJob::Clear()
|
||||
{
|
||||
mCommandBuffer = nullptr;
|
||||
mCursor = 0;
|
||||
}
|
||||
|
||||
void
|
||||
DrawingJobBuilder::BeginDrawingJob(DrawTarget* aTarget, IntPoint aOffset,
|
||||
SyncObject* aStart)
|
||||
{
|
||||
MOZ_ASSERT(mCommandOffsets.empty());
|
||||
MOZ_ASSERT(aTarget);
|
||||
mDrawTarget = aTarget;
|
||||
mOffset = aOffset;
|
||||
mStart = aStart;
|
||||
}
|
||||
|
||||
DrawingJob*
|
||||
DrawingJobBuilder::EndDrawingJob(CommandBuffer* aCmdBuffer,
|
||||
SyncObject* aCompletion,
|
||||
WorkerThread* aPinToWorker)
|
||||
{
|
||||
MOZ_ASSERT(mDrawTarget);
|
||||
DrawingJob* task = new DrawingJob(mDrawTarget, mOffset, mStart, aCompletion, aPinToWorker);
|
||||
task->mCommandBuffer = aCmdBuffer;
|
||||
task->mCommandOffsets = Move(mCommandOffsets);
|
||||
|
||||
mDrawTarget = nullptr;
|
||||
mOffset = IntPoint();
|
||||
mStart = nullptr;
|
||||
|
||||
return task;
|
||||
}
|
||||
|
||||
DrawingJob::DrawingJob(DrawTarget* aTarget, IntPoint aOffset,
|
||||
SyncObject* aStart, SyncObject* aCompletion,
|
||||
WorkerThread* aPinToWorker)
|
||||
: Job(aStart, aCompletion, aPinToWorker)
|
||||
, mCommandBuffer(nullptr)
|
||||
, mCursor(0)
|
||||
, mDrawTarget(aTarget)
|
||||
, mOffset(aOffset)
|
||||
{
|
||||
mCommandOffsets.reserve(64);
|
||||
}
|
||||
|
||||
JobStatus
|
||||
DrawingJob::Run()
|
||||
{
|
||||
while (mCursor < mCommandOffsets.size()) {
|
||||
|
||||
const DrawingCommand* cmd = mCommandBuffer->GetDrawingCommand(mCommandOffsets[mCursor]);
|
||||
|
||||
if (!cmd) {
|
||||
return JobStatus::Error;
|
||||
}
|
||||
|
||||
cmd->ExecuteOnDT(mDrawTarget);
|
||||
|
||||
++mCursor;
|
||||
}
|
||||
|
||||
return JobStatus::Complete;
|
||||
}
|
||||
|
||||
DrawingJob::~DrawingJob()
|
||||
{
|
||||
Clear();
|
||||
}
|
||||
|
||||
const DrawingCommand*
|
||||
CommandBuffer::GetDrawingCommand(ptrdiff_t aId)
|
||||
{
|
||||
return static_cast<DrawingCommand*>(mStorage.GetStorage(aId));
|
||||
}
|
||||
|
||||
CommandBuffer::~CommandBuffer()
|
||||
{
|
||||
mStorage.ForEach([](void* item){
|
||||
static_cast<DrawingCommand*>(item)->~DrawingCommand();
|
||||
});
|
||||
mStorage.Clear();
|
||||
}
|
||||
|
||||
void
|
||||
CommandBufferBuilder::BeginCommandBuffer(size_t aBufferSize)
|
||||
{
|
||||
MOZ_ASSERT(!mCommands);
|
||||
mCommands = new CommandBuffer(aBufferSize);
|
||||
}
|
||||
|
||||
already_AddRefed<CommandBuffer>
|
||||
CommandBufferBuilder::EndCommandBuffer()
|
||||
{
|
||||
return mCommands.forget();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace
|
|
@ -0,0 +1,157 @@
|
|||
/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
|
||||
* 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_GFX_COMMANDBUFFER_H_
|
||||
#define MOZILLA_GFX_COMMANDBUFFER_H_
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include "mozilla/RefPtr.h"
|
||||
#include "mozilla/Assertions.h"
|
||||
#include "mozilla/gfx/Matrix.h"
|
||||
#include "mozilla/gfx/JobScheduler.h"
|
||||
#include "mozilla/gfx/IterableArena.h"
|
||||
#include "DrawCommand.h"
|
||||
|
||||
namespace mozilla {
|
||||
namespace gfx {
|
||||
|
||||
class DrawingCommand;
|
||||
class PrintCommand;
|
||||
class SignalCommand;
|
||||
class DrawingJob;
|
||||
class WaitCommand;
|
||||
|
||||
class SyncObject;
|
||||
class MultiThreadedJobQueue;
|
||||
|
||||
class DrawTarget;
|
||||
|
||||
class DrawingJobBuilder;
|
||||
class CommandBufferBuilder;
|
||||
|
||||
/// Contains a sequence of immutable drawing commands that are typically used by
|
||||
/// several DrawingJobs.
|
||||
///
|
||||
/// CommandBuffer objects are built using CommandBufferBuilder.
|
||||
class CommandBuffer : public external::AtomicRefCounted<CommandBuffer>
|
||||
{
|
||||
public:
|
||||
MOZ_DECLARE_REFCOUNTED_TYPENAME(CommandBuffer)
|
||||
|
||||
~CommandBuffer();
|
||||
|
||||
const DrawingCommand* GetDrawingCommand(ptrdiff_t aId);
|
||||
|
||||
protected:
|
||||
explicit CommandBuffer(size_t aSize = 256)
|
||||
: mStorage(IterableArena::GROWABLE, aSize)
|
||||
{}
|
||||
|
||||
IterableArena mStorage;
|
||||
friend class CommandBufferBuilder;
|
||||
};
|
||||
|
||||
/// Generates CommandBuffer objects.
|
||||
///
|
||||
/// The builder is a separate object to ensure that commands are not added to a
|
||||
/// submitted CommandBuffer.
|
||||
class CommandBufferBuilder
|
||||
{
|
||||
public:
|
||||
void BeginCommandBuffer(size_t aBufferSize = 256);
|
||||
|
||||
already_AddRefed<CommandBuffer> EndCommandBuffer();
|
||||
|
||||
/// Build the CommandBuffer, command after command.
|
||||
/// This must be used between BeginCommandBuffer and EndCommandBuffer.
|
||||
template<typename T, typename... Args>
|
||||
ptrdiff_t AddCommand(Args&&... aArgs)
|
||||
{
|
||||
static_assert(IsBaseOf<DrawingCommand, T>::value,
|
||||
"T must derive from DrawingCommand");
|
||||
return mCommands->mStorage.Alloc<T>(Forward<Args>(aArgs)...);
|
||||
}
|
||||
|
||||
bool HasCommands() const { return !!mCommands; }
|
||||
|
||||
protected:
|
||||
RefPtr<CommandBuffer> mCommands;
|
||||
};
|
||||
|
||||
/// Stores multiple commands to be executed sequencially.
|
||||
class DrawingJob : public Job {
|
||||
public:
|
||||
~DrawingJob();
|
||||
|
||||
virtual JobStatus Run() override;
|
||||
|
||||
protected:
|
||||
DrawingJob(DrawTarget* aTarget,
|
||||
IntPoint aOffset,
|
||||
SyncObject* aStart,
|
||||
SyncObject* aCompletion,
|
||||
WorkerThread* aPinToWorker = nullptr);
|
||||
|
||||
/// Runs the tasks's destructors and resets the buffer.
|
||||
void Clear();
|
||||
|
||||
std::vector<ptrdiff_t> mCommandOffsets;
|
||||
RefPtr<CommandBuffer> mCommandBuffer;
|
||||
uint32_t mCursor;
|
||||
|
||||
RefPtr<DrawTarget> mDrawTarget;
|
||||
IntPoint mOffset;
|
||||
|
||||
friend class DrawingJobBuilder;
|
||||
};
|
||||
|
||||
/// Generates DrawingJob objects.
|
||||
///
|
||||
/// The builder is a separate object to ensure that commands are not added to a
|
||||
/// submitted DrawingJob.
|
||||
class DrawingJobBuilder {
|
||||
public:
|
||||
DrawingJobBuilder();
|
||||
|
||||
~DrawingJobBuilder();
|
||||
|
||||
/// Allocates a DrawingJob.
|
||||
///
|
||||
/// call this method before starting to add commands.
|
||||
void BeginDrawingJob(DrawTarget* aTarget, IntPoint aOffset,
|
||||
SyncObject* aStart = nullptr);
|
||||
|
||||
/// Build the DrawingJob, command after command.
|
||||
/// This must be used between BeginDrawingJob and EndDrawingJob.
|
||||
void AddCommand(ptrdiff_t offset)
|
||||
{
|
||||
mCommandOffsets.push_back(offset);
|
||||
}
|
||||
|
||||
/// Finalizes and returns the drawing task.
|
||||
///
|
||||
/// If aCompletion is not null, the sync object will be signaled after the
|
||||
/// task buffer is destroyed (and after the destructor of the tasks have run).
|
||||
/// In most cases this means after the completion of all tasks in the task buffer,
|
||||
/// but also when the task buffer is destroyed due to an error.
|
||||
DrawingJob* EndDrawingJob(CommandBuffer* aCmdBuffer,
|
||||
SyncObject* aCompletion = nullptr,
|
||||
WorkerThread* aPinToWorker = nullptr);
|
||||
|
||||
/// Returns true between BeginDrawingJob and EndDrawingJob, false otherwise.
|
||||
bool HasDrawingJob() const { return !!mDrawTarget; }
|
||||
|
||||
protected:
|
||||
std::vector<ptrdiff_t> mCommandOffsets;
|
||||
RefPtr<DrawTarget> mDrawTarget;
|
||||
IntPoint mOffset;
|
||||
RefPtr<SyncObject> mStart;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
} // namespace
|
||||
|
||||
#endif
|
|
@ -0,0 +1,193 @@
|
|||
/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
|
||||
* 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_GFX_ITERABLEARENA_H_
|
||||
#define MOZILLA_GFX_ITERABLEARENA_H_
|
||||
|
||||
#include "mozilla/Move.h"
|
||||
#include "mozilla/Assertions.h"
|
||||
#include "mozilla/gfx/Logging.h"
|
||||
|
||||
#include <string.h>
|
||||
#include <vector>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
|
||||
namespace mozilla {
|
||||
namespace gfx {
|
||||
|
||||
/// A simple pool allocator for plain data structures.
|
||||
///
|
||||
/// Beware that the pool will not attempt to run the destructors. It is the
|
||||
/// responsibility of the user of this class to either use objects with no
|
||||
/// destructor or to manually call the allocated objects destructors.
|
||||
/// If the pool is growable, its allocated objects must be safely moveable in
|
||||
/// in memory (through memcpy).
|
||||
class IterableArena {
|
||||
protected:
|
||||
struct Header
|
||||
{
|
||||
size_t mBlocSize;
|
||||
};
|
||||
public:
|
||||
enum ArenaType {
|
||||
FIXED_SIZE,
|
||||
GROWABLE
|
||||
};
|
||||
|
||||
IterableArena(ArenaType aType, size_t aStorageSize)
|
||||
: mSize(aStorageSize)
|
||||
, mCursor(0)
|
||||
, mIsGrowable(aType == GROWABLE)
|
||||
{
|
||||
if (mSize == 0) {
|
||||
mSize = 128;
|
||||
}
|
||||
|
||||
mStorage = (uint8_t*)malloc(mSize);
|
||||
if (mStorage == nullptr) {
|
||||
gfxCriticalError() << "Not enough Memory allocate a memory pool of size " << aStorageSize;
|
||||
MOZ_CRASH();
|
||||
}
|
||||
}
|
||||
|
||||
~IterableArena()
|
||||
{
|
||||
free(mStorage);
|
||||
}
|
||||
|
||||
/// Constructs a new item in the pool and returns a positive offset in case of
|
||||
/// success.
|
||||
///
|
||||
/// The offset never changes even if the storage is reallocated, so users
|
||||
/// of this class should prefer storing offsets rather than direct pointers
|
||||
/// to the allocated objects.
|
||||
/// Alloc can cause the storage to be reallocated if the pool was initialized
|
||||
/// with IterableArena::GROWABLE.
|
||||
/// If for any reason the pool fails to allocate enough space for the new item
|
||||
/// Alloc returns a negative offset and the object's constructor is not called.
|
||||
template<typename T, typename... Args>
|
||||
ptrdiff_t
|
||||
Alloc(Args&&... aArgs)
|
||||
{
|
||||
void* storage = nullptr;
|
||||
auto offset = AllocRaw(sizeof(T), &storage);
|
||||
if (offset < 0) {
|
||||
return offset;
|
||||
}
|
||||
new (storage) T(Forward<Args>(aArgs)...);
|
||||
return offset;
|
||||
}
|
||||
|
||||
ptrdiff_t AllocRaw(size_t aSize, void** aOutPtr = nullptr)
|
||||
{
|
||||
const size_t blocSize = AlignedSize(sizeof(Header) + aSize);
|
||||
|
||||
if (AlignedSize(mCursor + blocSize) > mSize) {
|
||||
if (!mIsGrowable) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
size_t newSize = mSize * 2;
|
||||
while (AlignedSize(mCursor + blocSize) > newSize) {
|
||||
newSize *= 2;
|
||||
}
|
||||
|
||||
uint8_t* newStorage = (uint8_t*)realloc(mStorage, newSize);
|
||||
if (!newStorage) {
|
||||
gfxCriticalError() << "Not enough Memory to grow the memory pool, size: " << newSize;
|
||||
return -1;
|
||||
}
|
||||
|
||||
mStorage = newStorage;
|
||||
mSize = newSize;
|
||||
}
|
||||
ptrdiff_t offset = mCursor;
|
||||
GetHeader(offset)->mBlocSize = blocSize;
|
||||
mCursor += blocSize;
|
||||
if (aOutPtr) {
|
||||
*aOutPtr = GetStorage(offset);
|
||||
}
|
||||
return offset;
|
||||
}
|
||||
|
||||
/// Get access to an allocated item at a given offset (only use offsets returned
|
||||
/// by Alloc or AllocRaw).
|
||||
///
|
||||
/// If the pool is growable, the returned pointer is only valid temporarily. The
|
||||
/// underlying storage can be reallocated in Alloc or AllocRaw, so do not keep
|
||||
/// these pointers around and store the offset instead.
|
||||
void* GetStorage(ptrdiff_t offset = 0)
|
||||
{
|
||||
MOZ_ASSERT(offset >= 0);
|
||||
MOZ_ASSERT(offset < mCursor);
|
||||
return offset >= 0 ? mStorage + offset + sizeof(Header) : nullptr;
|
||||
}
|
||||
|
||||
/// Clears the storage without running any destructor and without deallocating it.
|
||||
void Clear()
|
||||
{
|
||||
mCursor = 0;
|
||||
}
|
||||
|
||||
/// Iterate over the elements allocated in this pool.
|
||||
///
|
||||
/// Takes a lambda or function object accepting a void* as parameter.
|
||||
template<typename Func>
|
||||
void ForEach(Func cb)
|
||||
{
|
||||
Iterator it;
|
||||
while (void* ptr = it.Next(this)) {
|
||||
cb(ptr);
|
||||
}
|
||||
}
|
||||
|
||||
/// A simple iterator over an arena.
|
||||
class Iterator {
|
||||
public:
|
||||
Iterator()
|
||||
: mCursor(0)
|
||||
{}
|
||||
|
||||
void* Next(IterableArena* aArena)
|
||||
{
|
||||
if (mCursor >= aArena->mCursor) {
|
||||
return nullptr;
|
||||
}
|
||||
void* result = aArena->GetStorage(mCursor);
|
||||
const size_t blocSize = aArena->GetHeader(mCursor)->mBlocSize;
|
||||
MOZ_ASSERT(blocSize != 0);
|
||||
mCursor += blocSize;
|
||||
return result;
|
||||
}
|
||||
|
||||
private:
|
||||
ptrdiff_t mCursor;
|
||||
};
|
||||
|
||||
protected:
|
||||
Header* GetHeader(ptrdiff_t offset)
|
||||
{
|
||||
return (Header*) (mStorage + offset);
|
||||
}
|
||||
|
||||
size_t AlignedSize(size_t aSize) const
|
||||
{
|
||||
const size_t alignment = sizeof(uintptr_t);
|
||||
return aSize + (alignment - (aSize % alignment)) % alignment;
|
||||
}
|
||||
|
||||
uint8_t* mStorage;
|
||||
uint32_t mSize;
|
||||
ptrdiff_t mCursor;
|
||||
bool mIsGrowable;
|
||||
|
||||
friend class Iterator;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
} // namespace
|
||||
|
||||
#endif
|
|
@ -0,0 +1,279 @@
|
|||
/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
|
||||
* 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 "JobScheduler.h"
|
||||
|
||||
namespace mozilla {
|
||||
namespace gfx {
|
||||
|
||||
JobScheduler* JobScheduler::sSingleton = nullptr;
|
||||
|
||||
bool JobScheduler::Init(uint32_t aNumThreads, uint32_t aNumQueues)
|
||||
{
|
||||
MOZ_ASSERT(!sSingleton);
|
||||
MOZ_ASSERT(aNumThreads >= aNumQueues);
|
||||
|
||||
sSingleton = new JobScheduler();
|
||||
sSingleton->mNextQueue = 0;
|
||||
|
||||
for (uint32_t i = 0; i < aNumQueues; ++i) {
|
||||
sSingleton->mDrawingQueues.push_back(new MultiThreadedJobQueue());
|
||||
}
|
||||
|
||||
for (uint32_t i = 0; i < aNumThreads; ++i) {
|
||||
sSingleton->mWorkerThreads.push_back(WorkerThread::Create(sSingleton->mDrawingQueues[i%aNumQueues]));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void JobScheduler::ShutDown()
|
||||
{
|
||||
MOZ_ASSERT(IsEnabled());
|
||||
if (!IsEnabled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (auto queue : sSingleton->mDrawingQueues) {
|
||||
queue->ShutDown();
|
||||
delete queue;
|
||||
}
|
||||
|
||||
for (WorkerThread* thread : sSingleton->mWorkerThreads) {
|
||||
// this will block until the thread is joined.
|
||||
delete thread;
|
||||
}
|
||||
|
||||
sSingleton->mWorkerThreads.clear();
|
||||
delete sSingleton;
|
||||
sSingleton = nullptr;
|
||||
}
|
||||
|
||||
JobStatus
|
||||
JobScheduler::ProcessJob(Job* aJob)
|
||||
{
|
||||
MOZ_ASSERT(aJob);
|
||||
auto status = aJob->Run();
|
||||
if (status == JobStatus::Error || status == JobStatus::Complete) {
|
||||
delete aJob;
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
void
|
||||
JobScheduler::SubmitJob(Job* aJob)
|
||||
{
|
||||
MOZ_ASSERT(aJob);
|
||||
RefPtr<SyncObject> start = aJob->GetStartSync();
|
||||
if (start && start->Register(aJob)) {
|
||||
// The Job buffer starts with a non-signaled sync object, it
|
||||
// is now registered in the list of task buffers waiting on the
|
||||
// sync object, so we should not place it in the queue.
|
||||
return;
|
||||
}
|
||||
|
||||
GetQueueForJob(aJob)->SubmitJob(aJob);
|
||||
}
|
||||
|
||||
MultiThreadedJobQueue*
|
||||
JobScheduler::GetQueueForJob(Job* aJob)
|
||||
{
|
||||
return aJob->IsPinnedToAThread() ? aJob->GetWorkerThread()->GetJobQueue()
|
||||
: GetDrawingQueue();
|
||||
}
|
||||
|
||||
Job::Job(SyncObject* aStart, SyncObject* aCompletion, WorkerThread* aThread)
|
||||
: mNextWaitingJob(nullptr)
|
||||
, mStartSync(aStart)
|
||||
, mCompletionSync(aCompletion)
|
||||
, mPinToThread(aThread)
|
||||
{
|
||||
if (mStartSync) {
|
||||
mStartSync->AddSubsequent(this);
|
||||
}
|
||||
if (mCompletionSync) {
|
||||
mCompletionSync->AddPrerequisite(this);
|
||||
}
|
||||
}
|
||||
|
||||
Job::~Job()
|
||||
{
|
||||
if (mCompletionSync) {
|
||||
//printf(" -- Job %p dtor completion %p\n", this, mCompletionSync);
|
||||
mCompletionSync->Signal();
|
||||
mCompletionSync = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
JobStatus
|
||||
SetEventJob::Run()
|
||||
{
|
||||
mEvent->Set();
|
||||
return JobStatus::Complete;
|
||||
}
|
||||
|
||||
SetEventJob::SetEventJob(EventObject* aEvent,
|
||||
SyncObject* aStart, SyncObject* aCompletion,
|
||||
WorkerThread* aWorker)
|
||||
: Job(aStart, aCompletion, aWorker)
|
||||
, mEvent(aEvent)
|
||||
{}
|
||||
|
||||
SetEventJob::~SetEventJob()
|
||||
{}
|
||||
|
||||
SyncObject::SyncObject(uint32_t aNumPrerequisites)
|
||||
: mSignals(aNumPrerequisites)
|
||||
, mFirstWaitingJob(nullptr)
|
||||
#ifdef DEBUG
|
||||
, mNumPrerequisites(aNumPrerequisites)
|
||||
, mAddedPrerequisites(0)
|
||||
#endif
|
||||
{}
|
||||
|
||||
SyncObject::~SyncObject()
|
||||
{
|
||||
MOZ_ASSERT(mFirstWaitingJob == nullptr);
|
||||
}
|
||||
|
||||
bool
|
||||
SyncObject::Register(Job* aJob)
|
||||
{
|
||||
MOZ_ASSERT(aJob);
|
||||
|
||||
// For now, ensure that when we schedule the first subsequent, we have already
|
||||
// created all of the prerequisites. This is an arbitrary restriction because
|
||||
// we specify the number of prerequisites in the constructor, but in the typical
|
||||
// scenario, if the assertion FreezePrerequisite blows up here it probably means
|
||||
// we got the initial nmber of prerequisites wrong. We can decide to remove
|
||||
// this restriction if needed.
|
||||
FreezePrerequisites();
|
||||
|
||||
int32_t signals = mSignals;
|
||||
|
||||
if (signals > 0) {
|
||||
AddWaitingJob(aJob);
|
||||
// Since Register and Signal can be called concurrently, it can happen that
|
||||
// reading mSignals in Register happens before decrementing mSignals in Signal,
|
||||
// but SubmitWaitingJobs happens before AddWaitingJob. This ordering means
|
||||
// the SyncObject ends up in the signaled state with a task sitting in the
|
||||
// waiting list. To prevent that we check mSignals a second time and submit
|
||||
// again if signals reached zero in the mean time.
|
||||
// We do this instead of holding a mutex around mSignals+mJobs to reduce
|
||||
// lock contention.
|
||||
int32_t signals2 = mSignals;
|
||||
if (signals2 == 0) {
|
||||
SubmitWaitingJobs();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void
|
||||
SyncObject::Signal()
|
||||
{
|
||||
int32_t signals = --mSignals;
|
||||
MOZ_ASSERT(signals >= 0);
|
||||
|
||||
if (signals == 0) {
|
||||
SubmitWaitingJobs();
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
SyncObject::AddWaitingJob(Job* aJob)
|
||||
{
|
||||
// Push (using atomics) the task into the list of waiting tasks.
|
||||
for (;;) {
|
||||
Job* first = mFirstWaitingJob;
|
||||
aJob->mNextWaitingJob = first;
|
||||
if (mFirstWaitingJob.compareExchange(first, aJob)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SyncObject::SubmitWaitingJobs()
|
||||
{
|
||||
// Scheduling the tasks can cause code that modifies <this>'s reference
|
||||
// count to run concurrently, and cause the caller of this function to
|
||||
// be owned by another thread. We need to make sure the reference count
|
||||
// does not reach 0 on another thread before the end of this method, so
|
||||
// hold a strong ref to prevent that!
|
||||
RefPtr<SyncObject> kungFuDeathGrip(this);
|
||||
|
||||
// First atomically swap mFirstWaitingJob and waitingJobs...
|
||||
Job* waitingJobs = nullptr;
|
||||
for (;;) {
|
||||
waitingJobs = mFirstWaitingJob;
|
||||
if (mFirstWaitingJob.compareExchange(waitingJobs, nullptr)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// ... and submit all of the waiting tasks in waitingJob now that they belong
|
||||
// to this thread.
|
||||
while (waitingJobs) {
|
||||
Job* next = waitingJobs->mNextWaitingJob;
|
||||
waitingJobs->mNextWaitingJob = nullptr;
|
||||
JobScheduler::GetQueueForJob(waitingJobs)->SubmitJob(waitingJobs);
|
||||
waitingJobs = next;
|
||||
}
|
||||
}
|
||||
|
||||
bool
|
||||
SyncObject::IsSignaled()
|
||||
{
|
||||
return mSignals == 0;
|
||||
}
|
||||
|
||||
void
|
||||
SyncObject::FreezePrerequisites()
|
||||
{
|
||||
MOZ_ASSERT(mAddedPrerequisites == mNumPrerequisites);
|
||||
}
|
||||
|
||||
void
|
||||
SyncObject::AddPrerequisite(Job* aJob)
|
||||
{
|
||||
MOZ_ASSERT(++mAddedPrerequisites <= mNumPrerequisites);
|
||||
}
|
||||
|
||||
void
|
||||
SyncObject::AddSubsequent(Job* aJob)
|
||||
{
|
||||
}
|
||||
|
||||
WorkerThread::WorkerThread(MultiThreadedJobQueue* aJobQueue)
|
||||
: mQueue(aJobQueue)
|
||||
{
|
||||
aJobQueue->RegisterThread();
|
||||
}
|
||||
|
||||
void
|
||||
WorkerThread::Run()
|
||||
{
|
||||
SetName("gfx worker");
|
||||
|
||||
for (;;) {
|
||||
Job* commands = nullptr;
|
||||
if (!mQueue->WaitForJob(commands)) {
|
||||
mQueue->UnregisterThread();
|
||||
return;
|
||||
}
|
||||
|
||||
JobStatus status = JobScheduler::ProcessJob(commands);
|
||||
|
||||
if (status == JobStatus::Error) {
|
||||
// Don't try to handle errors for now, but that's open to discussions.
|
||||
// I expect errors to be mostly OOM issues.
|
||||
MOZ_CRASH();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} //namespace
|
||||
} //namespace
|
|
@ -0,0 +1,250 @@
|
|||
/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
|
||||
* 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_GFX_TASKSCHEDULER_H_
|
||||
#define MOZILLA_GFX_TASKSCHEDULER_H_
|
||||
|
||||
#include "mozilla/RefPtr.h"
|
||||
#include "mozilla/gfx/Types.h"
|
||||
|
||||
#ifdef WIN32
|
||||
#include "mozilla/gfx/JobScheduler_win32.h"
|
||||
#else
|
||||
#include "mozilla/gfx/JobScheduler_posix.h"
|
||||
#endif
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace mozilla {
|
||||
namespace gfx {
|
||||
|
||||
class MultiThreadedJobQueue;
|
||||
class SyncObject;
|
||||
class WorkerThread;
|
||||
|
||||
class JobScheduler {
|
||||
public:
|
||||
/// Return one of the queues that the drawing worker threads pull from, chosen
|
||||
/// pseudo-randomly.
|
||||
static MultiThreadedJobQueue* GetDrawingQueue()
|
||||
{
|
||||
return sSingleton->mDrawingQueues[
|
||||
sSingleton->mNextQueue++ % sSingleton->mDrawingQueues.size()
|
||||
];
|
||||
}
|
||||
|
||||
/// Return one of the queues that the drawing worker threads pull from with a
|
||||
/// hash to choose the queue.
|
||||
///
|
||||
/// Calling this function several times with the same hash will yield the same queue.
|
||||
static MultiThreadedJobQueue* GetDrawingQueue(uint32_t aHash)
|
||||
{
|
||||
return sSingleton->mDrawingQueues[
|
||||
aHash % sSingleton->mDrawingQueues.size()
|
||||
];
|
||||
}
|
||||
|
||||
/// Return the task queue associated to the worker the task is pinned to if
|
||||
/// the task is pinned to a worker, or a random queue.
|
||||
static MultiThreadedJobQueue* GetQueueForJob(Job* aJob);
|
||||
|
||||
/// Initialize the task scheduler with aNumThreads worker threads for drawing
|
||||
/// and aNumQueues task queues.
|
||||
///
|
||||
/// The number of threads must be superior or equal to the number of queues
|
||||
/// (since for now a worker thread only pulls from one queue).
|
||||
static bool Init(uint32_t aNumThreads, uint32_t aNumQueues);
|
||||
|
||||
/// Shut the scheduler down.
|
||||
///
|
||||
/// This will block until worker threads are joined and deleted.
|
||||
static void ShutDown();
|
||||
|
||||
/// Returns true if there is a successfully initialized JobScheduler singleton.
|
||||
static bool IsEnabled() { return !!sSingleton; }
|
||||
|
||||
/// Submit a task buffer to its associated queue.
|
||||
///
|
||||
/// The caller looses ownership of the task buffer.
|
||||
static void SubmitJob(Job* aJobs);
|
||||
|
||||
/// Process commands until the command buffer needs to block on a sync object,
|
||||
/// completes, yields, or encounters an error.
|
||||
///
|
||||
/// Can be used on any thread. Worker threads basically loop over this, but the
|
||||
/// main thread can also dequeue pending task buffers and process them alongside
|
||||
/// the worker threads if it is about to block until completion anyway.
|
||||
///
|
||||
/// The caller looses ownership of the task buffer.
|
||||
static JobStatus ProcessJob(Job* aJobs);
|
||||
|
||||
protected:
|
||||
static JobScheduler* sSingleton;
|
||||
|
||||
// queues of Job that are ready to be processed
|
||||
std::vector<MultiThreadedJobQueue*> mDrawingQueues;
|
||||
std::vector<WorkerThread*> mWorkerThreads;
|
||||
Atomic<uint32_t> mNextQueue;
|
||||
};
|
||||
|
||||
/// Jobs are not reference-counted because they don't have shared ownership.
|
||||
/// The ownership of tasks can change when they are passed to certain methods
|
||||
/// of JobScheduler and SyncObject. See the docuumentaion of these classes.
|
||||
class Job {
|
||||
public:
|
||||
Job(SyncObject* aStart, SyncObject* aCompletion, WorkerThread* aThread = nullptr);
|
||||
|
||||
virtual ~Job();
|
||||
|
||||
virtual JobStatus Run() = 0;
|
||||
|
||||
/// For use in JobScheduler::SubmitJob. Don't use it anywhere else.
|
||||
//already_AddRefed<SyncObject> GetAndResetStartSync();
|
||||
SyncObject* GetStartSync() { return mStartSync; }
|
||||
|
||||
bool IsPinnedToAThread() const { return !!mPinToThread; }
|
||||
|
||||
WorkerThread* GetWorkerThread() { return mPinToThread; }
|
||||
|
||||
protected:
|
||||
// An intrusive linked list of tasks waiting for a sync object to enter the
|
||||
// signaled state. When the task is not waiting for a sync object, mNextWaitingJob
|
||||
// should be null. This is only accessed from the thread that owns the task.
|
||||
Job* mNextWaitingJob;
|
||||
|
||||
RefPtr<SyncObject> mStartSync;
|
||||
RefPtr<SyncObject> mCompletionSync;
|
||||
WorkerThread* mPinToThread;
|
||||
|
||||
friend class SyncObject;
|
||||
};
|
||||
|
||||
class EventObject;
|
||||
|
||||
/// This task will set an EventObject.
|
||||
///
|
||||
/// Typically used as the final task, so that the main thread can block on the
|
||||
/// corresponfing EventObject until all of the tasks are processed.
|
||||
class SetEventJob : public Job
|
||||
{
|
||||
public:
|
||||
explicit SetEventJob(EventObject* aEvent,
|
||||
SyncObject* aStart, SyncObject* aCompletion = nullptr,
|
||||
WorkerThread* aPinToWorker = nullptr);
|
||||
|
||||
~SetEventJob();
|
||||
|
||||
JobStatus Run() override;
|
||||
|
||||
EventObject* GetEvent() { return mEvent; }
|
||||
|
||||
protected:
|
||||
RefPtr<EventObject> mEvent;
|
||||
};
|
||||
|
||||
/// A synchronization object that can be used to express dependencies and ordering between
|
||||
/// tasks.
|
||||
///
|
||||
/// Jobs can register to SyncObjects in order to asynchronously wait for a signal.
|
||||
/// In practice, Job objects usually start with a sync object (startSyc) and end
|
||||
/// with another one (completionSync).
|
||||
/// a Job never gets processed before its startSync is in the signaled state, and
|
||||
/// signals its completionSync as soon as it finishes. This is how dependencies
|
||||
/// between tasks is expressed.
|
||||
class SyncObject final : public external::AtomicRefCounted<SyncObject> {
|
||||
public:
|
||||
MOZ_DECLARE_REFCOUNTED_TYPENAME(SyncObject)
|
||||
|
||||
/// Create a synchronization object.
|
||||
///
|
||||
/// aNumPrerequisites represents the number of times the object must be signaled
|
||||
/// before actually entering the signaled state (in other words, it means the
|
||||
/// number of dependencies of this sync object).
|
||||
///
|
||||
/// Explicitly specifying the number of prerequisites when creating sync objects
|
||||
/// makes it easy to start scheduling some of the prerequisite tasks while
|
||||
/// creating the others, which is how we typically use the task scheduler.
|
||||
/// Automatically determining the number of prerequisites using Job's constructor
|
||||
/// brings the risk that the sync object enters the signaled state while we
|
||||
/// are still adding prerequisites which is hard to fix without using muteces.
|
||||
explicit SyncObject(uint32_t aNumPrerequisites = 1);
|
||||
|
||||
~SyncObject();
|
||||
|
||||
/// Attempt to register a task.
|
||||
///
|
||||
/// If the sync object is already in the signaled state, the buffer is *not*
|
||||
/// registered and the sync object does not take ownership of the task.
|
||||
/// If the object is not yet in the signaled state, it takes ownership of
|
||||
/// the task and places it in a list of pending tasks.
|
||||
/// Pending tasks will not be processed by the worker thread.
|
||||
/// When the SyncObject reaches the signaled state, it places the pending
|
||||
/// tasks back in the available buffer queue, so that they can be
|
||||
/// scheduled again.
|
||||
///
|
||||
/// Returns true if the SyncOject is not already in the signaled state.
|
||||
/// This means that if this method returns true, the SyncObject has taken
|
||||
/// ownership of the Job.
|
||||
bool Register(Job* aJob);
|
||||
|
||||
/// Signal the SyncObject.
|
||||
///
|
||||
/// This decrements an internal counter. The sync object reaches the signaled
|
||||
/// state when the counter gets to zero.
|
||||
void Signal();
|
||||
|
||||
/// Returns true if mSignals is equal to zero. In other words, returns true
|
||||
/// if all prerequisite tasks have already signaled the sync object.
|
||||
bool IsSignaled();
|
||||
|
||||
/// Asserts that the number of added prerequisites is equal to the number
|
||||
/// specified in the constructor (does nothin in release builds).
|
||||
void FreezePrerequisites();
|
||||
|
||||
private:
|
||||
// Called by Job's constructor
|
||||
void AddSubsequent(Job* aJob);
|
||||
void AddPrerequisite(Job* aJob);
|
||||
|
||||
void AddWaitingJob(Job* aJob);
|
||||
|
||||
void SubmitWaitingJobs();
|
||||
|
||||
Atomic<int32_t> mSignals;
|
||||
Atomic<Job*> mFirstWaitingJob;
|
||||
|
||||
#ifdef DEBUG
|
||||
uint32_t mNumPrerequisites;
|
||||
Atomic<uint32_t> mAddedPrerequisites;
|
||||
#endif
|
||||
|
||||
friend class Job;
|
||||
friend class JobScheduler;
|
||||
};
|
||||
|
||||
/// Base class for worker threads.
|
||||
class WorkerThread
|
||||
{
|
||||
public:
|
||||
static WorkerThread* Create(MultiThreadedJobQueue* aJobQueue);
|
||||
|
||||
virtual ~WorkerThread() {}
|
||||
|
||||
void Run();
|
||||
|
||||
MultiThreadedJobQueue* GetJobQueue() { return mQueue; }
|
||||
|
||||
protected:
|
||||
explicit WorkerThread(MultiThreadedJobQueue* aJobQueue);
|
||||
|
||||
virtual void SetName(const char* aName) {}
|
||||
|
||||
MultiThreadedJobQueue* mQueue;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
} // namespace
|
||||
|
||||
#endif
|
|
@ -0,0 +1,194 @@
|
|||
/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
|
||||
* 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 "JobScheduler.h"
|
||||
#include "mozilla/gfx/Logging.h"
|
||||
|
||||
using namespace std;
|
||||
|
||||
namespace mozilla {
|
||||
namespace gfx {
|
||||
|
||||
void* ThreadCallback(void* threadData);
|
||||
|
||||
class WorkerThreadPosix : public WorkerThread {
|
||||
public:
|
||||
explicit WorkerThreadPosix(MultiThreadedJobQueue* aJobQueue)
|
||||
: WorkerThread(aJobQueue)
|
||||
{
|
||||
pthread_create(&mThread, nullptr, ThreadCallback, static_cast<WorkerThread*>(this));
|
||||
}
|
||||
|
||||
~WorkerThreadPosix()
|
||||
{
|
||||
pthread_join(mThread, nullptr);
|
||||
}
|
||||
|
||||
virtual void SetName(const char*) override
|
||||
{
|
||||
// XXX - temporarily disabled, see bug 1209039
|
||||
//
|
||||
// // Call this from the thread itself because of Mac.
|
||||
//#ifdef XP_MACOSX
|
||||
// pthread_setname_np(aName);
|
||||
//#elif defined(__DragonFly__) || defined(__FreeBSD__) || defined(__OpenBSD__)
|
||||
// pthread_set_name_np(mThread, aName);
|
||||
//#elif defined(__NetBSD__)
|
||||
// pthread_setname_np(mThread, "%s", (void*)aName);
|
||||
//#else
|
||||
// pthread_setname_np(mThread, aName);
|
||||
//#endif
|
||||
}
|
||||
|
||||
protected:
|
||||
pthread_t mThread;
|
||||
};
|
||||
|
||||
void* ThreadCallback(void* threadData)
|
||||
{
|
||||
WorkerThread* thread = static_cast<WorkerThread*>(threadData);
|
||||
thread->Run();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
WorkerThread*
|
||||
WorkerThread::Create(MultiThreadedJobQueue* aJobQueue)
|
||||
{
|
||||
return new WorkerThreadPosix(aJobQueue);
|
||||
}
|
||||
|
||||
MultiThreadedJobQueue::MultiThreadedJobQueue()
|
||||
: mThreadsCount(0)
|
||||
, mShuttingDown(false)
|
||||
{}
|
||||
|
||||
MultiThreadedJobQueue::~MultiThreadedJobQueue()
|
||||
{
|
||||
MOZ_ASSERT(mJobs.empty());
|
||||
}
|
||||
|
||||
bool
|
||||
MultiThreadedJobQueue::WaitForJob(Job*& aOutJob)
|
||||
{
|
||||
return PopJob(aOutJob, BLOCKING);
|
||||
}
|
||||
|
||||
bool
|
||||
MultiThreadedJobQueue::PopJob(Job*& aOutJobs, AccessType aAccess)
|
||||
{
|
||||
for (;;) {
|
||||
MutexAutoLock lock(&mMutex);
|
||||
|
||||
while (aAccess == BLOCKING && !mShuttingDown && mJobs.empty()) {
|
||||
mAvailableCondvar.Wait(&mMutex);
|
||||
}
|
||||
|
||||
if (mShuttingDown) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (mJobs.empty()) {
|
||||
if (aAccess == NON_BLOCKING) {
|
||||
return false;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
Job* task = mJobs.front();
|
||||
MOZ_ASSERT(task);
|
||||
|
||||
mJobs.pop_front();
|
||||
|
||||
aOutJobs = task;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
MultiThreadedJobQueue::SubmitJob(Job* aJobs)
|
||||
{
|
||||
MOZ_ASSERT(aJobs);
|
||||
MutexAutoLock lock(&mMutex);
|
||||
mJobs.push_back(aJobs);
|
||||
mAvailableCondvar.Broadcast();
|
||||
}
|
||||
|
||||
size_t
|
||||
MultiThreadedJobQueue::NumJobs()
|
||||
{
|
||||
MutexAutoLock lock(&mMutex);
|
||||
return mJobs.size();
|
||||
}
|
||||
|
||||
bool
|
||||
MultiThreadedJobQueue::IsEmpty()
|
||||
{
|
||||
MutexAutoLock lock(&mMutex);
|
||||
return mJobs.empty();
|
||||
}
|
||||
|
||||
void
|
||||
MultiThreadedJobQueue::ShutDown()
|
||||
{
|
||||
MutexAutoLock lock(&mMutex);
|
||||
mShuttingDown = true;
|
||||
while (mThreadsCount) {
|
||||
mAvailableCondvar.Broadcast();
|
||||
mShutdownCondvar.Wait(&mMutex);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
MultiThreadedJobQueue::RegisterThread()
|
||||
{
|
||||
mThreadsCount += 1;
|
||||
}
|
||||
|
||||
void
|
||||
MultiThreadedJobQueue::UnregisterThread()
|
||||
{
|
||||
MutexAutoLock lock(&mMutex);
|
||||
mThreadsCount -= 1;
|
||||
if (mThreadsCount == 0) {
|
||||
mShutdownCondvar.Broadcast();
|
||||
}
|
||||
}
|
||||
|
||||
EventObject::EventObject()
|
||||
: mIsSet(false)
|
||||
{}
|
||||
|
||||
EventObject::~EventObject()
|
||||
{}
|
||||
|
||||
bool
|
||||
EventObject::Peak()
|
||||
{
|
||||
MutexAutoLock lock(&mMutex);
|
||||
return mIsSet;
|
||||
}
|
||||
|
||||
void
|
||||
EventObject::Set()
|
||||
{
|
||||
MutexAutoLock lock(&mMutex);
|
||||
if (!mIsSet) {
|
||||
mIsSet = true;
|
||||
mCond.Broadcast();
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
EventObject::Wait()
|
||||
{
|
||||
MutexAutoLock lock(&mMutex);
|
||||
if (mIsSet) {
|
||||
return;
|
||||
}
|
||||
mCond.Wait(&mMutex);
|
||||
}
|
||||
|
||||
} // namespce
|
||||
} // namespce
|
|
@ -0,0 +1,144 @@
|
|||
/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
|
||||
* 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 WIN32
|
||||
#ifndef MOZILLA_GFX_TASKSCHEDULER_POSIX_H_
|
||||
#define MOZILLA_GFX_TASKSCHEDULER_POSIX_H_
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <list>
|
||||
#include <pthread.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#include "mozilla/RefPtr.h"
|
||||
#include "mozilla/DebugOnly.h"
|
||||
#include "mozilla/gfx/CriticalSection.h"
|
||||
|
||||
namespace mozilla {
|
||||
namespace gfx {
|
||||
|
||||
class Job;
|
||||
class PosixCondVar;
|
||||
class WorkerThread;
|
||||
|
||||
typedef mozilla::gfx::CriticalSection Mutex;
|
||||
typedef mozilla::gfx::CriticalSectionAutoEnter MutexAutoLock;
|
||||
|
||||
// posix platforms only!
|
||||
class PosixCondVar {
|
||||
public:
|
||||
PosixCondVar() {
|
||||
DebugOnly<int> err = pthread_cond_init(&mCond, nullptr);
|
||||
MOZ_ASSERT(!err);
|
||||
}
|
||||
|
||||
~PosixCondVar() {
|
||||
DebugOnly<int> err = pthread_cond_destroy(&mCond);
|
||||
MOZ_ASSERT(!err);
|
||||
}
|
||||
|
||||
void Wait(Mutex* aMutex) {
|
||||
DebugOnly<int> err = pthread_cond_wait(&mCond, &aMutex->mMutex);
|
||||
MOZ_ASSERT(!err);
|
||||
}
|
||||
|
||||
void Broadcast() {
|
||||
DebugOnly<int> err = pthread_cond_broadcast(&mCond);
|
||||
MOZ_ASSERT(!err);
|
||||
}
|
||||
|
||||
protected:
|
||||
pthread_cond_t mCond;
|
||||
};
|
||||
|
||||
|
||||
/// A simple and naive multithreaded task queue
|
||||
///
|
||||
/// The public interface of this class must remain identical to its equivalent
|
||||
/// in JobScheduler_win32.h
|
||||
class MultiThreadedJobQueue {
|
||||
public:
|
||||
enum AccessType {
|
||||
BLOCKING,
|
||||
NON_BLOCKING
|
||||
};
|
||||
|
||||
// Producer thread
|
||||
MultiThreadedJobQueue();
|
||||
|
||||
// Producer thread
|
||||
~MultiThreadedJobQueue();
|
||||
|
||||
// Worker threads
|
||||
bool WaitForJob(Job*& aOutJob);
|
||||
|
||||
// Any thread
|
||||
bool PopJob(Job*& aOutJob, AccessType aAccess);
|
||||
|
||||
// Any threads
|
||||
void SubmitJob(Job* aJob);
|
||||
|
||||
// Producer thread
|
||||
void ShutDown();
|
||||
|
||||
// Any thread
|
||||
size_t NumJobs();
|
||||
|
||||
// Any thread
|
||||
bool IsEmpty();
|
||||
|
||||
// Producer thread
|
||||
void RegisterThread();
|
||||
|
||||
// Worker threads
|
||||
void UnregisterThread();
|
||||
|
||||
protected:
|
||||
|
||||
std::list<Job*> mJobs;
|
||||
Mutex mMutex;
|
||||
PosixCondVar mAvailableCondvar;
|
||||
PosixCondVar mShutdownCondvar;
|
||||
int32_t mThreadsCount;
|
||||
bool mShuttingDown;
|
||||
|
||||
friend class WorkerThread;
|
||||
};
|
||||
|
||||
/// An object that a thread can synchronously wait on.
|
||||
/// Usually set by a SetEventJob.
|
||||
class EventObject : public external::AtomicRefCounted<EventObject>
|
||||
{
|
||||
public:
|
||||
MOZ_DECLARE_REFCOUNTED_TYPENAME(EventObject)
|
||||
|
||||
EventObject();
|
||||
|
||||
~EventObject();
|
||||
|
||||
/// Synchronously wait until the event is set.
|
||||
void Wait();
|
||||
|
||||
/// Return true if the event is set, without blocking.
|
||||
bool Peak();
|
||||
|
||||
/// Set the event.
|
||||
void Set();
|
||||
|
||||
protected:
|
||||
Mutex mMutex;
|
||||
PosixCondVar mCond;
|
||||
bool mIsSet;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
} // namespace
|
||||
|
||||
#include "JobScheduler.h"
|
||||
|
||||
#endif
|
||||
#endif
|
|
@ -0,0 +1,143 @@
|
|||
/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
|
||||
* 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 "JobScheduler.h"
|
||||
#include "mozilla/gfx/Logging.h"
|
||||
|
||||
using namespace std;
|
||||
|
||||
namespace mozilla {
|
||||
namespace gfx {
|
||||
|
||||
DWORD __stdcall ThreadCallback(void* threadData);
|
||||
|
||||
class WorkerThreadWin32 : public WorkerThread {
|
||||
public:
|
||||
explicit WorkerThreadWin32(MultiThreadedJobQueue* aJobQueue)
|
||||
: WorkerThread(aJobQueue)
|
||||
{
|
||||
mThread = ::CreateThread(nullptr, 0, ThreadCallback, static_cast<WorkerThread*>(this), 0, nullptr);
|
||||
}
|
||||
|
||||
~WorkerThreadWin32()
|
||||
{
|
||||
::WaitForSingleObject(mThread, INFINITE);
|
||||
::CloseHandle(mThread);
|
||||
}
|
||||
|
||||
protected:
|
||||
HANDLE mThread;
|
||||
};
|
||||
|
||||
DWORD __stdcall ThreadCallback(void* threadData)
|
||||
{
|
||||
WorkerThread* thread = static_cast<WorkerThread*>(threadData);
|
||||
thread->Run();
|
||||
return 0;
|
||||
}
|
||||
|
||||
WorkerThread*
|
||||
WorkerThread::Create(MultiThreadedJobQueue* aJobQueue)
|
||||
{
|
||||
return new WorkerThreadWin32(aJobQueue);
|
||||
}
|
||||
|
||||
bool
|
||||
MultiThreadedJobQueue::PopJob(Job*& aOutJob, AccessType aAccess)
|
||||
{
|
||||
for (;;) {
|
||||
while (aAccess == BLOCKING && mJobs.empty()) {
|
||||
{
|
||||
CriticalSectionAutoEnter lock(&mSection);
|
||||
if (mShuttingDown) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
HANDLE handles[] = { mAvailableEvent, mShutdownEvent };
|
||||
::WaitForMultipleObjects(2, handles, FALSE, INFINITE);
|
||||
}
|
||||
|
||||
CriticalSectionAutoEnter lock(&mSection);
|
||||
|
||||
if (mShuttingDown) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (mJobs.empty()) {
|
||||
if (aAccess == NON_BLOCKING) {
|
||||
return false;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
Job* task = mJobs.front();
|
||||
MOZ_ASSERT(task);
|
||||
|
||||
mJobs.pop_front();
|
||||
|
||||
if (mJobs.empty()) {
|
||||
::ResetEvent(mAvailableEvent);
|
||||
}
|
||||
|
||||
aOutJob = task;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
MultiThreadedJobQueue::SubmitJob(Job* aJob)
|
||||
{
|
||||
MOZ_ASSERT(aJob);
|
||||
CriticalSectionAutoEnter lock(&mSection);
|
||||
mJobs.push_back(aJob);
|
||||
::SetEvent(mAvailableEvent);
|
||||
}
|
||||
|
||||
void
|
||||
MultiThreadedJobQueue::ShutDown()
|
||||
{
|
||||
{
|
||||
CriticalSectionAutoEnter lock(&mSection);
|
||||
mShuttingDown = true;
|
||||
}
|
||||
while (mThreadsCount) {
|
||||
::SetEvent(mAvailableEvent);
|
||||
::WaitForSingleObject(mShutdownEvent, INFINITE);
|
||||
}
|
||||
}
|
||||
|
||||
size_t
|
||||
MultiThreadedJobQueue::NumJobs()
|
||||
{
|
||||
CriticalSectionAutoEnter lock(&mSection);
|
||||
return mJobs.size();
|
||||
}
|
||||
|
||||
bool
|
||||
MultiThreadedJobQueue::IsEmpty()
|
||||
{
|
||||
CriticalSectionAutoEnter lock(&mSection);
|
||||
return mJobs.empty();
|
||||
}
|
||||
|
||||
void
|
||||
MultiThreadedJobQueue::RegisterThread()
|
||||
{
|
||||
mThreadsCount += 1;
|
||||
}
|
||||
|
||||
void
|
||||
MultiThreadedJobQueue::UnregisterThread()
|
||||
{
|
||||
CriticalSectionAutoEnter lock(&mSection);
|
||||
mThreadsCount -= 1;
|
||||
if (mThreadsCount == 0) {
|
||||
::SetEvent(mShutdownEvent);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace
|
|
@ -0,0 +1,98 @@
|
|||
/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
|
||||
* 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/. */
|
||||
|
||||
#ifdef WIN32
|
||||
#ifndef MOZILLA_GFX_TASKSCHEDULER_WIN32_H_
|
||||
#define MOZILLA_GFX_TASKSCHEDULER_WIN32_H_
|
||||
|
||||
#include <windows.h>
|
||||
#include <list>
|
||||
|
||||
#include "mozilla/RefPtr.h"
|
||||
#include "mozilla/gfx/CriticalSection.h"
|
||||
|
||||
namespace mozilla {
|
||||
namespace gfx {
|
||||
|
||||
class WorkerThread;
|
||||
class Job;
|
||||
|
||||
// The public interface of this class must remain identical to its equivalent
|
||||
// in JobScheduler_posix.h
|
||||
class MultiThreadedJobQueue {
|
||||
public:
|
||||
enum AccessType {
|
||||
BLOCKING,
|
||||
NON_BLOCKING
|
||||
};
|
||||
|
||||
MultiThreadedJobQueue()
|
||||
: mThreadsCount(0)
|
||||
, mShuttingDown(false)
|
||||
{
|
||||
mAvailableEvent = ::CreateEventW(nullptr, TRUE, FALSE, nullptr);
|
||||
mShutdownEvent = ::CreateEventW(nullptr, TRUE, FALSE, nullptr);
|
||||
}
|
||||
|
||||
~MultiThreadedJobQueue()
|
||||
{
|
||||
::CloseHandle(mAvailableEvent);
|
||||
::CloseHandle(mShutdownEvent);
|
||||
}
|
||||
|
||||
bool WaitForJob(Job*& aOutJob) { return PopJob(aOutJob, BLOCKING); }
|
||||
|
||||
bool PopJob(Job*& aOutJob, AccessType aAccess);
|
||||
|
||||
void SubmitJob(Job* aJob);
|
||||
|
||||
void ShutDown();
|
||||
|
||||
size_t NumJobs();
|
||||
|
||||
bool IsEmpty();
|
||||
|
||||
void RegisterThread();
|
||||
|
||||
void UnregisterThread();
|
||||
|
||||
protected:
|
||||
std::list<Job*> mJobs;
|
||||
CriticalSection mSection;
|
||||
HANDLE mAvailableEvent;
|
||||
HANDLE mShutdownEvent;
|
||||
int32_t mThreadsCount;
|
||||
bool mShuttingDown;
|
||||
|
||||
friend class WorkerThread;
|
||||
};
|
||||
|
||||
|
||||
// The public interface of this class must remain identical to its equivalent
|
||||
// in JobScheduler_posix.h
|
||||
class EventObject : public external::AtomicRefCounted<EventObject>
|
||||
{
|
||||
public:
|
||||
MOZ_DECLARE_REFCOUNTED_TYPENAME(EventObject)
|
||||
|
||||
EventObject() { mEvent = ::CreateEventW(nullptr, TRUE, FALSE, nullptr); }
|
||||
|
||||
~EventObject() { ::CloseHandle(mEvent); }
|
||||
|
||||
void Wait() { ::WaitForSingleObject(mEvent, INFINITE); }
|
||||
|
||||
bool Peak() { return ::WaitForSingleObject(mEvent, 0) == WAIT_OBJECT_0; }
|
||||
|
||||
void Set() { ::SetEvent(mEvent); }
|
||||
protected:
|
||||
// TODO: it's expensive to create events so we should try to reuse them
|
||||
HANDLE mEvent;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
} // namespace
|
||||
|
||||
#endif
|
||||
#endif
|
|
@ -289,6 +289,13 @@ struct GradientStop
|
|||
Color color;
|
||||
};
|
||||
|
||||
enum class JobStatus {
|
||||
Complete,
|
||||
Wait,
|
||||
Yield,
|
||||
Error
|
||||
};
|
||||
|
||||
} // namespace gfx
|
||||
} // namespace mozilla
|
||||
|
||||
|
|
|
@ -20,11 +20,16 @@ EXPORTS.mozilla.gfx += [
|
|||
'Blur.h',
|
||||
'BorrowedContext.h',
|
||||
'Coord.h',
|
||||
'CriticalSection.h',
|
||||
'DataSurfaceHelpers.h',
|
||||
'DrawTargetTiled.h',
|
||||
'Filters.h',
|
||||
'Helpers.h',
|
||||
'HelpersCairo.h',
|
||||
'IterableArena.h',
|
||||
'JobScheduler.h',
|
||||
'JobScheduler_posix.h',
|
||||
'JobScheduler_win32.h',
|
||||
'Logging.h',
|
||||
'Matrix.h',
|
||||
'NumericTools.h',
|
||||
|
@ -60,6 +65,7 @@ elif CONFIG['MOZ_WIDGET_TOOLKIT'] == 'windows':
|
|||
'DrawTargetD2D1.cpp',
|
||||
'ExtendInputEffectD2D1.cpp',
|
||||
'FilterNodeD2D1.cpp',
|
||||
'JobScheduler_win32.cpp',
|
||||
'PathD2D.cpp',
|
||||
'RadialGradientEffectD2D1.cpp',
|
||||
'ScaledFontDWrite.cpp',
|
||||
|
@ -70,6 +76,11 @@ elif CONFIG['MOZ_WIDGET_TOOLKIT'] == 'windows':
|
|||
]
|
||||
DEFINES['WIN32'] = True
|
||||
|
||||
if CONFIG['MOZ_WIDGET_TOOLKIT'] != 'windows':
|
||||
SOURCES += [
|
||||
'JobScheduler_posix.cpp',
|
||||
]
|
||||
|
||||
if CONFIG['MOZ_ENABLE_SKIA']:
|
||||
UNIFIED_SOURCES += [
|
||||
'convolver.cpp',
|
||||
|
@ -118,6 +129,7 @@ UNIFIED_SOURCES += [
|
|||
'DataSourceSurface.cpp',
|
||||
'DataSurfaceHelpers.cpp',
|
||||
'DrawEventRecorder.cpp',
|
||||
'DrawingJob.cpp',
|
||||
'DrawTarget.cpp',
|
||||
'DrawTargetCairo.cpp',
|
||||
'DrawTargetCapture.cpp',
|
||||
|
@ -129,6 +141,7 @@ UNIFIED_SOURCES += [
|
|||
'FilterProcessing.cpp',
|
||||
'FilterProcessingScalar.cpp',
|
||||
'ImageScaling.cpp',
|
||||
'JobScheduler.cpp',
|
||||
'Matrix.cpp',
|
||||
'Path.cpp',
|
||||
'PathCairo.cpp',
|
||||
|
|
|
@ -7,16 +7,21 @@
|
|||
#define GFX_LAYERSTYPES_H
|
||||
|
||||
#include <stdint.h> // for uint32_t
|
||||
#include "mozilla/gfx/Point.h" // for IntPoint
|
||||
#include "nsRegion.h"
|
||||
|
||||
#include "mozilla/TypedEnumBits.h"
|
||||
|
||||
#ifdef MOZ_WIDGET_GONK
|
||||
#include <utils/RefBase.h>
|
||||
#if ANDROID_VERSION >= 21
|
||||
#include <utils/NativeHandle.h>
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#include "mozilla/gfx/Point.h" // for IntPoint
|
||||
#include "mozilla/TypedEnumBits.h"
|
||||
#include "nsRegion.h"
|
||||
|
||||
#include <stdio.h> // FILE
|
||||
#include "mozilla/Logging.h" // for PR_LOG
|
||||
|
||||
#ifndef MOZ_LAYERS_HAVE_LOG
|
||||
# define MOZ_LAYERS_HAVE_LOG
|
||||
#endif
|
||||
|
@ -110,6 +115,14 @@ struct LayerRenderState {
|
|||
|
||||
void SetOverlayId(const int32_t& aId)
|
||||
{ mOverlayId = aId; }
|
||||
|
||||
android::GraphicBuffer* GetGrallocBuffer() const
|
||||
{ return mSurface.get(); }
|
||||
|
||||
#if ANDROID_VERSION >= 21
|
||||
android::NativeHandle* GetSidebandStream() const
|
||||
{ return mSidebandStream.get(); }
|
||||
#endif
|
||||
#endif
|
||||
|
||||
void SetOffset(const nsIntPoint& aOffset)
|
||||
|
@ -133,6 +146,9 @@ struct LayerRenderState {
|
|||
// size of mSurface
|
||||
gfx::IntSize mSize;
|
||||
TextureHost* mTexture;
|
||||
#if ANDROID_VERSION >= 21
|
||||
android::sp<android::NativeHandle> mSidebandStream;
|
||||
#endif
|
||||
#endif
|
||||
};
|
||||
|
||||
|
|
|
@ -417,13 +417,11 @@ ClientTiledPaintedLayer::RenderLayer()
|
|||
}
|
||||
|
||||
if (!mContentClient) {
|
||||
#if defined(MOZ_B2G) || defined(XP_MACOSX)
|
||||
if (mCreationHint == LayerManager::NONE &&
|
||||
SingleTiledContentClient::ClientSupportsLayerSize(layerSize, ClientManager())) {
|
||||
SingleTiledContentClient::ClientSupportsLayerSize(layerSize, ClientManager()) &&
|
||||
gfxPrefs::LayersSingleTileEnabled()) {
|
||||
mContentClient = new SingleTiledContentClient(this, ClientManager());
|
||||
} else
|
||||
#endif
|
||||
{
|
||||
} else {
|
||||
mContentClient = new MultiTiledContentClient(this, ClientManager());
|
||||
}
|
||||
|
||||
|
@ -567,7 +565,6 @@ ClientTiledPaintedLayer::RenderLayer()
|
|||
bool
|
||||
ClientTiledPaintedLayer::IsOptimizedFor(LayerManager::PaintedLayerCreationHint aHint)
|
||||
{
|
||||
#if defined(MOZ_B2G) || defined(XP_MACOSX)
|
||||
// The only creation hint is whether the layer is scrollable or not, and this
|
||||
// is only respected on B2G and OSX, where it's used to determine whether to
|
||||
// use a tiled content client or not.
|
||||
|
@ -575,9 +572,6 @@ ClientTiledPaintedLayer::IsOptimizedFor(LayerManager::PaintedLayerCreationHint a
|
|||
// large, scrollable layers, so we want the layer to be recreated in this
|
||||
// situation.
|
||||
return aHint == GetCreationHint();
|
||||
#else
|
||||
return true;
|
||||
#endif
|
||||
}
|
||||
|
||||
void
|
||||
|
|
|
@ -0,0 +1,188 @@
|
|||
/* vim:set ts=2 sw=2 sts=2 et: */
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/
|
||||
*/
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
#include "gmock/gmock.h"
|
||||
|
||||
#include "mozilla/gfx/IterableArena.h"
|
||||
#include <string>
|
||||
|
||||
using namespace mozilla;
|
||||
using namespace mozilla::gfx;
|
||||
|
||||
#ifdef A
|
||||
#undef A
|
||||
#endif
|
||||
|
||||
#ifdef B
|
||||
#undef B
|
||||
#endif
|
||||
|
||||
// to avoid having symbols that collide easily like A and B in the global namespace
|
||||
namespace test_arena {
|
||||
|
||||
class A;
|
||||
class B;
|
||||
|
||||
class Base {
|
||||
public:
|
||||
virtual ~Base() {}
|
||||
virtual A* AsA() { return nullptr; }
|
||||
virtual B* AsB() { return nullptr; }
|
||||
};
|
||||
|
||||
static int sDtorItemA = 0;
|
||||
static int sDtorItemB = 0;
|
||||
|
||||
class A : public Base {
|
||||
public:
|
||||
virtual A* AsA() override { return this; }
|
||||
|
||||
explicit A(uint64_t val) : mVal(val) {}
|
||||
~A() { ++sDtorItemA; }
|
||||
|
||||
uint64_t mVal;
|
||||
};
|
||||
|
||||
class B : public Base {
|
||||
public:
|
||||
virtual B* AsB() override { return this; }
|
||||
|
||||
explicit B(const string& str) : mVal(str) {}
|
||||
~B() { ++sDtorItemB; }
|
||||
|
||||
std::string mVal;
|
||||
};
|
||||
|
||||
struct BigStruct {
|
||||
uint64_t mVal;
|
||||
uint8_t data[120];
|
||||
|
||||
explicit BigStruct(uint64_t val) : mVal(val) {}
|
||||
};
|
||||
|
||||
void TestArenaAlloc(IterableArena::ArenaType aType)
|
||||
{
|
||||
sDtorItemA = 0;
|
||||
sDtorItemB = 0;
|
||||
IterableArena arena(aType, 256);
|
||||
|
||||
// An empty arena has no items to iterate over.
|
||||
{
|
||||
int iterations = 0;
|
||||
arena.ForEach([&](void* item){
|
||||
iterations++;
|
||||
});
|
||||
ASSERT_EQ(iterations, 0);
|
||||
}
|
||||
|
||||
auto a1 = arena.Alloc<A>(42);
|
||||
auto b1 = arena.Alloc<B>("Obladi oblada");
|
||||
auto a2 = arena.Alloc<A>(1337);
|
||||
auto b2 = arena.Alloc<B>("Yellow submarine");
|
||||
auto b3 = arena.Alloc<B>("She's got a ticket to ride");
|
||||
|
||||
// Alloc returns a non-negative offset if the allocation succeeded.
|
||||
ASSERT_TRUE(a1 >= 0);
|
||||
ASSERT_TRUE(a2 >= 0);
|
||||
ASSERT_TRUE(b1 >= 0);
|
||||
ASSERT_TRUE(b2 >= 0);
|
||||
ASSERT_TRUE(b3 >= 0);
|
||||
|
||||
ASSERT_TRUE(arena.GetStorage(a1) != nullptr);
|
||||
ASSERT_TRUE(arena.GetStorage(a2) != nullptr);
|
||||
ASSERT_TRUE(arena.GetStorage(b1) != nullptr);
|
||||
ASSERT_TRUE(arena.GetStorage(b2) != nullptr);
|
||||
ASSERT_TRUE(arena.GetStorage(b3) != nullptr);
|
||||
|
||||
ASSERT_TRUE(((Base*)arena.GetStorage(a1))->AsA() != nullptr);
|
||||
ASSERT_TRUE(((Base*)arena.GetStorage(a2))->AsA() != nullptr);
|
||||
|
||||
ASSERT_TRUE(((Base*)arena.GetStorage(b1))->AsB() != nullptr);
|
||||
ASSERT_TRUE(((Base*)arena.GetStorage(b2))->AsB() != nullptr);
|
||||
ASSERT_TRUE(((Base*)arena.GetStorage(b3))->AsB() != nullptr);
|
||||
|
||||
ASSERT_EQ(((Base*)arena.GetStorage(a1))->AsA()->mVal, (uint64_t)42);
|
||||
ASSERT_EQ(((Base*)arena.GetStorage(a2))->AsA()->mVal, (uint64_t)1337);
|
||||
|
||||
ASSERT_EQ(((Base*)arena.GetStorage(b1))->AsB()->mVal, std::string("Obladi oblada"));
|
||||
ASSERT_EQ(((Base*)arena.GetStorage(b2))->AsB()->mVal, std::string("Yellow submarine"));
|
||||
ASSERT_EQ(((Base*)arena.GetStorage(b3))->AsB()->mVal, std::string("She's got a ticket to ride"));
|
||||
|
||||
{
|
||||
int iterations = 0;
|
||||
arena.ForEach([&](void* item){
|
||||
iterations++;
|
||||
});
|
||||
ASSERT_EQ(iterations, 5);
|
||||
}
|
||||
|
||||
// Typically, running the destructors of the elements in the arena will is done
|
||||
// manually like this:
|
||||
arena.ForEach([](void* item){
|
||||
((Base*)item)->~Base();
|
||||
});
|
||||
arena.Clear();
|
||||
ASSERT_EQ(sDtorItemA, 2);
|
||||
ASSERT_EQ(sDtorItemB, 3);
|
||||
|
||||
// An empty arena has no items to iterate over (we just cleared it).
|
||||
{
|
||||
int iterations = 0;
|
||||
arena.ForEach([&](void* item){
|
||||
iterations++;
|
||||
});
|
||||
ASSERT_EQ(iterations, 0);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void TestArenaLimit(IterableArena::ArenaType aType, bool aShouldReachLimit)
|
||||
{
|
||||
IterableArena arena(aType, 128);
|
||||
|
||||
// A non-growable arena should return a negative offset when running out
|
||||
// of space, without crashing.
|
||||
// We should not run out of space with a growable arena (unless the os is
|
||||
// running out of memory but this isn't expected for this test).
|
||||
bool reachedLimit = false;
|
||||
for (int i = 0; i < 100; ++i) {
|
||||
auto offset = arena.Alloc<A>(42);
|
||||
if (offset < 0) {
|
||||
reachedLimit = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
ASSERT_EQ(reachedLimit, aShouldReachLimit);
|
||||
}
|
||||
|
||||
} // namespace test_arena
|
||||
|
||||
using namespace test_arena;
|
||||
|
||||
TEST(Moz2D, FixedArena) {
|
||||
TestArenaAlloc(IterableArena::FIXED_SIZE);
|
||||
TestArenaLimit(IterableArena::FIXED_SIZE, true);
|
||||
}
|
||||
|
||||
TEST(Moz2D, GrowableArena) {
|
||||
TestArenaAlloc(IterableArena::GROWABLE);
|
||||
TestArenaLimit(IterableArena::GROWABLE, false);
|
||||
|
||||
IterableArena arena(IterableArena::GROWABLE, 16);
|
||||
// sizeof(BigStruct) is more than twice the initial capacity, make sure that
|
||||
// this doesn't blow everything up, since the arena doubles its storage size each
|
||||
// time it grows (until it finds a size that fits).
|
||||
auto a = arena.Alloc<BigStruct>(1);
|
||||
auto b = arena.Alloc<BigStruct>(2);
|
||||
auto c = arena.Alloc<BigStruct>(3);
|
||||
|
||||
// Offsets should also still point to the appropriate values after reallocation.
|
||||
ASSERT_EQ(((BigStruct*)arena.GetStorage(a))->mVal, (uint64_t)1);
|
||||
ASSERT_EQ(((BigStruct*)arena.GetStorage(b))->mVal, (uint64_t)2);
|
||||
ASSERT_EQ(((BigStruct*)arena.GetStorage(c))->mVal, (uint64_t)3);
|
||||
|
||||
arena.Clear();
|
||||
}
|
|
@ -0,0 +1,247 @@
|
|||
/* vim:set ts=2 sw=2 sts=2 et: */
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/
|
||||
*/
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
#include "gmock/gmock.h"
|
||||
|
||||
#include "mozilla/gfx/JobScheduler.h"
|
||||
|
||||
#ifndef WIN32
|
||||
#include <pthread.h>
|
||||
#include <sched.h>
|
||||
#endif
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <time.h>
|
||||
|
||||
namespace test_scheduler {
|
||||
|
||||
using namespace mozilla::gfx;
|
||||
using namespace mozilla;
|
||||
|
||||
// Artificially cause threads to yield randomly in an attempt to make racy
|
||||
// things more apparent (if any).
|
||||
void MaybeYieldThread()
|
||||
{
|
||||
#ifndef WIN32
|
||||
if (rand() % 5 == 0) {
|
||||
sched_yield();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
/// Used by the TestCommand to check that tasks are processed in the right order.
|
||||
struct SanityChecker {
|
||||
std::vector<uint64_t> mAdvancements;
|
||||
mozilla::gfx::CriticalSection mSection;
|
||||
|
||||
explicit SanityChecker(uint64_t aNumCmdBuffers)
|
||||
{
|
||||
for (uint32_t i = 0; i < aNumCmdBuffers; ++i) {
|
||||
mAdvancements.push_back(0);
|
||||
}
|
||||
}
|
||||
|
||||
virtual void Check(uint64_t aJobId, uint64_t aCmdId)
|
||||
{
|
||||
MaybeYieldThread();
|
||||
CriticalSectionAutoEnter lock(&mSection);
|
||||
ASSERT_EQ(mAdvancements[aJobId], aCmdId-1);
|
||||
mAdvancements[aJobId] = aCmdId;
|
||||
}
|
||||
};
|
||||
|
||||
/// Run checks that are specific to TestSchulerJoin.
|
||||
struct JoinTestSanityCheck : public SanityChecker {
|
||||
bool mSpecialJobHasRun;
|
||||
|
||||
explicit JoinTestSanityCheck(uint64_t aNumCmdBuffers)
|
||||
: SanityChecker(aNumCmdBuffers)
|
||||
, mSpecialJobHasRun(false)
|
||||
{}
|
||||
|
||||
virtual void Check(uint64_t aJobId, uint64_t aCmdId) override
|
||||
{
|
||||
// Job 0 is the special task executed when everything is joined after task 1
|
||||
if (aCmdId == 0) {
|
||||
ASSERT_FALSE(mSpecialJobHasRun);
|
||||
mSpecialJobHasRun = true;
|
||||
for (auto advancement : mAdvancements) {
|
||||
// Because of the synchronization point (beforeFilter), all
|
||||
// task buffers should have run task 1 when task 0 is run.
|
||||
ASSERT_EQ(advancement, (uint32_t)1);
|
||||
}
|
||||
} else {
|
||||
// This check does not apply to task 0.
|
||||
SanityChecker::Check(aJobId, aCmdId);
|
||||
}
|
||||
|
||||
if (aCmdId == 2) {
|
||||
ASSERT_TRUE(mSpecialJobHasRun);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
class TestJob : public Job
|
||||
{
|
||||
public:
|
||||
TestJob(uint64_t aCmdId, uint64_t aJobId, SanityChecker* aChecker,
|
||||
SyncObject* aStart, SyncObject* aCompletion)
|
||||
: Job(aStart, aCompletion, nullptr)
|
||||
, mCmdId(aCmdId)
|
||||
, mCmdBufferId(aJobId)
|
||||
, mSanityChecker(aChecker)
|
||||
{}
|
||||
|
||||
JobStatus Run()
|
||||
{
|
||||
MaybeYieldThread();
|
||||
mSanityChecker->Check(mCmdBufferId, mCmdId);
|
||||
MaybeYieldThread();
|
||||
return JobStatus::Complete;
|
||||
}
|
||||
|
||||
uint64_t mCmdId;
|
||||
uint64_t mCmdBufferId;
|
||||
SanityChecker* mSanityChecker;
|
||||
};
|
||||
|
||||
/// This test creates aNumCmdBuffers task buffers with sync objects set up
|
||||
/// so that all tasks will join after command 5 before a task buffer runs
|
||||
/// a special task (task 0) after which all task buffers fork again.
|
||||
/// This simulates the kind of scenario where all tiles must join at
|
||||
/// a certain point to execute, say, a filter, and fork again after the filter
|
||||
/// has been processed.
|
||||
/// The main thread is only blocked when waiting for the completion of the entire
|
||||
/// task stream (it doesn't have to wait at the filter's sync points to orchestrate it).
|
||||
void TestSchedulerJoin(uint32_t aNumThreads, uint32_t aNumCmdBuffers)
|
||||
{
|
||||
JoinTestSanityCheck check(aNumCmdBuffers);
|
||||
|
||||
RefPtr<SyncObject> beforeFilter = new SyncObject(aNumCmdBuffers);
|
||||
RefPtr<SyncObject> afterFilter = new SyncObject();
|
||||
RefPtr<SyncObject> completion = new SyncObject(aNumCmdBuffers);
|
||||
|
||||
|
||||
for (uint32_t i = 0; i < aNumCmdBuffers; ++i) {
|
||||
Job* t1 = new TestJob(1, i, &check, nullptr, beforeFilter);
|
||||
JobScheduler::SubmitJob(t1);
|
||||
MaybeYieldThread();
|
||||
}
|
||||
beforeFilter->FreezePrerequisites();
|
||||
|
||||
// This task buffer is executed when all other tasks have joined after task 1
|
||||
JobScheduler::SubmitJob(
|
||||
new TestJob(0, 0, &check, beforeFilter, afterFilter)
|
||||
);
|
||||
afterFilter->FreezePrerequisites();
|
||||
|
||||
for (uint32_t i = 0; i < aNumCmdBuffers; ++i) {
|
||||
Job* t2 = new TestJob(2, i, &check, afterFilter, completion);
|
||||
JobScheduler::SubmitJob(t2);
|
||||
MaybeYieldThread();
|
||||
}
|
||||
completion->FreezePrerequisites();
|
||||
|
||||
RefPtr<EventObject> waitForCompletion = new EventObject();
|
||||
auto evtJob = new SetEventJob(waitForCompletion, completion);
|
||||
JobScheduler::SubmitJob(evtJob);
|
||||
|
||||
MaybeYieldThread();
|
||||
|
||||
waitForCompletion->Wait();
|
||||
|
||||
MaybeYieldThread();
|
||||
|
||||
for (auto advancement : check.mAdvancements) {
|
||||
ASSERT_TRUE(advancement == 2);
|
||||
}
|
||||
}
|
||||
|
||||
/// This test creates several chains of 10 task, tasks of a given chain are executed
|
||||
/// sequentially, and chains are exectuted in parallel.
|
||||
/// This simulates the typical scenario where we want to process sequences of drawing
|
||||
/// commands for several tiles in parallel.
|
||||
void TestSchedulerChain(uint32_t aNumThreads, uint32_t aNumCmdBuffers)
|
||||
{
|
||||
SanityChecker check(aNumCmdBuffers);
|
||||
|
||||
RefPtr<SyncObject> completion = new SyncObject(aNumCmdBuffers);
|
||||
|
||||
uint32_t numJobs = 10;
|
||||
|
||||
for (uint32_t i = 0; i < aNumCmdBuffers; ++i) {
|
||||
|
||||
std::vector<RefPtr<SyncObject>> syncs;
|
||||
std::vector<Job*> tasks;
|
||||
syncs.reserve(numJobs);
|
||||
tasks.reserve(numJobs);
|
||||
|
||||
for (uint32_t t = 0; t < numJobs-1; ++t) {
|
||||
syncs.push_back(new SyncObject());
|
||||
tasks.push_back(new TestJob(t+1, i, &check, t == 0 ? nullptr
|
||||
: syncs[t-1].get(),
|
||||
syncs[t]));
|
||||
syncs.back()->FreezePrerequisites();
|
||||
}
|
||||
|
||||
tasks.push_back(new TestJob(numJobs, i, &check, syncs.back(), completion));
|
||||
|
||||
if (i % 2 == 0) {
|
||||
// submit half of the tasks in order
|
||||
for (Job* task : tasks) {
|
||||
JobScheduler::SubmitJob(task);
|
||||
MaybeYieldThread();
|
||||
}
|
||||
} else {
|
||||
// ... and submit the other half in reverse order
|
||||
for (int32_t reverse = numJobs-1; reverse >= 0; --reverse) {
|
||||
JobScheduler::SubmitJob(tasks[reverse]);
|
||||
MaybeYieldThread();
|
||||
}
|
||||
}
|
||||
}
|
||||
completion->FreezePrerequisites();
|
||||
|
||||
RefPtr<EventObject> waitForCompletion = new EventObject();
|
||||
auto evtJob = new SetEventJob(waitForCompletion, completion);
|
||||
JobScheduler::SubmitJob(evtJob);
|
||||
|
||||
MaybeYieldThread();
|
||||
|
||||
waitForCompletion->Wait();
|
||||
|
||||
for (auto advancement : check.mAdvancements) {
|
||||
ASSERT_TRUE(advancement == numJobs);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace test_scheduler
|
||||
|
||||
TEST(Moz2D, JobScheduler_Join) {
|
||||
srand(time(nullptr));
|
||||
for (uint32_t threads = 1; threads < 8; ++threads) {
|
||||
for (uint32_t queues = 1; queues < threads; ++queues) {
|
||||
for (uint32_t buffers = 1; buffers < 100; buffers += 3) {
|
||||
mozilla::gfx::JobScheduler::Init(threads, queues);
|
||||
test_scheduler::TestSchedulerJoin(threads, buffers);
|
||||
mozilla::gfx::JobScheduler::ShutDown();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST(Moz2D, JobScheduler_Chain) {
|
||||
srand(time(nullptr));
|
||||
for (uint32_t threads = 1; threads < 8; ++threads) {
|
||||
for (uint32_t queues = 1; queues < threads; ++queues) {
|
||||
for (uint32_t buffers = 1; buffers < 100; buffers += 3) {
|
||||
mozilla::gfx::JobScheduler::Init(threads, queues);
|
||||
test_scheduler::TestSchedulerChain(threads, buffers);
|
||||
mozilla::gfx::JobScheduler::ShutDown();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -8,11 +8,13 @@ UNIFIED_SOURCES += [
|
|||
'gfxSurfaceRefCountTest.cpp',
|
||||
# Disabled on suspicion of causing bug 904227
|
||||
#'gfxWordCacheTest.cpp',
|
||||
'TestArena.cpp',
|
||||
'TestBufferRotation.cpp',
|
||||
'TestColorNames.cpp',
|
||||
'TestCompositor.cpp',
|
||||
'TestGfxPrefs.cpp',
|
||||
'TestGfxWidgets.cpp',
|
||||
'TestJobScheduler.cpp',
|
||||
'TestLayers.cpp',
|
||||
'TestMoz2D.cpp',
|
||||
'TestQcms.cpp',
|
||||
|
|
|
@ -4,5 +4,5 @@ fuzzy-if(winWidget,175,443) == 611498-1.html 611498-ref.html
|
|||
skip-if(B2G) fuzzy-if(Android&&AndroidVersion>=15,8,1000) == 709477-1.html 709477-1-ref.html # bug 773482
|
||||
skip-if(!asyncPan) == 1086723.html 1086723-ref.html
|
||||
== 853889-1.html 853889-1-ref.html
|
||||
== 1143303-1.svg pass.svg
|
||||
fuzzy(100,30) == 1149923.html 1149923-ref.html # use fuzzy due to few distorted pixels caused by border-radius
|
||||
skip-if(Android) == 1143303-1.svg pass.svg
|
||||
fuzzy(100,30) == 1149923.html 1149923-ref.html # use fuzzy due to few distorted pixels caused by border-radius
|
||||
|
|
|
@ -100,13 +100,7 @@ gfxAndroidPlatform::gfxAndroidPlatform()
|
|||
|
||||
RegisterStrongMemoryReporter(new FreetypeReporter());
|
||||
|
||||
nsCOMPtr<nsIScreenManager> screenMgr = do_GetService("@mozilla.org/gfx/screenmanager;1");
|
||||
nsCOMPtr<nsIScreen> screen;
|
||||
screenMgr->GetPrimaryScreen(getter_AddRefs(screen));
|
||||
mScreenDepth = 24;
|
||||
screen->GetColorDepth(&mScreenDepth);
|
||||
|
||||
mOffscreenFormat = mScreenDepth == 16
|
||||
mOffscreenFormat = GetScreenDepth() == 16
|
||||
? gfxImageFormat::RGB16_565
|
||||
: gfxImageFormat::RGB24;
|
||||
|
||||
|
@ -418,12 +412,6 @@ gfxAndroidPlatform::RequiresLinearZoom()
|
|||
return gfxPlatform::RequiresLinearZoom();
|
||||
}
|
||||
|
||||
int
|
||||
gfxAndroidPlatform::GetScreenDepth() const
|
||||
{
|
||||
return mScreenDepth;
|
||||
}
|
||||
|
||||
bool
|
||||
gfxAndroidPlatform::UseAcceleratedSkiaCanvas()
|
||||
{
|
||||
|
|
|
@ -80,8 +80,6 @@ public:
|
|||
|
||||
FT_Library GetFTLibrary();
|
||||
|
||||
virtual int GetScreenDepth() const;
|
||||
|
||||
virtual bool CanRenderContentToDataSurface() const override {
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -52,6 +52,7 @@
|
|||
#include "gfxGraphiteShaper.h"
|
||||
#include "gfx2DGlue.h"
|
||||
#include "gfxGradientCache.h"
|
||||
#include "gfxUtils.h" // for NextPowerOfTwo
|
||||
|
||||
#include "nsUnicodeRange.h"
|
||||
#include "nsServiceManagerUtils.h"
|
||||
|
@ -394,6 +395,7 @@ gfxPlatform::gfxPlatform()
|
|||
, mAzureCanvasBackendCollector(this, &gfxPlatform::GetAzureBackendInfo)
|
||||
, mApzSupportCollector(this, &gfxPlatform::GetApzSupportInfo)
|
||||
, mCompositorBackend(layers::LayersBackend::LAYERS_NONE)
|
||||
, mScreenDepth(0)
|
||||
{
|
||||
mAllowDownloadableFonts = UNINITIALIZED_VALUE;
|
||||
mFallbackUsesCmaps = UNINITIALIZED_VALUE;
|
||||
|
@ -514,6 +516,7 @@ gfxPlatform::Init()
|
|||
InitLayersAccelerationPrefs();
|
||||
InitLayersIPC();
|
||||
|
||||
gPlatform->PopulateScreenInfo();
|
||||
gPlatform->ComputeTileSize();
|
||||
|
||||
nsresult rv;
|
||||
|
@ -1024,45 +1027,49 @@ gfxPlatform::ComputeTileSize()
|
|||
int32_t w = gfxPrefs::LayersTileWidth();
|
||||
int32_t h = gfxPrefs::LayersTileHeight();
|
||||
|
||||
// TODO We may want to take the screen size into consideration here.
|
||||
if (gfxPrefs::LayersTilesAdjust()) {
|
||||
gfx::IntSize screenSize = GetScreenSize();
|
||||
if (screenSize.width > 0) {
|
||||
// FIXME: we should probably make sure this is within the max texture size,
|
||||
// but I think everything should at least support 1024
|
||||
w = h = std::max(std::min(NextPowerOfTwo(screenSize.width) / 2, 1024), 256);
|
||||
}
|
||||
|
||||
#ifdef MOZ_WIDGET_GONK
|
||||
int32_t format = android::PIXEL_FORMAT_RGBA_8888;
|
||||
android::sp<android::GraphicBuffer> alloc =
|
||||
new android::GraphicBuffer(gfxPrefs::LayersTileWidth(), gfxPrefs::LayersTileHeight(),
|
||||
format,
|
||||
android::GraphicBuffer::USAGE_SW_READ_OFTEN |
|
||||
android::GraphicBuffer::USAGE_SW_WRITE_OFTEN |
|
||||
android::GraphicBuffer::USAGE_HW_TEXTURE);
|
||||
new android::GraphicBuffer(w, h, android::PIXEL_FORMAT_RGBA_8888,
|
||||
android::GraphicBuffer::USAGE_SW_READ_OFTEN |
|
||||
android::GraphicBuffer::USAGE_SW_WRITE_OFTEN |
|
||||
android::GraphicBuffer::USAGE_HW_TEXTURE);
|
||||
|
||||
if (alloc.get()) {
|
||||
w = alloc->getStride(); // We want the tiles to be gralloc stride aligned.
|
||||
// No need to adjust the height here.
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifdef XP_MACOSX
|
||||
// Use double sized tiles for HiDPI screens.
|
||||
nsCOMPtr<nsIScreenManager> screenManager =
|
||||
do_GetService("@mozilla.org/gfx/screenmanager;1");
|
||||
if (screenManager) {
|
||||
nsCOMPtr<nsIScreen> primaryScreen;
|
||||
screenManager->GetPrimaryScreen(getter_AddRefs(primaryScreen));
|
||||
double scaleFactor = 1.0;
|
||||
if (primaryScreen) {
|
||||
primaryScreen->GetContentsScaleFactor(&scaleFactor);
|
||||
}
|
||||
if (scaleFactor > 1.0) {
|
||||
w *= 2;
|
||||
h *= 2;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
SetTileSize(w, h);
|
||||
}
|
||||
|
||||
void
|
||||
gfxPlatform::PopulateScreenInfo()
|
||||
{
|
||||
nsCOMPtr<nsIScreenManager> manager = do_GetService("@mozilla.org/gfx/screenmanager;1");
|
||||
MOZ_ASSERT(manager, "failed to get nsIScreenManager");
|
||||
|
||||
nsCOMPtr<nsIScreen> screen;
|
||||
manager->GetPrimaryScreen(getter_AddRefs(screen));
|
||||
if (!screen) {
|
||||
// This can happen in xpcshell, for instance
|
||||
return;
|
||||
}
|
||||
|
||||
screen->GetColorDepth(&mScreenDepth);
|
||||
|
||||
int left, top;
|
||||
screen->GetRect(&left, &top, &mScreenSize.width, &mScreenSize.height);
|
||||
}
|
||||
|
||||
bool
|
||||
gfxPlatform::SupportsAzureContentForDrawTarget(DrawTarget* aTarget)
|
||||
{
|
||||
|
@ -2093,13 +2100,6 @@ gfxPlatform::GetLog(eGfxLog aWhichLog)
|
|||
return nullptr;
|
||||
}
|
||||
|
||||
int
|
||||
gfxPlatform::GetScreenDepth() const
|
||||
{
|
||||
NS_WARNING("GetScreenDepth not implemented on this platform -- returning 0!");
|
||||
return 0;
|
||||
}
|
||||
|
||||
mozilla::gfx::SurfaceFormat
|
||||
gfxPlatform::Optimal2DFormatForContent(gfxContentType aContent)
|
||||
{
|
||||
|
|
|
@ -568,7 +568,8 @@ public:
|
|||
*/
|
||||
static PRLogModuleInfo* GetLog(eGfxLog aWhichLog);
|
||||
|
||||
virtual int GetScreenDepth() const;
|
||||
int GetScreenDepth() const { return mScreenDepth; }
|
||||
mozilla::gfx::IntSize GetScreenSize() const { return mScreenSize; }
|
||||
|
||||
/**
|
||||
* Return the layer debugging options to use browser-wide.
|
||||
|
@ -777,6 +778,11 @@ private:
|
|||
*/
|
||||
void ComputeTileSize();
|
||||
|
||||
/**
|
||||
* This uses nsIScreenManager to determine the screen size and color depth
|
||||
*/
|
||||
void PopulateScreenInfo();
|
||||
|
||||
nsRefPtr<gfxASurface> mScreenReferenceSurface;
|
||||
nsTArray<uint32_t> mCJKPrefLangs;
|
||||
nsCOMPtr<nsIObserver> mSRGBOverrideObserver;
|
||||
|
@ -804,6 +810,9 @@ private:
|
|||
// Backend that we are compositing with. NONE, if no compositor has been
|
||||
// created yet.
|
||||
mozilla::layers::LayersBackend mCompositorBackend;
|
||||
|
||||
int32_t mScreenDepth;
|
||||
mozilla::gfx::IntSize mScreenSize;
|
||||
};
|
||||
|
||||
#endif /* GFX_PLATFORM_H */
|
||||
|
|
|
@ -363,24 +363,6 @@ gfxPlatformGtk::GetOffscreenFormat()
|
|||
return gfxImageFormat::RGB24;
|
||||
}
|
||||
|
||||
static int sDepth = 0;
|
||||
|
||||
int
|
||||
gfxPlatformGtk::GetScreenDepth() const
|
||||
{
|
||||
if (!sDepth) {
|
||||
GdkScreen *screen = gdk_screen_get_default();
|
||||
if (screen) {
|
||||
sDepth = gdk_visual_get_depth(gdk_visual_get_system());
|
||||
} else {
|
||||
sDepth = 24;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return sDepth;
|
||||
}
|
||||
|
||||
void
|
||||
gfxPlatformGtk::GetPlatformCMSOutputProfile(void *&mem, size_t &size)
|
||||
{
|
||||
|
|
|
@ -122,8 +122,6 @@ public:
|
|||
|
||||
virtual gfxImageFormat GetOffscreenFormat() override;
|
||||
|
||||
virtual int GetScreenDepth() const override;
|
||||
|
||||
bool SupportsApzWheelInput() const override {
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -355,6 +355,7 @@ private:
|
|||
DECL_GFX_PREF(Live, "layers.transaction.warning-ms", LayerTransactionWarning, uint32_t, 200);
|
||||
DECL_GFX_PREF(Once, "layers.uniformity-info", UniformityInfo, bool, false);
|
||||
DECL_GFX_PREF(Once, "layers.use-image-offscreen-surfaces", UseImageOffscreenSurfaces, bool, false);
|
||||
DECL_GFX_PREF(Live, "layers.single-tile.enabled", LayersSingleTileEnabled, bool, true);
|
||||
|
||||
DECL_GFX_PREF(Live, "layout.css.scroll-behavior.damping-ratio", ScrollBehaviorDampingRatio, float, 1.0f);
|
||||
DECL_GFX_PREF(Live, "layout.css.scroll-behavior.enabled", ScrollBehaviorEnabled, bool, false);
|
||||
|
|
|
@ -53,8 +53,8 @@ gfxQtPlatform::gfxQtPlatform()
|
|||
if (!sFontconfigUtils)
|
||||
sFontconfigUtils = gfxFontconfigUtils::GetFontconfigUtils();
|
||||
|
||||
mScreenDepth = qApp->primaryScreen()->depth();
|
||||
if (mScreenDepth == 16) {
|
||||
int32_t depth = GetScreenDepth();
|
||||
if (depth == 16) {
|
||||
sOffscreenFormat = gfxImageFormat::RGB16_565;
|
||||
}
|
||||
uint32_t canvasMask = BackendTypeBit(BackendType::CAIRO) | BackendTypeBit(BackendType::SKIA);
|
||||
|
@ -195,12 +195,6 @@ gfxQtPlatform::GetOffscreenFormat()
|
|||
return sOffscreenFormat;
|
||||
}
|
||||
|
||||
int
|
||||
gfxQtPlatform::GetScreenDepth() const
|
||||
{
|
||||
return mScreenDepth;
|
||||
}
|
||||
|
||||
already_AddRefed<ScaledFont>
|
||||
gfxQtPlatform::GetScaledFontForFont(DrawTarget* aTarget, gfxFont* aFont)
|
||||
{
|
||||
|
|
|
@ -82,8 +82,6 @@ public:
|
|||
static Screen* GetXScreen(QWindow* aWindow = 0);
|
||||
#endif
|
||||
|
||||
virtual int GetScreenDepth() const override;
|
||||
|
||||
bool AccelerateLayersByDefault() override {
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -1592,26 +1592,6 @@ gfxWindowsPlatform::IsOptimus()
|
|||
return knowIsOptimus;
|
||||
}
|
||||
|
||||
int
|
||||
gfxWindowsPlatform::GetScreenDepth() const
|
||||
{
|
||||
// if the system doesn't have all displays with the same
|
||||
// pixel format, just return 24 and move on with life.
|
||||
if (!GetSystemMetrics(SM_SAMEDISPLAYFORMAT))
|
||||
return 24;
|
||||
|
||||
HDC hdc = GetDC(nullptr);
|
||||
if (!hdc)
|
||||
return 24;
|
||||
|
||||
int depth = GetDeviceCaps(hdc, BITSPIXEL) *
|
||||
GetDeviceCaps(hdc, PLANES);
|
||||
|
||||
ReleaseDC(nullptr, hdc);
|
||||
|
||||
return depth;
|
||||
}
|
||||
|
||||
IDXGIAdapter1*
|
||||
gfxWindowsPlatform::GetDXGIAdapter()
|
||||
{
|
||||
|
|
|
@ -140,8 +140,6 @@ public:
|
|||
RENDER_MODE_MAX
|
||||
};
|
||||
|
||||
int GetScreenDepth() const;
|
||||
|
||||
RenderMode GetRenderMode() { return mRenderMode; }
|
||||
void SetRenderMode(RenderMode rmode) { mRenderMode = rmode; }
|
||||
|
||||
|
|
|
@ -5140,9 +5140,9 @@ class _GenerateProtocolActorCode(ipdl.ast.Visitor):
|
|||
+ self.invokeRecvHandler(md, implicit=0)
|
||||
+ [ Whitespace.NL ]
|
||||
+ saveIdStmts
|
||||
+ self.dtorEpilogue(md, md.actorDecl().var())
|
||||
+ [ Whitespace.NL ]
|
||||
+ self.makeReply(md, errfnRecv, routingId=idvar)
|
||||
+ [ Whitespace.NL ]
|
||||
+ self.dtorEpilogue(md, md.actorDecl().var())
|
||||
+ [ Whitespace.NL,
|
||||
StmtReturn(_Result.Processed) ])
|
||||
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
try {
|
||||
evalInWorker(`
|
||||
function f() { f(); }
|
||||
try { f(); } catch(e) {}
|
||||
`);
|
||||
} catch(e) {
|
||||
assertEq(e.toString().includes("--no-threads"), true);
|
||||
}
|
|
@ -2165,6 +2165,19 @@ IonBuilder::inlineIsPossiblyWrappedTypedArray(CallInfo& callInfo)
|
|||
return inlineIsTypedArrayHelper(callInfo, AllowWrappedTypedArrays);
|
||||
}
|
||||
|
||||
static bool
|
||||
IsTypedArrayObject(CompilerConstraintList* constraints, MDefinition* def)
|
||||
{
|
||||
MOZ_ASSERT(def->type() == MIRType_Object);
|
||||
|
||||
TemporaryTypeSet* types = def->resultTypeSet();
|
||||
if (!types)
|
||||
return false;
|
||||
|
||||
return types->forAllClasses(constraints, IsTypedArrayClass) ==
|
||||
TemporaryTypeSet::ForAllResult::ALL_TRUE;
|
||||
}
|
||||
|
||||
IonBuilder::InliningStatus
|
||||
IonBuilder::inlineTypedArrayLength(CallInfo& callInfo)
|
||||
{
|
||||
|
@ -2175,8 +2188,10 @@ IonBuilder::inlineTypedArrayLength(CallInfo& callInfo)
|
|||
if (getInlineReturnType() != MIRType_Int32)
|
||||
return InliningStatus_NotInlined;
|
||||
|
||||
// We assume that when calling this function we always
|
||||
// have a TypedArray. The native asserts that as well.
|
||||
// Note that the argument we see here is not necessarily a typed array.
|
||||
// If it's not, this call should be unreachable though.
|
||||
if (!IsTypedArrayObject(constraints(), callInfo.getArg(0)))
|
||||
return InliningStatus_NotInlined;
|
||||
|
||||
MInstruction* length = addTypedArrayLength(callInfo.getArg(0));
|
||||
current->push(length);
|
||||
|
@ -2210,19 +2225,10 @@ IonBuilder::inlineSetDisjointTypedElements(CallInfo& callInfo)
|
|||
// Only attempt to optimize if |target| and |sourceTypedArray| are both
|
||||
// definitely typed arrays. (The former always is. The latter is not,
|
||||
// necessarily, because of wrappers.)
|
||||
|
||||
MDefinition* arrays[] = { target, sourceTypedArray };
|
||||
|
||||
for (MDefinition* def : arrays) {
|
||||
TemporaryTypeSet* types = def->resultTypeSet();
|
||||
if (!types)
|
||||
return InliningStatus_NotInlined;
|
||||
|
||||
if (types->forAllClasses(constraints(), IsTypedArrayClass) !=
|
||||
TemporaryTypeSet::ForAllResult::ALL_TRUE)
|
||||
{
|
||||
return InliningStatus_NotInlined;
|
||||
}
|
||||
if (!IsTypedArrayObject(constraints(), target) ||
|
||||
!IsTypedArrayObject(constraints(), sourceTypedArray))
|
||||
{
|
||||
return InliningStatus_NotInlined;
|
||||
}
|
||||
|
||||
auto sets = MSetDisjointTypedElements::New(alloc(), target, targetOffset, sourceTypedArray);
|
||||
|
|
|
@ -153,7 +153,7 @@ MacroAssembler::guardObjectType(Register obj, const TypeSet* types,
|
|||
{
|
||||
MOZ_ASSERT(!types->unknown());
|
||||
MOZ_ASSERT(!types->hasType(TypeSet::AnyObjectType()));
|
||||
MOZ_ASSERT(scratch != InvalidReg);
|
||||
MOZ_ASSERT_IF(types->getObjectCount() > 0, scratch != InvalidReg);
|
||||
|
||||
// Note: this method elides read barriers on values read from type sets, as
|
||||
// this may be called off the main thread during Ion compilation. This is
|
||||
|
@ -215,7 +215,6 @@ MacroAssembler::guardObjectType(Register obj, const TypeSet* types,
|
|||
lastBranch.emit(*this);
|
||||
|
||||
bind(&matched);
|
||||
return;
|
||||
}
|
||||
|
||||
template void MacroAssembler::guardTypeSet(const Address& address, const TypeSet* types,
|
||||
|
|
|
@ -2604,7 +2604,8 @@ EvalInWorker(JSContext* cx, unsigned argc, Value* vp)
|
|||
return false;
|
||||
|
||||
PRThread* thread = PR_CreateThread(PR_USER_THREAD, WorkerMain, input,
|
||||
PR_PRIORITY_NORMAL, PR_GLOBAL_THREAD, PR_JOINABLE_THREAD, 0);
|
||||
PR_PRIORITY_NORMAL, PR_GLOBAL_THREAD, PR_JOINABLE_THREAD,
|
||||
gMaxStackSize + 128 * 1024);
|
||||
if (!thread || !workerThreads.append(thread))
|
||||
return false;
|
||||
|
||||
|
@ -6129,6 +6130,8 @@ SetWorkerRuntimeOptions(JSRuntime* rt)
|
|||
if (*gZealStr)
|
||||
rt->gc.parseAndSetZeal(gZealStr);
|
||||
#endif
|
||||
|
||||
JS_SetNativeStackQuota(rt, gMaxStackSize);
|
||||
}
|
||||
|
||||
static int
|
||||
|
|
|
@ -335,7 +335,7 @@ TemporaryTypeSet::TemporaryTypeSet(LifoAlloc* alloc, Type type)
|
|||
}
|
||||
|
||||
bool
|
||||
TypeSet::mightBeMIRType(jit::MIRType type)
|
||||
TypeSet::mightBeMIRType(jit::MIRType type) const
|
||||
{
|
||||
if (unknown())
|
||||
return true;
|
||||
|
|
|
@ -475,7 +475,7 @@ class TypeSet
|
|||
}
|
||||
|
||||
/* Whether any values in this set might have the specified type. */
|
||||
bool mightBeMIRType(jit::MIRType type);
|
||||
bool mightBeMIRType(jit::MIRType type) const;
|
||||
|
||||
/*
|
||||
* Get whether this type set is known to be a subset of other.
|
||||
|
|
|
@ -2094,9 +2094,6 @@ ContainerState::GetLayerCreationHint(const nsIFrame* aAnimatedGeometryRoot)
|
|||
{
|
||||
// Check whether the layer will be scrollable. This is used as a hint to
|
||||
// influence whether tiled layers are used or not.
|
||||
if (mParameters.mInLowPrecisionDisplayPort) {
|
||||
return LayerManager::SCROLLABLE;
|
||||
}
|
||||
|
||||
// Check whether there's any active scroll frame on the animated geometry
|
||||
// root chain.
|
||||
|
|
|
@ -69,7 +69,7 @@ fuzzy(3,7860) fuzzy-if(cocoaWidget,5,89041) fuzzy-if(azureSkiaGL,4,90000) == rad
|
|||
== radial-position-1a.html radial-position-1-ref.html
|
||||
fuzzy-if(cocoaWidget,1,28) fuzzy-if(winWidget,1,18) == radial-position-1b.html radial-position-1-ref.html
|
||||
fuzzy-if(cocoaWidget,4,22317) fuzzy-if(Android,8,771) == radial-shape-closest-corner-1a.html radial-shape-closest-corner-1-ref.html
|
||||
fuzzy(1,238) fuzzy-if(cocoaWidget,4,22608) fuzzy-if((/^Windows\x20NT\x2010\.0/.test(http.oscpu)||/^Windows\x20NT\x206\./.test(http.oscpu))&&d2d,1,336) fuzzy-if(Android,8,787) == radial-shape-closest-corner-1b.html radial-shape-closest-corner-1-ref.html
|
||||
fuzzy(1,238) fuzzy-if(cocoaWidget,4,22608) fuzzy-if((/^Windows\x20NT\x2010\.0/.test(http.oscpu)||/^Windows\x20NT\x206\./.test(http.oscpu))&&d2d,1,336) fuzzy-if(Android,8,787) fuzzy-if(B2G,1,242) == radial-shape-closest-corner-1b.html radial-shape-closest-corner-1-ref.html
|
||||
fuzzy-if(azureQuartz,2,41171) fuzzy-if(Android,8,771) == radial-shape-closest-corner-1c.html radial-shape-closest-corner-1-ref.html
|
||||
fuzzy-if(/^Windows\x20NT\x2010\.0/.test(http.oscpu)||/^Windows\x20NT\x206\.2/.test(http.oscpu),1,5) fuzzy-if(Android,17,3880) == radial-shape-closest-side-1a.html radial-shape-closest-side-1-ref.html
|
||||
fuzzy-if(/^Windows\x20NT\x2010\.0/.test(http.oscpu)||/^Windows\x20NT\x206\.2/.test(http.oscpu),1,5) fuzzy-if(Android,17,3880) == radial-shape-closest-side-1b.html radial-shape-closest-side-1-ref.html
|
||||
|
|
|
@ -33,7 +33,7 @@ include svg-integration/reftest.list
|
|||
== clip-02a.svg clip-02-ref.svg
|
||||
== clip-02b.svg clip-02-ref.svg
|
||||
== clipPath-advanced-01.svg pass.svg
|
||||
fuzzy-if(/^Windows\x20NT\x2010\.0/.test(http.oscpu)||/^Windows\x20NT\x206\.[12]/.test(http.oscpu),1,5) fuzzy-if(azureQuartz,1,6) fuzzy-if(OSX,1,2) == clipPath-and-shape-rendering-01.svg clipPath-and-shape-rendering-01-ref.svg # bug 614840
|
||||
fuzzy-if(/^Windows\x20NT\x2010\.0/.test(http.oscpu)||/^Windows\x20NT\x206\.[12]/.test(http.oscpu),1,5) fuzzy-if(azureQuartz,1,6) fuzzy-if(OSX,1,6) == clipPath-and-shape-rendering-01.svg clipPath-and-shape-rendering-01-ref.svg # bug 614840
|
||||
== clipPath-and-transform-01.svg pass.svg
|
||||
== clipPath-basic-01.svg pass.svg
|
||||
== clipPath-basic-02.svg pass.svg
|
||||
|
|
|
@ -4,7 +4,7 @@ fuzzy-if(Android,16,244) skip-if(B2G||Mulet) HTTP(..) == marker-basic.html marke
|
|||
skip-if(B2G||Mulet) HTTP(..) == marker-string.html marker-string-ref.html # Initial mulet triage: parity with B2G/B2G Desktop
|
||||
skip-if(Android||B2G) HTTP(..) == bidi-simple.html bidi-simple-ref.html # Fails on Android due to anti-aliasing
|
||||
skip-if(!gtkWidget) fuzzy-if(gtkWidget,2,289) HTTP(..) == bidi-simple-scrolled.html bidi-simple-scrolled-ref.html # Fails on Windows and OSX due to anti-aliasing
|
||||
skip-if(B2G||Mulet) fuzzy-if(Android&&AndroidVersion<15,9,2545) fuzzy-if(Android&&AndroidVersion>=15,24,4000) fuzzy-if(cocoaWidget,1,40) fuzzy-if(asyncPan&&!layersGPUAccelerated,102,1770) HTTP(..) == scroll-rounding.html scroll-rounding-ref.html # bug 760264 # Initial mulet triage: parity with B2G/B2G Desktop
|
||||
skip-if(B2G||Mulet) fuzzy-if(Android&&AndroidVersion<15,206,41) fuzzy-if(Android&&AndroidVersion>=15,24,4000) fuzzy-if(cocoaWidget,1,40) fuzzy-if(asyncPan&&!layersGPUAccelerated,102,1770) HTTP(..) == scroll-rounding.html scroll-rounding-ref.html # bug 760264 # Initial mulet triage: parity with B2G/B2G Desktop
|
||||
fuzzy-if(OSX==1008,1,1) HTTP(..) == anonymous-block.html anonymous-block-ref.html
|
||||
skip-if(B2G||Mulet) HTTP(..) == false-marker-overlap.html false-marker-overlap-ref.html # Initial mulet triage: parity with B2G/B2G Desktop
|
||||
HTTP(..) == visibility-hidden.html visibility-hidden-ref.html
|
||||
|
|
|
@ -262,6 +262,15 @@ nsCSSCompressedDataBlock::MapRuleInfoInto(nsRuleData *aRuleData) const
|
|||
nsCSSValue* target = aRuleData->ValueFor(iProp);
|
||||
if (target->GetUnit() == eCSSUnit_Null) {
|
||||
const nsCSSValue *val = ValueAtIndex(i);
|
||||
// In order for variable resolution to have the right information
|
||||
// about the stylesheet level of a value, that level needs to be
|
||||
// stored on the token stream. We can't do that at creation time
|
||||
// because the CSS parser (which creates the object) has no idea
|
||||
// about the stylesheet level, so we do it here instead, where
|
||||
// the rule walking will have just updated aRuleData.
|
||||
if (val->GetUnit() == eCSSUnit_TokenStream) {
|
||||
val->GetTokenStreamValue()->mLevel = aRuleData->mLevel;
|
||||
}
|
||||
MapSinglePropertyInto(iProp, val, target, aRuleData);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,6 +23,7 @@
|
|||
#include "nsPresContext.h"
|
||||
#include "nsStyleUtil.h"
|
||||
#include "nsDeviceContext.h"
|
||||
#include "nsStyleSet.h"
|
||||
|
||||
using namespace mozilla;
|
||||
|
||||
|
@ -2537,6 +2538,7 @@ nsCSSValueGradient::SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) con
|
|||
nsCSSValueTokenStream::nsCSSValueTokenStream()
|
||||
: mPropertyID(eCSSProperty_UNKNOWN)
|
||||
, mShorthandPropertyID(eCSSProperty_UNKNOWN)
|
||||
, mLevel(nsStyleSet::eSheetTypeCount)
|
||||
{
|
||||
MOZ_COUNT_CTOR(nsCSSValueTokenStream);
|
||||
}
|
||||
|
|
|
@ -1541,6 +1541,7 @@ public:
|
|||
// mozilla::CSSStyleSheet* mSheet;
|
||||
uint32_t mLineNumber;
|
||||
uint32_t mLineOffset;
|
||||
uint16_t mLevel; // an nsStyleSet::sheetType
|
||||
|
||||
private:
|
||||
nsCSSValueTokenStream(const nsCSSValueTokenStream& aOther) = delete;
|
||||
|
|
Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше
Загрузка…
Ссылка в новой задаче